Java tutorial
/* * (C) Copyright 2006-2010 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Thierry Delprat */ package org.nuxeo.apidoc.documentation; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.apidoc.api.DocumentationItem; import org.nuxeo.apidoc.api.NuxeoArtifact; import org.nuxeo.apidoc.api.QueryHelper; import org.nuxeo.apidoc.search.ArtifactSearcher; import org.nuxeo.apidoc.search.ArtifactSearcherImpl; import org.nuxeo.apidoc.security.SecurityConstants; import org.nuxeo.common.utils.IdUtils; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.Blobs; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.DocumentRef; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.PathRef; import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; import org.nuxeo.ecm.core.api.security.ACE; import org.nuxeo.ecm.core.api.security.ACL; import org.nuxeo.ecm.core.api.security.ACP; import org.nuxeo.ecm.core.api.security.impl.ACLImpl; import org.nuxeo.ecm.core.io.DocumentPipe; import org.nuxeo.ecm.core.io.DocumentReader; import org.nuxeo.ecm.core.io.DocumentTransformer; import org.nuxeo.ecm.core.io.DocumentWriter; import org.nuxeo.ecm.core.io.ExportedDocument; import org.nuxeo.ecm.core.io.impl.DocumentPipeImpl; import org.nuxeo.ecm.core.io.impl.plugins.DocumentModelWriter; import org.nuxeo.ecm.core.io.impl.plugins.NuxeoArchiveReader; import org.nuxeo.ecm.core.io.impl.plugins.NuxeoArchiveWriter; import org.nuxeo.ecm.core.query.sql.NXQL; import org.nuxeo.ecm.directory.Session; import org.nuxeo.ecm.directory.api.DirectoryService; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.model.DefaultComponent; public class DocumentationComponent extends DefaultComponent implements DocumentationService { public static final String DIRECTORY_NAME = "documentationTypes"; public static final String Root_PATH = "/"; public static final String Root_NAME = "nuxeo-api-doc"; public static final String Read_Grp = SecurityConstants.Read_Group; public static final String Write_Grp = SecurityConstants.Write_Group; protected static final Log log = LogFactory.getLog(DocumentationComponent.class); protected final ArtifactSearcher searcher = new ArtifactSearcherImpl(); class UnrestrictedRootCreator extends UnrestrictedSessionRunner { protected DocumentRef rootRef; public DocumentRef getRootRef() { return rootRef; } UnrestrictedRootCreator(CoreSession session) { super(session); } @Override public void run() { DocumentModel root = session.createDocumentModel(Root_PATH, Root_NAME, "Folder"); root.setProperty("dublincore", "title", Root_NAME); root = session.createDocument(root); ACL acl = new ACLImpl(); acl.add(new ACE(Write_Grp, "Write", true)); acl.add(new ACE(Read_Grp, "Read", true)); ACP acp = root.getACP(); acp.addACL(acl); session.setACP(root.getRef(), acp, true); rootRef = root.getRef(); // flush caches session.save(); } } protected DocumentModel getDocumentationRoot(CoreSession session) { DocumentRef rootRef = new PathRef(Root_PATH + Root_NAME); if (session.exists(rootRef)) { return session.getDocument(rootRef); } UnrestrictedRootCreator creator = new UnrestrictedRootCreator(session); creator.runUnrestricted(); // flush caches session.save(); return session.getDocument(creator.getRootRef()); } @Override @SuppressWarnings("unchecked") public <T> T getAdapter(Class<T> adapter) { if (adapter.isAssignableFrom(DocumentationService.class)) { return (T) this; } else if (adapter.isAssignableFrom(ArtifactSearcher.class)) { return (T) searcher; } return null; } @Override public Map<String, List<DocumentationItem>> listDocumentationItems(CoreSession session, String category, String targetType) { String query = "SELECT * FROM " + DocumentationItem.TYPE_NAME + " WHERE " + QueryHelper.NOT_DELETED; if (category != null) { query += " AND " + DocumentationItem.PROP_TYPE + " = " + NXQL.escapeString(category); } if (targetType != null) { query += " AND " + DocumentationItem.PROP_TARGET_TYPE + " = " + NXQL.escapeString(targetType); } query += " ORDER BY " + DocumentationItem.PROP_DOCUMENTATION_ID + ", dc:modified"; List<DocumentModel> docs = session.query(query); Map<String, List<DocumentationItem>> sortMap = new HashMap<>(); for (DocumentModel doc : docs) { DocumentationItem item = doc.getAdapter(DocumentationItem.class); List<DocumentationItem> alternatives = sortMap.get(item.getId()); if (alternatives == null) { alternatives = new ArrayList<>(); alternatives.add(item); sortMap.put(item.getId(), alternatives); } else { alternatives.add(item); } } List<DocumentationItem> result = new ArrayList<>(); for (String documentationId : sortMap.keySet()) { DocumentationItem bestDoc = sortMap.get(documentationId).get(0); result.add(bestDoc); } Map<String, List<DocumentationItem>> sortedResult = new HashMap<>(); Map<String, String> categories = getCategories(); for (DocumentationItem item : result) { String key = item.getType(); String label = categories.get(key); if (sortedResult.containsKey(label)) { sortedResult.get(label).add(item); } else { List<DocumentationItem> items = new ArrayList<>(); items.add(item); sortedResult.put(label, items); } } return sortedResult; } @Override public List<DocumentationItem> findDocumentItems(CoreSession session, NuxeoArtifact nxItem) { String id = nxItem.getId(); String type = nxItem.getArtifactType(); String query = "SELECT * FROM " + DocumentationItem.TYPE_NAME + " WHERE " + DocumentationItem.PROP_TARGET + " = " + NXQL.escapeString(id) + " AND " + DocumentationItem.PROP_TARGET_TYPE + " = " + NXQL.escapeString(type) + " AND " + QueryHelper.NOT_DELETED + " ORDER BY " + DocumentationItem.PROP_DOCUMENTATION_ID + ", dc:modified"; List<DocumentModel> docs = session.query(query); Map<String, List<DocumentationItem>> sortMap = new HashMap<>(); for (DocumentModel doc : docs) { DocumentationItem item = doc.getAdapter(DocumentationItem.class); List<DocumentationItem> alternatives = sortMap.get(item.getId()); if (alternatives == null) { alternatives = new ArrayList<>(); alternatives.add(item); sortMap.put(item.getId(), alternatives); } else { alternatives.add(item); } } List<DocumentationItem> result = new ArrayList<>(); for (String documentationId : sortMap.keySet()) { DocumentationItem bestDoc = findBestMatch(nxItem, sortMap.get(documentationId)); result.add(bestDoc); } return result; } protected DocumentationItem findBestMatch(NuxeoArtifact nxItem, List<DocumentationItem> docItems) { for (DocumentationItem docItem : docItems) { // get first possible because already sorted on modification date if (docItem.getApplicableVersion().contains(nxItem.getVersion())) { return docItem; } } // XXX may be find the closest match ? return docItems.get(0); } @Override public List<DocumentationItem> findDocumentationItemVariants(CoreSession session, DocumentationItem item) { List<DocumentationItem> result = new ArrayList<>(); List<DocumentModel> docs = findDocumentModelVariants(session, item); for (DocumentModel doc : docs) { DocumentationItem docItem = doc.getAdapter(DocumentationItem.class); if (docItem != null) { result.add(docItem); } } Collections.sort(result); Collections.reverse(result); return result; } public List<DocumentModel> findDocumentModelVariants(CoreSession session, DocumentationItem item) { String id = item.getId(); String type = item.getTargetType(); String query = "SELECT * FROM " + DocumentationItem.TYPE_NAME + " WHERE " + DocumentationItem.PROP_DOCUMENTATION_ID + " = " + NXQL.escapeString(id) + " AND " + DocumentationItem.PROP_TARGET_TYPE + " = " + NXQL.escapeString(type) + " AND " + QueryHelper.NOT_DELETED; query += " ORDER BY dc:created"; return session.query(query); } @Override public DocumentationItem createDocumentationItem(CoreSession session, NuxeoArtifact item, String title, String content, String type, List<String> applicableVersions, boolean approved, String renderingType) { DocumentModel doc = session.createDocumentModel(DocumentationItem.TYPE_NAME); String name = title + '-' + item.getId(); name = IdUtils.generateId(name, "-", true, 64); UUID docUUID = UUID.nameUUIDFromBytes(name.getBytes()); doc.setPathInfo(getDocumentationRoot(session).getPathAsString(), name); doc.setPropertyValue("dc:title", title); Blob blob = Blobs.createBlob(content); blob.setFilename(type); doc.setPropertyValue("file:content", (Serializable) blob); doc.setPropertyValue(DocumentationItem.PROP_TARGET, item.getId()); doc.setPropertyValue(DocumentationItem.PROP_TARGET_TYPE, item.getArtifactType()); doc.setPropertyValue(DocumentationItem.PROP_DOCUMENTATION_ID, docUUID.toString()); doc.setPropertyValue(DocumentationItem.PROP_NUXEO_APPROVED, Boolean.valueOf(approved)); doc.setPropertyValue(DocumentationItem.PROP_TYPE, type); doc.setPropertyValue(DocumentationItem.PROP_RENDERING_TYPE, renderingType); doc.setPropertyValue(DocumentationItem.PROP_APPLICABLE_VERSIONS, (Serializable) applicableVersions); doc = session.createDocument(doc); session.save(); return doc.getAdapter(DocumentationItem.class); } @Override public void deleteDocumentationItem(CoreSession session, String uuid) { DocumentModel doc = session.getDocument(new IdRef(uuid)); // check type if (!doc.getType().equals(DocumentationItem.TYPE_NAME)) { throw new RuntimeException("Invalid documentation item"); } // check under our root DocumentModel root = getDocumentationRoot(session); DocumentModel parent = session.getDocument(doc.getParentRef()); if (!root.getId().equals(parent.getId())) { throw new RuntimeException("Invalid documentation item"); } // ok to delete session.removeDocument(doc.getRef()); } protected DocumentModel updateDocumentModel(DocumentModel doc, DocumentationItem item) { doc.setPropertyValue("dc:title", item.getTitle()); Blob content = Blobs.createBlob(item.getContent()); content.setFilename(item.getTypeLabel()); doc.setPropertyValue("file:content", (Serializable) content); doc.setPropertyValue(DocumentationItem.PROP_DOCUMENTATION_ID, item.getId()); doc.setPropertyValue(DocumentationItem.PROP_NUXEO_APPROVED, Boolean.valueOf(item.isApproved())); doc.setPropertyValue(DocumentationItem.PROP_RENDERING_TYPE, item.getRenderingType()); doc.setPropertyValue(DocumentationItem.PROP_APPLICABLE_VERSIONS, (Serializable) item.getApplicableVersion()); List<Map<String, Serializable>> atts = new ArrayList<>(); Map<String, String> attData = item.getAttachments(); if (attData != null && attData.size() > 0) { for (String fileName : attData.keySet()) { Map<String, Serializable> fileItem = new HashMap<>(); Blob blob = Blobs.createBlob(attData.get(fileName)); blob.setFilename(fileName); fileItem.put("file", (Serializable) blob); fileItem.put("filename", fileName); atts.add(fileItem); } doc.setPropertyValue("files:files", (Serializable) atts); } return doc; } @Override public DocumentationItem updateDocumentationItem(CoreSession session, DocumentationItem docItem) { DocumentModel existingDoc = session.getDocument(new IdRef(docItem.getUUID())); DocumentationItem existingDocItem = existingDoc.getAdapter(DocumentationItem.class); List<String> applicableVersions = docItem.getApplicableVersion(); List<String> existingApplicableVersions = existingDocItem.getApplicableVersion(); List<String> discardedVersion = new ArrayList<>(); for (String version : existingApplicableVersions) { if (!applicableVersions.contains(version)) { discardedVersion.add(version); } // XXX check for older versions in case of inconsistent // applicableVersions values ... } if (discardedVersion.size() > 0) { // save old version String newName = existingDoc.getName(); Collections.sort(discardedVersion); for (String version : discardedVersion) { newName = newName + "_" + version; } newName = IdUtils.generateId(newName, "-", true, 100); DocumentModel discardedDoc = session.copy(existingDoc.getRef(), existingDoc.getParentRef(), newName, new CoreSession.CopyOption[0]); discardedDoc.setPropertyValue(DocumentationItem.PROP_APPLICABLE_VERSIONS, (Serializable) discardedVersion); discardedDoc = session.saveDocument(discardedDoc); } existingDoc = updateDocumentModel(existingDoc, docItem); existingDoc = session.saveDocument(existingDoc); session.save(); return existingDoc.getAdapter(DocumentationItem.class); } protected List<DocumentModel> listCategories() { DirectoryService dm = Framework.getService(DirectoryService.class); try (Session session = dm.open(DIRECTORY_NAME)) { return session.query(Collections.<String, Serializable>emptyMap(), null, Collections.singletonMap("ordering", "ASC")); } } @Override public List<String> getCategoryKeys() { List<String> categories = new ArrayList<>(); for (DocumentModel entry : listCategories()) { categories.add(entry.getId()); } return categories; } @Override public Map<String, String> getCategories() { Map<String, String> categories = new LinkedHashMap<>(); if (!Framework.isTestModeSet()) { for (DocumentModel entry : listCategories()) { String value = (String) entry.getProperty("vocabulary", "label"); categories.put(entry.getId(), value); } } else { categories.put("description", "Description"); categories.put("codeSample", "Code Sample"); categories.put("howTo", "How To"); } return categories; } @Override public void exportDocumentation(CoreSession session, OutputStream out) { try { String query = "SELECT * FROM " + DocumentationItem.TYPE_NAME + " WHERE " + QueryHelper.NOT_DELETED; DocumentModelList docList = session.query(query); DocumentReader reader = new DocumentModelListReader(docList); DocumentWriter writer = new NuxeoArchiveWriter(out); DocumentPipe pipe = new DocumentPipeImpl(10); pipe.setReader(reader); pipe.setWriter(writer); pipe.run(); reader.close(); writer.close(); } catch (IOException | NuxeoException e) { log.error("Error while exporting documentation", e); } } @Override public void importDocumentation(CoreSession session, InputStream is) { try { String importPath = getDocumentationRoot(session).getPathAsString(); DocumentReader reader = new NuxeoArchiveReader(is); DocumentWriter writer = new DocumentModelWriter(session, importPath); DocumentPipe pipe = new DocumentPipeImpl(10); pipe.setReader(reader); pipe.setWriter(writer); DocumentTransformer rootCutter = new DocumentTransformer() { @Override public boolean transform(ExportedDocument doc) { doc.setPath(doc.getPath().removeFirstSegments(1)); return true; } }; pipe.addTransformer(rootCutter); pipe.run(); reader.close(); writer.close(); } catch (IOException | NuxeoException e) { log.error("Error while importing documentation", e); } } @Override public String getDocumentationStats(CoreSession session) { String query = "SELECT * FROM " + DocumentationItem.TYPE_NAME + " WHERE " + QueryHelper.NOT_DELETED; DocumentModelList docList = session.query(query); return docList.size() + " documents"; } @Override public Map<String, DocumentationItem> getAvailableDescriptions(CoreSession session, String targetType) { Map<String, List<DocumentationItem>> itemsByCat = listDocumentationItems(session, DefaultDocumentationType.DESCRIPTION.getValue(), targetType); Map<String, DocumentationItem> result = new HashMap<>(); if (itemsByCat.size() > 0) { String labelKey = itemsByCat.keySet().iterator().next(); List<DocumentationItem> docs = itemsByCat.get(labelKey); for (DocumentationItem doc : docs) { result.put(doc.getTarget(), doc); } } return result; } }