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.core.api.Blob;
056import org.nuxeo.ecm.core.api.Blobs;
057import org.nuxeo.ecm.core.api.CoreSession;
058import org.nuxeo.ecm.core.api.DocumentModel;
059import org.nuxeo.ecm.core.api.DocumentRef;
060import org.nuxeo.ecm.core.api.NuxeoException;
061import org.nuxeo.ecm.core.api.PathRef;
062import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
063import org.nuxeo.ecm.core.api.security.ACE;
064import org.nuxeo.ecm.core.api.security.ACL;
065import org.nuxeo.ecm.core.api.security.ACP;
066import org.nuxeo.ecm.core.api.security.impl.ACLImpl;
067import org.nuxeo.runtime.api.Framework;
068
069public class SnapshotPersister {
070
071    public static final String Root_PATH = "/";
072
073    public static final String Root_NAME = "nuxeo-distributions";
074
075    public static final String Seam_Root_NAME = "Seam";
076
077    public static final String Operation_Root_NAME = "Automation";
078
079    public static final String Bundle_Root_NAME = "Bundles";
080
081    public static final String Read_Grp = "Everyone";
082
083    public static final String Write_Grp = "members";
084
085    protected static final Log log = LogFactory.getLog(SnapshotPersister.class);
086
087    class UnrestrictedRootCreator extends UnrestrictedSessionRunner {
088
089        protected DocumentRef rootRef;
090
091        protected final String parentPath;
092
093        protected final String name;
094
095        protected final boolean setAcl;
096
097        public UnrestrictedRootCreator(CoreSession session, String parentPath, String name, boolean setAcl) {
098            super(session);
099            this.name = name;
100            this.parentPath = parentPath;
101            this.setAcl = setAcl;
102        }
103
104        public DocumentRef getRootRef() {
105            return rootRef;
106        }
107
108        @Override
109        public void run() {
110
111            DocumentModel root = session.createDocumentModel(parentPath, name, "Workspace");
112            root.setProperty("dublincore", "title", name);
113            root = session.createDocument(root);
114
115            if (setAcl) {
116                ACL acl = new ACLImpl();
117                acl.add(new ACE(Write_Grp, "Write", true));
118                acl.add(new ACE(Read_Grp, "Read", true));
119                ACP acp = root.getACP();
120                acp.addACL(acl);
121                session.setACP(root.getRef(), acp, true);
122            }
123
124            rootRef = root.getRef();
125            // flush caches
126            session.save();
127        }
128
129    }
130
131    public DocumentModel getSubRoot(CoreSession session, DocumentModel root, String name) {
132
133        DocumentRef rootRef = new PathRef(root.getPathAsString() + name);
134        if (session.exists(rootRef)) {
135            return session.getDocument(rootRef);
136        }
137        UnrestrictedRootCreator creator = new UnrestrictedRootCreator(session, root.getPathAsString(), name, false);
138        creator.runUnrestricted();
139        // flush caches
140        session.save();
141        return session.getDocument(creator.getRootRef());
142    }
143
144    public DocumentModel getDistributionRoot(CoreSession session) {
145        DocumentRef rootRef = new PathRef(Root_PATH + Root_NAME);
146        if (session.exists(rootRef)) {
147            return session.getDocument(rootRef);
148        }
149        UnrestrictedRootCreator creator = new UnrestrictedRootCreator(session, Root_PATH, Root_NAME, true);
150        creator.runUnrestricted();
151        // flush caches
152        session.save();
153        return session.getDocument(creator.getRootRef());
154    }
155
156    public DistributionSnapshot persist(DistributionSnapshot snapshot, CoreSession session, String label,
157            SnapshotFilter filter, Map<String, Serializable> properties) {
158
159        RepositoryDistributionSnapshot distribContainer = createDistributionDoc(snapshot, session, label, properties);
160
161        if (filter == null) {
162            // If no filter, clean old entries
163            distribContainer.cleanPreviousArtifacts();
164        }
165
166        DocumentModel bundleContainer = getSubRoot(session, distribContainer.getDoc(), Bundle_Root_NAME);
167
168        if (filter != null) {
169            // create VGroup that contain,s only the target bundles
170            BundleGroupImpl vGroup = new BundleGroupImpl(filter.getBundleGroupName(), snapshot.getVersion());
171            for (String bundleId : snapshot.getBundleIds()) {
172                if (filter.includeBundleId(bundleId)) {
173                    vGroup.add(bundleId);
174                }
175            }
176            persistBundleGroup(snapshot, vGroup, session, label + "-bundles", bundleContainer);
177        } else {
178            List<BundleGroup> bundleGroups = snapshot.getBundleGroups();
179            for (BundleGroup bundleGroup : bundleGroups) {
180                persistBundleGroup(snapshot, bundleGroup, session, label, bundleContainer);
181            }
182        }
183
184        DocumentModel seamContainer = getSubRoot(session, distribContainer.getDoc(), Seam_Root_NAME);
185        persistSeamComponents(snapshot, snapshot.getSeamComponents(), session, label, seamContainer, filter);
186
187        DocumentModel opContainer = getSubRoot(session, distribContainer.getDoc(), Operation_Root_NAME);
188        persistOperations(snapshot, snapshot.getOperations(), session, label, opContainer, filter);
189
190        return distribContainer;
191    }
192
193    public void persistSeamComponents(DistributionSnapshot snapshot, List<SeamComponentInfo> seamComponents,
194            CoreSession session, String label, DocumentModel parent, SnapshotFilter filter) {
195        for (SeamComponentInfo seamComponent : seamComponents) {
196            if (filter == null || filter.includeSeamComponent(seamComponent)) {
197                persistSeamComponent(snapshot, seamComponent, session, label, parent);
198            }
199        }
200    }
201
202    public void persistSeamComponent(DistributionSnapshot snapshot, SeamComponentInfo seamComponent,
203            CoreSession session, String label, DocumentModel parent) {
204        SeamComponentInfoDocAdapter.create(seamComponent, session, parent.getPathAsString());
205    }
206
207    public void persistOperations(DistributionSnapshot snapshot, List<OperationInfo> operations, CoreSession session,
208            String label, DocumentModel parent, SnapshotFilter filter) {
209        for (OperationInfo op : operations) {
210            if (filter == null || op instanceof OperationInfoImpl && filter.includeOperation((OperationInfoImpl) op)) {
211                persistOperation(snapshot, op, session, label, parent);
212            }
213        }
214    }
215
216    public void persistOperation(DistributionSnapshot snapshot, OperationInfo op, CoreSession session, String label,
217            DocumentModel parent) {
218        OperationInfoDocAdapter.create(op, session, parent.getPathAsString());
219    }
220
221    public void persistBundleGroup(DistributionSnapshot snapshot, BundleGroup bundleGroup, CoreSession session,
222            String label, DocumentModel parent) {
223        if (log.isTraceEnabled()) {
224            log.trace("Persist bundle group " + bundleGroup.getId());
225        }
226
227        DocumentModel bundleGroupDoc = createBundleGroupDoc(bundleGroup, session, label, parent);
228
229        // save GitHub doc
230        if (bundleGroup instanceof BundleGroupImpl) {
231            Map<String, ResourceDocumentationItem> liveDoc = ((BundleGroupImpl) bundleGroup).getLiveDoc();
232            if (liveDoc != null && liveDoc.size() > 0) {
233                persistLiveDoc(liveDoc, bundleGroupDoc.getAdapter(BundleGroup.class), session);
234            }
235        }
236
237        for (String bundleId : bundleGroup.getBundleIds()) {
238            BundleInfo bi = snapshot.getBundle(bundleId);
239            persistBundle(snapshot, bi, session, label, bundleGroupDoc);
240        }
241
242        for (BundleGroup subGroup : bundleGroup.getSubGroups()) {
243            persistBundleGroup(snapshot, subGroup, session, label, bundleGroupDoc);
244        }
245    }
246
247    protected void persistLiveDoc(Map<String, ResourceDocumentationItem> liveDoc, NuxeoArtifact item,
248            CoreSession session) {
249        DocumentationService ds = Framework.getLocalService(DocumentationService.class);
250        List<DocumentationItem> existingDocs = ds.findDocumentItems(session, item);
251        for (String cat : liveDoc.keySet()) {
252            ResourceDocumentationItem docItem = liveDoc.get(cat);
253            DocumentationItem previousDocItem = null;
254            for (DocumentationItem exiting : existingDocs) {
255                if (exiting.getTitle().equals(docItem.getTitle()) && exiting.getTarget().equals(docItem.getTarget())) {
256                    previousDocItem = exiting;
257                    break;
258                }
259            }
260            if (previousDocItem == null) {
261                ds.createDocumentationItem(session, item, docItem.getTitle(), docItem.getContent(), cat,
262                        Arrays.asList(item.getVersion()), docItem.isApproved(), docItem.getRenderingType());
263            } else {
264                if (previousDocItem instanceof DocumentationItemDocAdapter) {
265                    DocumentationItemDocAdapter existingDoc = (DocumentationItemDocAdapter) previousDocItem;
266                    Blob blob = Blobs.createBlob(docItem.getContent());
267                    Blob oldBlob = (Blob) existingDoc.getDocumentModel().getPropertyValue("file:content");
268                    blob.setFilename(oldBlob.getFilename());
269                    existingDoc.getDocumentModel().setPropertyValue("file:content", (Serializable) blob);
270                    ds.updateDocumentationItem(session, existingDoc);
271                }
272
273            }
274        }
275    }
276
277    public void persistBundle(DistributionSnapshot snapshot, BundleInfo bundleInfo, CoreSession session, String label,
278            DocumentModel parent) {
279        if (log.isTraceEnabled()) {
280            log.trace("Persist bundle " + bundleInfo.getId());
281        }
282        DocumentModel bundleDoc = createBundleDoc(snapshot, session, label, bundleInfo, parent);
283
284        // save GitHub doc
285        if (bundleInfo instanceof BundleInfoImpl) {
286            Map<String, ResourceDocumentationItem> liveDoc = ((BundleInfoImpl) bundleInfo).getLiveDoc();
287            if (liveDoc != null && liveDoc.size() > 0) {
288                persistLiveDoc(liveDoc, bundleDoc.getAdapter(BundleInfo.class), session);
289            }
290        }
291
292        for (ComponentInfo ci : bundleInfo.getComponents()) {
293            persistComponent(snapshot, ci, session, label, bundleDoc);
294        }
295    }
296
297    public void persistComponent(DistributionSnapshot snapshot, ComponentInfo ci, CoreSession session, String label,
298            DocumentModel parent) {
299
300        DocumentModel componentDoc = createComponentDoc(snapshot, session, label, ci, parent);
301
302        for (ExtensionPointInfo epi : ci.getExtensionPoints()) {
303            createExtensionPointDoc(snapshot, session, label, epi, componentDoc);
304        }
305        for (ExtensionInfo ei : ci.getExtensions()) {
306            createContributionDoc(snapshot, session, label, ei, componentDoc);
307        }
308
309        for (ServiceInfo si : ci.getServices()) {
310            createServiceDoc(snapshot, session, label, si, componentDoc);
311        }
312    }
313
314    protected DocumentModel createContributionDoc(DistributionSnapshot snapshot, CoreSession session, String label,
315            ExtensionInfo ei, DocumentModel parent) {
316        return ExtensionInfoDocAdapter.create(ei, session, parent.getPathAsString()).getDoc();
317    }
318
319    protected DocumentModel createServiceDoc(DistributionSnapshot snapshot, CoreSession session, String label,
320            ServiceInfo si, DocumentModel parent) {
321        return ServiceInfoDocAdapter.create(si, session, parent.getPathAsString()).getDoc();
322    }
323
324    protected DocumentModel createExtensionPointDoc(DistributionSnapshot snapshot, CoreSession session, String label,
325            ExtensionPointInfo epi, DocumentModel parent) {
326        return ExtensionPointInfoDocAdapter.create(epi, session, parent.getPathAsString()).getDoc();
327    }
328
329    protected DocumentModel createComponentDoc(DistributionSnapshot snapshot, CoreSession session, String label,
330            ComponentInfo ci, DocumentModel parent) {
331        try {
332            return ComponentInfoDocAdapter.create(ci, session, parent.getPathAsString()).getDoc();
333        } catch (IOException e) {
334            throw new NuxeoException("Unable to create Component Doc", e);
335        }
336    }
337
338    protected DocumentModel createBundleDoc(DistributionSnapshot snapshot, CoreSession session, String label,
339            BundleInfo bi, DocumentModel parent) {
340        return BundleInfoDocAdapter.create(bi, session, parent.getPathAsString()).getDoc();
341    }
342
343    protected RepositoryDistributionSnapshot createDistributionDoc(DistributionSnapshot snapshot, CoreSession session,
344            String label, Map<String, Serializable> properties) {
345        return RepositoryDistributionSnapshot.create(snapshot, session, getDistributionRoot(session).getPathAsString(),
346                label, properties);
347    }
348
349    protected DocumentModel createBundleGroupDoc(BundleGroup bundleGroup, CoreSession session, String label,
350            DocumentModel parent) {
351        return BundleGroupDocAdapter.create(bundleGroup, session, parent.getPathAsString()).getDoc();
352    }
353
354}