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