001/*
002 * (C) Copyright 2006-2010 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Thierry Delprat
016 */
017package org.nuxeo.apidoc.repository;
018
019import java.io.IOException;
020import java.io.Serializable;
021import java.util.Arrays;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027import org.nuxeo.apidoc.adapters.BundleGroupDocAdapter;
028import org.nuxeo.apidoc.adapters.BundleInfoDocAdapter;
029import org.nuxeo.apidoc.adapters.ComponentInfoDocAdapter;
030import org.nuxeo.apidoc.adapters.ExtensionInfoDocAdapter;
031import org.nuxeo.apidoc.adapters.ExtensionPointInfoDocAdapter;
032import org.nuxeo.apidoc.adapters.OperationInfoDocAdapter;
033import org.nuxeo.apidoc.adapters.SeamComponentInfoDocAdapter;
034import org.nuxeo.apidoc.adapters.ServiceInfoDocAdapter;
035import org.nuxeo.apidoc.api.BundleGroup;
036import org.nuxeo.apidoc.api.BundleInfo;
037import org.nuxeo.apidoc.api.ComponentInfo;
038import org.nuxeo.apidoc.api.DocumentationItem;
039import org.nuxeo.apidoc.api.ExtensionInfo;
040import org.nuxeo.apidoc.api.ExtensionPointInfo;
041import org.nuxeo.apidoc.api.NuxeoArtifact;
042import org.nuxeo.apidoc.api.OperationInfo;
043import org.nuxeo.apidoc.api.SeamComponentInfo;
044import org.nuxeo.apidoc.api.ServiceInfo;
045import org.nuxeo.apidoc.documentation.DocumentationItemDocAdapter;
046import org.nuxeo.apidoc.documentation.DocumentationService;
047import org.nuxeo.apidoc.documentation.ResourceDocumentationItem;
048import org.nuxeo.apidoc.introspection.BundleGroupImpl;
049import org.nuxeo.apidoc.introspection.BundleInfoImpl;
050import org.nuxeo.apidoc.introspection.OperationInfoImpl;
051import org.nuxeo.apidoc.snapshot.DistributionSnapshot;
052import org.nuxeo.apidoc.snapshot.SnapshotFilter;
053import org.nuxeo.ecm.automation.OperationException;
054import org.nuxeo.ecm.core.api.Blob;
055import org.nuxeo.ecm.core.api.Blobs;
056import org.nuxeo.ecm.core.api.CoreSession;
057import org.nuxeo.ecm.core.api.DocumentModel;
058import org.nuxeo.ecm.core.api.DocumentRef;
059import org.nuxeo.ecm.core.api.NuxeoException;
060import org.nuxeo.ecm.core.api.PathRef;
061import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
062import org.nuxeo.ecm.core.api.security.ACE;
063import org.nuxeo.ecm.core.api.security.ACL;
064import org.nuxeo.ecm.core.api.security.ACP;
065import org.nuxeo.ecm.core.api.security.impl.ACLImpl;
066import org.nuxeo.runtime.api.Framework;
067
068public class SnapshotPersister {
069
070    public static final String Root_PATH = "/";
071
072    public static final String Root_NAME = "nuxeo-distributions";
073
074    public static final String Seam_Root_NAME = "Seam";
075
076    public static final String Operation_Root_NAME = "Automation";
077
078    public static final String Bundle_Root_NAME = "Bundles";
079
080    public static final String Read_Grp = "Everyone";
081
082    public static final String Write_Grp = "members";
083
084    protected static final Log log = LogFactory.getLog(SnapshotPersister.class);
085
086    class UnrestrictedRootCreator extends UnrestrictedSessionRunner {
087
088        protected DocumentRef rootRef;
089
090        protected final String parentPath;
091
092        protected final String name;
093
094        protected final boolean setAcl;
095
096        public UnrestrictedRootCreator(CoreSession session, String parentPath, String name, boolean setAcl) {
097            super(session);
098            this.name = name;
099            this.parentPath = parentPath;
100            this.setAcl = setAcl;
101        }
102
103        public DocumentRef getRootRef() {
104            return rootRef;
105        }
106
107        @Override
108        public void run() {
109
110            DocumentModel root = session.createDocumentModel(parentPath, name, "Workspace");
111            root.setProperty("dublincore", "title", name);
112            root = session.createDocument(root);
113
114            if (setAcl) {
115                ACL acl = new ACLImpl();
116                acl.add(new ACE(Write_Grp, "Write", true));
117                acl.add(new ACE(Read_Grp, "Read", true));
118                ACP acp = root.getACP();
119                acp.addACL(acl);
120                session.setACP(root.getRef(), acp, true);
121            }
122
123            rootRef = root.getRef();
124            // flush caches
125            session.save();
126        }
127
128    }
129
130    public DocumentModel getSubRoot(CoreSession session, DocumentModel root, String name) {
131
132        DocumentRef rootRef = new PathRef(root.getPathAsString() + name);
133        if (session.exists(rootRef)) {
134            return session.getDocument(rootRef);
135        }
136        UnrestrictedRootCreator creator = new UnrestrictedRootCreator(session, root.getPathAsString(), name, false);
137        creator.runUnrestricted();
138        // flush caches
139        session.save();
140        return session.getDocument(creator.getRootRef());
141    }
142
143    public DocumentModel getDistributionRoot(CoreSession session) {
144        DocumentRef rootRef = new PathRef(Root_PATH + Root_NAME);
145        if (session.exists(rootRef)) {
146            return session.getDocument(rootRef);
147        }
148        UnrestrictedRootCreator creator = new UnrestrictedRootCreator(session, Root_PATH, Root_NAME, true);
149        creator.runUnrestricted();
150        // flush caches
151        session.save();
152        return session.getDocument(creator.getRootRef());
153    }
154
155    public DistributionSnapshot persist(DistributionSnapshot snapshot, CoreSession session, String label,
156            SnapshotFilter filter) {
157
158        RepositoryDistributionSnapshot distribContainer = createDistributionDoc(snapshot, session, label);
159
160        if (filter == null) {
161            // If no filter, clean old entries
162            distribContainer.cleanPreviousArtifacts();
163        }
164
165        DocumentModel bundleContainer = getSubRoot(session, distribContainer.getDoc(), Bundle_Root_NAME);
166
167        if (filter != null) {
168            // create VGroup that contain,s only the target bundles
169            BundleGroupImpl vGroup = new BundleGroupImpl(filter.getBundleGroupName(), snapshot.getVersion());
170            for (String bundleId : snapshot.getBundleIds()) {
171                if (filter.includeBundleId(bundleId)) {
172                    vGroup.add(bundleId);
173                }
174            }
175            persistBundleGroup(snapshot, vGroup, session, label + "-bundles", bundleContainer);
176        } else {
177            List<BundleGroup> bundleGroups = snapshot.getBundleGroups();
178            for (BundleGroup bundleGroup : bundleGroups) {
179                persistBundleGroup(snapshot, bundleGroup, session, label, bundleContainer);
180            }
181        }
182
183        DocumentModel seamContainer = getSubRoot(session, distribContainer.getDoc(), Seam_Root_NAME);
184        persistSeamComponents(snapshot, snapshot.getSeamComponents(), session, label, seamContainer, filter);
185
186        DocumentModel opContainer = getSubRoot(session, distribContainer.getDoc(), Operation_Root_NAME);
187        persistOperations(snapshot, snapshot.getOperations(), session, label, opContainer, filter);
188
189        return distribContainer;
190    }
191
192    public void persistSeamComponents(DistributionSnapshot snapshot, List<SeamComponentInfo> seamComponents,
193            CoreSession session, String label, DocumentModel parent, SnapshotFilter filter) {
194        for (SeamComponentInfo seamComponent : seamComponents) {
195            if (filter == null || filter.includeSeamComponent(seamComponent)) {
196                persistSeamComponent(snapshot, seamComponent, session, label, parent);
197            }
198        }
199    }
200
201    public void persistSeamComponent(DistributionSnapshot snapshot, SeamComponentInfo seamComponent,
202            CoreSession session, String label, DocumentModel parent) {
203        SeamComponentInfoDocAdapter.create(seamComponent, session, parent.getPathAsString());
204    }
205
206    public void persistOperations(DistributionSnapshot snapshot, List<OperationInfo> operations, CoreSession session,
207            String label, DocumentModel parent, SnapshotFilter filter) {
208        for (OperationInfo op : operations) {
209            if (filter == null || (op instanceof OperationInfoImpl && filter.includeOperation((OperationInfoImpl) op))) {
210                persistOperation(snapshot, op, session, label, parent);
211            }
212        }
213    }
214
215    public void persistOperation(DistributionSnapshot snapshot, OperationInfo op, CoreSession session, String label,
216            DocumentModel parent) {
217        OperationInfoDocAdapter.create(op, session, parent.getPathAsString());
218    }
219
220    public void persistBundleGroup(DistributionSnapshot snapshot, BundleGroup bundleGroup, CoreSession session,
221            String label, DocumentModel parent) {
222        log.info("Persist bundle group " + bundleGroup.getId());
223
224        DocumentModel bundleGroupDoc = createBundleGroupDoc(bundleGroup, session, label, parent);
225
226        // save GitHub doc
227        if (bundleGroup instanceof BundleGroupImpl) {
228            Map<String, ResourceDocumentationItem> liveDoc = ((BundleGroupImpl) bundleGroup).getLiveDoc();
229            if (liveDoc != null && liveDoc.size() > 0) {
230                persistLiveDoc(liveDoc, bundleGroupDoc.getAdapter(BundleGroup.class), session);
231            }
232        }
233
234        for (String bundleId : bundleGroup.getBundleIds()) {
235            BundleInfo bi = snapshot.getBundle(bundleId);
236            persistBundle(snapshot, bi, session, label, bundleGroupDoc);
237        }
238
239        for (BundleGroup subGroup : bundleGroup.getSubGroups()) {
240            persistBundleGroup(snapshot, subGroup, session, label, bundleGroupDoc);
241        }
242    }
243
244    protected void persistLiveDoc(Map<String, ResourceDocumentationItem> liveDoc, NuxeoArtifact item,
245            CoreSession session) {
246        DocumentationService ds = Framework.getLocalService(DocumentationService.class);
247        List<DocumentationItem> existingDocs = ds.findDocumentItems(session, item);
248        for (String cat : liveDoc.keySet()) {
249            ResourceDocumentationItem docItem = liveDoc.get(cat);
250            DocumentationItem previousDocItem = null;
251            for (DocumentationItem exiting : existingDocs) {
252                if (exiting.getTitle().equals(docItem.getTitle()) && exiting.getTarget().equals(docItem.getTarget())) {
253                    previousDocItem = exiting;
254                    break;
255                }
256            }
257            if (previousDocItem == null) {
258                ds.createDocumentationItem(session, item, docItem.getTitle(), docItem.getContent(), cat,
259                        Arrays.asList(item.getVersion()), docItem.isApproved(), docItem.getRenderingType());
260            } else {
261                if (previousDocItem instanceof DocumentationItemDocAdapter) {
262                    DocumentationItemDocAdapter existingDoc = (DocumentationItemDocAdapter) previousDocItem;
263                    Blob blob = Blobs.createBlob(docItem.getContent());
264                    Blob oldBlob = (Blob) existingDoc.getDocumentModel().getPropertyValue("file:content");
265                    blob.setFilename(oldBlob.getFilename());
266                    existingDoc.getDocumentModel().setPropertyValue("file:content", (Serializable) blob);
267                    ds.updateDocumentationItem(session, existingDoc);
268                }
269
270            }
271        }
272    }
273
274    public void persistBundle(DistributionSnapshot snapshot, BundleInfo bundleInfo, CoreSession session, String label,
275            DocumentModel parent) {
276        log.info("Persist bundle " + bundleInfo.getId());
277
278        DocumentModel bundleDoc = createBundleDoc(snapshot, session, label, bundleInfo, parent);
279
280        // save GitHub doc
281        if (bundleInfo instanceof BundleInfoImpl) {
282            Map<String, ResourceDocumentationItem> liveDoc = ((BundleInfoImpl) bundleInfo).getLiveDoc();
283            if (liveDoc != null && liveDoc.size() > 0) {
284                persistLiveDoc(liveDoc, bundleDoc.getAdapter(BundleInfo.class), session);
285            }
286        }
287
288        for (ComponentInfo ci : bundleInfo.getComponents()) {
289            persistComponent(snapshot, ci, session, label, bundleDoc);
290        }
291    }
292
293    public void persistComponent(DistributionSnapshot snapshot, ComponentInfo ci, CoreSession session, String label,
294            DocumentModel parent) {
295
296        DocumentModel componentDoc = createComponentDoc(snapshot, session, label, ci, parent);
297
298        for (ExtensionPointInfo epi : ci.getExtensionPoints()) {
299            createExtensionPointDoc(snapshot, session, label, epi, componentDoc);
300        }
301        for (ExtensionInfo ei : ci.getExtensions()) {
302            createContributionDoc(snapshot, session, label, ei, componentDoc);
303        }
304
305        for (ServiceInfo si : ci.getServices()) {
306            createServiceDoc(snapshot, session, label, si, componentDoc);
307        }
308    }
309
310    protected DocumentModel createContributionDoc(DistributionSnapshot snapshot, CoreSession session, String label,
311            ExtensionInfo ei, DocumentModel parent) {
312        return ExtensionInfoDocAdapter.create(ei, session, parent.getPathAsString()).getDoc();
313    }
314
315    protected DocumentModel createServiceDoc(DistributionSnapshot snapshot, CoreSession session, String label,
316            ServiceInfo si, DocumentModel parent) {
317        return ServiceInfoDocAdapter.create(si, session, parent.getPathAsString()).getDoc();
318    }
319
320    protected DocumentModel createExtensionPointDoc(DistributionSnapshot snapshot, CoreSession session, String label,
321            ExtensionPointInfo epi, DocumentModel parent) {
322        return ExtensionPointInfoDocAdapter.create(epi, session, parent.getPathAsString()).getDoc();
323    }
324
325    protected DocumentModel createComponentDoc(DistributionSnapshot snapshot, CoreSession session, String label,
326            ComponentInfo ci, DocumentModel parent) {
327        try {
328            return ComponentInfoDocAdapter.create(ci, session, parent.getPathAsString()).getDoc();
329        } catch (IOException e) {
330            throw new NuxeoException("Unable to create Component Doc", e);
331        }
332    }
333
334    protected DocumentModel createBundleDoc(DistributionSnapshot snapshot, CoreSession session, String label,
335            BundleInfo bi, DocumentModel parent) {
336        return BundleInfoDocAdapter.create(bi, session, parent.getPathAsString()).getDoc();
337    }
338
339    protected RepositoryDistributionSnapshot createDistributionDoc(DistributionSnapshot snapshot, CoreSession session,
340            String label) {
341        return RepositoryDistributionSnapshot.create(snapshot, session, getDistributionRoot(session).getPathAsString(),
342                label);
343    }
344
345    protected DocumentModel createBundleGroupDoc(BundleGroup bundleGroup, CoreSession session, String label,
346            DocumentModel parent) {
347        return BundleGroupDocAdapter.create(bundleGroup, session, parent.getPathAsString()).getDoc();
348    }
349
350}