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.Serializable;
022import java.util.ArrayList;
023import java.util.Calendar;
024import java.util.Date;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.function.Function;
030import java.util.stream.Collectors;
031
032import org.nuxeo.apidoc.adapters.BaseNuxeoArtifactDocAdapter;
033import org.nuxeo.apidoc.api.BundleGroup;
034import org.nuxeo.apidoc.api.BundleInfo;
035import org.nuxeo.apidoc.api.ComponentInfo;
036import org.nuxeo.apidoc.api.ExtensionInfo;
037import org.nuxeo.apidoc.api.ExtensionPointInfo;
038import org.nuxeo.apidoc.api.NuxeoArtifact;
039import org.nuxeo.apidoc.api.OperationInfo;
040import org.nuxeo.apidoc.api.QueryHelper;
041import org.nuxeo.apidoc.api.SeamComponentInfo;
042import org.nuxeo.apidoc.api.ServiceInfo;
043import org.nuxeo.apidoc.documentation.JavaDocHelper;
044import org.nuxeo.apidoc.introspection.ServerInfo;
045import org.nuxeo.apidoc.snapshot.DistributionSnapshot;
046import org.nuxeo.common.utils.Path;
047import org.nuxeo.ecm.core.api.CoreSession;
048import org.nuxeo.ecm.core.api.DocumentModel;
049import org.nuxeo.ecm.core.api.DocumentModelList;
050import org.nuxeo.ecm.core.api.DocumentRef;
051import org.nuxeo.ecm.core.api.PathRef;
052import org.nuxeo.ecm.core.api.PropertyException;
053import org.nuxeo.ecm.core.query.sql.NXQL;
054
055public class RepositoryDistributionSnapshot extends BaseNuxeoArtifactDocAdapter implements DistributionSnapshot {
056
057    protected JavaDocHelper jdocHelper = null;
058
059    public static RepositoryDistributionSnapshot create(DistributionSnapshot distrib, CoreSession session,
060            String containerPath, String label, Map<String, Serializable> properties) {
061        DocumentModel doc = session.createDocumentModel(TYPE_NAME);
062        String name = computeDocumentName(distrib.getKey());
063        if (label != null) {
064            name = computeDocumentName(label);
065        }
066        String targetPath = new Path(containerPath).append(name).toString();
067
068        boolean exist = false;
069        if (session.exists(new PathRef(targetPath))) {
070            exist = true;
071            doc = session.getDocument(new PathRef(targetPath));
072        }
073
074        // Set first properties passed by parameter to not override default
075        // behavior
076        if (properties != null) {
077            properties.forEach(doc::setPropertyValue);
078        }
079
080        doc.setPathInfo(containerPath, name);
081        if (label == null) {
082            doc.setPropertyValue("dc:title", distrib.getKey());
083            doc.setPropertyValue(PROP_KEY, distrib.getKey());
084            doc.setPropertyValue(PROP_NAME, distrib.getName());
085        } else {
086            doc.setPropertyValue("dc:title", label);
087            doc.setPropertyValue(PROP_KEY, label + "-" + distrib.getVersion());
088            doc.setPropertyValue(PROP_NAME, label);
089        }
090        doc.setPropertyValue(PROP_LATEST_FT, distrib.isLatestFT());
091        doc.setPropertyValue(PROP_LATEST_LTS, distrib.isLatestLTS());
092        doc.setPropertyValue(PROP_VERSION, distrib.getVersion());
093
094        DocumentModel ret;
095        if (exist) {
096            ret = session.saveDocument(doc);
097        } else {
098            ret = session.createDocument(doc);
099        }
100        return new RepositoryDistributionSnapshot(ret);
101    }
102
103    public static List<DistributionSnapshot> readPersistentSnapshots(CoreSession session) {
104        List<DistributionSnapshot> result = new ArrayList<>();
105        String query = "SELECT * FROM " + TYPE_NAME + " where ecm:isTrashed = 0 AND ecm:isVersion = 0";
106        DocumentModelList docs = session.query(query);
107        for (DocumentModel child : docs) {
108            DistributionSnapshot ob = child.getAdapter(DistributionSnapshot.class);
109            if (ob != null) {
110                result.add(ob);
111            }
112        }
113        return result;
114    }
115
116    public RepositoryDistributionSnapshot(DocumentModel doc) {
117        super(doc);
118    }
119
120    protected <T> List<T> getChildren(Class<T> adapter, String docType) {
121        List<T> result = new ArrayList<>();
122        String query = QueryHelper.select(docType, doc);
123        DocumentModelList docs = getCoreSession().query(query);
124        for (DocumentModel child : docs) {
125            T ob = child.getAdapter(adapter);
126            if (ob != null) {
127                result.add(ob);
128            }
129        }
130        return result;
131    }
132
133    protected <T> T getChild(Class<T> adapter, String docType, String idField, String id) {
134        String query = QueryHelper.select(docType, doc) + " AND " + idField + " = " + NXQL.escapeString(id);
135        DocumentModelList docs = getCoreSession().query(query);
136        if (docs.isEmpty()) {
137            log.error("Unable to find " + docType + " for id " + id);
138        } else if (docs.size() == 1) {
139            return docs.get(0).getAdapter(adapter);
140        } else {
141            log.error("multiple match for " + docType + " for id " + id);
142            return docs.get(0).getAdapter(adapter);
143        }
144        return null;
145    }
146
147    @Override
148    public BundleInfo getBundle(String id) {
149        return getChild(BundleInfo.class, BundleInfo.TYPE_NAME, BundleInfo.PROP_BUNDLE_ID, id);
150    }
151
152    @Override
153    public BundleGroup getBundleGroup(String groupId) {
154        return getChild(BundleGroup.class, BundleGroup.TYPE_NAME, BundleGroup.PROP_KEY, groupId);
155    }
156
157    protected DocumentModel getBundleContainer() {
158        DocumentRef ref = new PathRef(doc.getPathAsString(), SnapshotPersister.Bundle_Root_NAME);
159        if (getCoreSession().exists(ref)) {
160            return getCoreSession().getDocument(ref);
161        } else {
162            // for compatibility with the previous persistence model
163            return doc;
164        }
165    }
166
167    @Override
168    public List<BundleGroup> getBundleGroups() {
169        List<BundleGroup> grps = new ArrayList<>();
170        String query = QueryHelper.select(BundleGroup.TYPE_NAME, doc, NXQL.ECM_PARENTID, getBundleContainer().getId());
171        DocumentModelList docs = getCoreSession().query(query);
172        for (DocumentModel child : docs) {
173            BundleGroup bg = child.getAdapter(BundleGroup.class);
174            if (bg != null) {
175                grps.add(bg);
176            }
177        }
178        return grps;
179    }
180
181    @Override
182    public List<String> getBundleIds() {
183        return getChildren(BundleInfo.class, BundleInfo.TYPE_NAME).stream().map(NuxeoArtifact::getId).collect(
184                Collectors.toList());
185    }
186
187    @Override
188    public ComponentInfo getComponent(String id) {
189        return getChild(ComponentInfo.class, ComponentInfo.TYPE_NAME, ComponentInfo.PROP_COMPONENT_ID, id);
190    }
191
192    @Override
193    public List<String> getComponentIds() {
194        return getChildren(ComponentInfo.class, ComponentInfo.TYPE_NAME).stream().map(NuxeoArtifact::getId).collect(
195                Collectors.toList());
196    }
197
198    @Override
199    public ExtensionInfo getContribution(String id) {
200        return getChild(ExtensionInfo.class, ExtensionInfo.TYPE_NAME, ExtensionInfo.PROP_CONTRIB_ID, id);
201    }
202
203    @Override
204    public List<String> getContributionIds() {
205        return getChildren(ExtensionInfo.class, ExtensionInfo.TYPE_NAME).stream().map(NuxeoArtifact::getId).collect(
206                Collectors.toList());
207    }
208
209    @Override
210    public List<ExtensionInfo> getContributions() {
211        return getChildren(ExtensionInfo.class, ExtensionInfo.TYPE_NAME);
212    }
213
214    @Override
215    public ExtensionPointInfo getExtensionPoint(String id) {
216        return getChild(ExtensionPointInfo.class, ExtensionPointInfo.TYPE_NAME, ExtensionPointInfo.PROP_EP_ID, id);
217    }
218
219    @Override
220    public List<String> getExtensionPointIds() {
221        return getChildren(ExtensionPointInfo.class, ExtensionPointInfo.TYPE_NAME).stream()
222                                                                                  .map(NuxeoArtifact::getId)
223                                                                                  .collect(Collectors.toList());
224    }
225
226    @Override
227    public List<String> getBundleGroupChildren(String groupId) {
228        BundleGroup bg = getChild(BundleGroup.class, BundleGroup.TYPE_NAME, BundleGroup.PROP_KEY, groupId);
229        return bg.getBundleIds();
230    }
231
232    public List<String> getBundleGroupIds() {
233        return getChildren(BundleGroup.class, BundleGroup.TYPE_NAME).stream().map(NuxeoArtifact::getId).collect(
234                Collectors.toList());
235    }
236
237    @Override
238    public List<String> getServiceIds() {
239        Set<String> ids = new HashSet<>();
240        String query = QueryHelper.select(ComponentInfo.TYPE_NAME, doc);
241        DocumentModelList components = getCoreSession().query(query);
242        for (DocumentModel componentDoc : components) {
243            ComponentInfo ci = componentDoc.getAdapter(ComponentInfo.class);
244            if (ci != null) {
245                ids.addAll(ci.getServiceNames());
246            }
247        }
248        return new ArrayList<>(ids);
249    }
250
251    @Override
252    public String getName() {
253        try {
254            return (String) doc.getPropertyValue(PROP_NAME);
255        } catch (PropertyException e) {
256            log.error("Error while reading nxdistribution:name", e);
257            return "!unknown!";
258        }
259    }
260
261    @Override
262    public String getVersion() {
263        try {
264            return (String) doc.getPropertyValue(PROP_VERSION);
265        } catch (PropertyException e) {
266            log.error("Error while reading nxdistribution:version", e);
267            return "!unknown!";
268        }
269    }
270
271    @Override
272    public String getKey() {
273        try {
274            return (String) doc.getPropertyValue(PROP_KEY);
275        } catch (PropertyException e) {
276            log.error("Error while reading nxdistribution:key", e);
277            return "!unknown!";
278        }
279    }
280
281    @Override
282    public List<Class<?>> getSpi() {
283        return null;
284    }
285
286    @Override
287    public String getId() {
288        return getKey();
289    }
290
291    @Override
292    public String getArtifactType() {
293        return TYPE_NAME;
294    }
295
296    @Override
297    public ServiceInfo getService(String id) {
298        // Select only not overriden ticket and old imported NXService without overriden value
299        String query = QueryHelper.select(ServiceInfo.TYPE_NAME, getDoc()) + " AND " + ServiceInfo.PROP_CLASS_NAME
300                + " = " + NXQL.escapeString(id) + " AND (" + ServiceInfo.PROP_OVERRIDEN + " = 0 OR "
301                + ServiceInfo.PROP_OVERRIDEN + " is NULL)";
302        DocumentModelList docs = getCoreSession().query(query);
303        if (docs.size() > 1) {
304            throw new AssertionError("Multiple services found for " + id);
305        }
306        return docs.get(0).getAdapter(ServiceInfo.class);
307    }
308
309    @Override
310    public List<String> getJavaComponentIds() {
311        return getChildren(ComponentInfo.class, ComponentInfo.TYPE_NAME).stream()
312                                                                        .filter(ci -> !ci.isXmlPureComponent())
313                                                                        .map(NuxeoArtifact::getId)
314                                                                        .collect(Collectors.toList());
315    }
316
317    @Override
318    public List<String> getXmlComponentIds() {
319        return getChildren(ComponentInfo.class, ComponentInfo.TYPE_NAME).stream()
320                                                                        .filter(ComponentInfo::isXmlPureComponent)
321                                                                        .map(NuxeoArtifact::getId)
322                                                                        .collect(Collectors.toList());
323    }
324
325    @Override
326    public Date getCreationDate() {
327        try {
328            Calendar cal = (Calendar) getDoc().getPropertyValue("dc:created");
329            return cal == null ? null : cal.getTime();
330        } catch (PropertyException e) {
331            return null;
332        }
333    }
334
335    @Override
336    public Date getReleaseDate() {
337        try {
338            Calendar cal = (Calendar) getDoc().getPropertyValue("nxdistribution:released");
339            return cal == null ? getCreationDate() : cal.getTime();
340        } catch (PropertyException e) {
341            return null;
342        }
343    }
344
345    @Override
346    public boolean isLive() {
347        return false;
348    }
349
350    @Override
351    public SeamComponentInfo getSeamComponent(String id) {
352        String name = id.replace("seam:", "");
353        String query = QueryHelper.select(SeamComponentInfo.TYPE_NAME, getDoc()) + " AND "
354                + SeamComponentInfo.PROP_COMPONENT_NAME + " = " + NXQL.escapeString(name);
355        DocumentModelList docs = getCoreSession().query(query);
356        return docs.isEmpty() ? null : docs.get(0).getAdapter(SeamComponentInfo.class);
357    }
358
359    @Override
360    public List<String> getSeamComponentIds() {
361        List<String> result = new ArrayList<>();
362        String query = QueryHelper.select(SeamComponentInfo.TYPE_NAME, getDoc());
363        DocumentModelList docs = getCoreSession().query(query);
364        for (DocumentModel doc : docs) {
365            result.add(doc.getAdapter(SeamComponentInfo.class).getId());
366        }
367        return result;
368    }
369
370    @Override
371    public List<SeamComponentInfo> getSeamComponents() {
372        List<SeamComponentInfo> result = new ArrayList<>();
373        String query = QueryHelper.select(SeamComponentInfo.TYPE_NAME, getDoc());
374        DocumentModelList docs = getCoreSession().query(query);
375        for (DocumentModel doc : docs) {
376            result.add(doc.getAdapter(SeamComponentInfo.class));
377        }
378        return result;
379    }
380
381    @Override
382    public boolean containsSeamComponents() {
383        return getSeamComponentIds().size() > 0;
384    }
385
386    @Override
387    public OperationInfo getOperation(String id) {
388        if (id.startsWith(OperationInfo.ARTIFACT_PREFIX)) {
389            id = id.substring(OperationInfo.ARTIFACT_PREFIX.length());
390        }
391        String query = QueryHelper.select(OperationInfo.TYPE_NAME, getDoc()) + " AND " + OperationInfo.PROP_NAME + " = "
392                + NXQL.escapeString(id) + " OR " + OperationInfo.PROP_ALIASES + " = " + NXQL.escapeString(id);
393        DocumentModelList docs = getCoreSession().query(query);
394        return docs.isEmpty() ? null : docs.get(0).getAdapter(OperationInfo.class);
395    }
396
397    @Override
398    public List<OperationInfo> getOperations() {
399        List<OperationInfo> result = new ArrayList<>();
400        String query = QueryHelper.select(OperationInfo.TYPE_NAME, getDoc());
401        DocumentModelList docs = getCoreSession().query(query);
402        for (DocumentModel doc : docs) {
403            result.add(doc.getAdapter(OperationInfo.class));
404        }
405        // TODO sort
406        return result;
407    }
408
409    public JavaDocHelper getJavaDocHelper() {
410        if (jdocHelper == null) {
411            jdocHelper = JavaDocHelper.getHelper(getName(), getVersion());
412        }
413        return jdocHelper;
414    }
415
416    @Override
417    public void cleanPreviousArtifacts() {
418        String query = QueryHelper.select("Document", getDoc());
419        List<DocumentRef> refs = new ArrayList<>();
420        DocumentModelList docs = getCoreSession().query(query);
421        for (DocumentModel doc : docs) {
422            refs.add(doc.getRef());
423        }
424        getCoreSession().removeDocuments(refs.toArray(new DocumentRef[refs.size()]));
425    }
426
427    @Override
428    public boolean isLatestFT() {
429        try {
430            return (Boolean) doc.getPropertyValue(PROP_LATEST_FT);
431        } catch (PropertyException e) {
432            log.error("Error while reading nxdistribution:latestFT", e);
433            return false;
434        }
435    }
436
437    @Override
438    public boolean isLatestLTS() {
439        try {
440            return (Boolean) doc.getPropertyValue(PROP_LATEST_LTS);
441        } catch (PropertyException e) {
442            log.error("Error while reading nxdistribution:latestLTS", e);
443            return false;
444        }
445    }
446
447    @Override
448    public List<String> getAliases() {
449        @SuppressWarnings("unchecked")
450        List<String> aliases = (List<String>) doc.getPropertyValue(PROP_ALIASES);
451        if (isLatestLTS()) {
452            aliases.add("latestLTS");
453        } else if (isLatestFT()) {
454            aliases.add("latestFT");
455        }
456        return aliases;
457    }
458
459    @Override
460    public boolean isHidden() {
461        return Boolean.TRUE.equals(doc.getPropertyValue(PROP_HIDE));
462    }
463
464    @Override
465    public ServerInfo getServerInfo() {
466        throw new UnsupportedOperationException();
467    }
468}