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.introspection;
020
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.Date;
025import java.util.HashMap;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029import java.util.Optional;
030
031import javax.servlet.http.HttpServletRequest;
032
033import org.nuxeo.apidoc.api.BaseNuxeoArtifact;
034import org.nuxeo.apidoc.api.BundleGroup;
035import org.nuxeo.apidoc.api.BundleGroupFlatTree;
036import org.nuxeo.apidoc.api.BundleGroupTreeHelper;
037import org.nuxeo.apidoc.api.BundleInfo;
038import org.nuxeo.apidoc.api.ComponentInfo;
039import org.nuxeo.apidoc.api.ExtensionInfo;
040import org.nuxeo.apidoc.api.ExtensionPointInfo;
041import org.nuxeo.apidoc.api.OperationInfo;
042import org.nuxeo.apidoc.api.SeamComponentInfo;
043import org.nuxeo.apidoc.api.ServiceInfo;
044import org.nuxeo.apidoc.documentation.JavaDocHelper;
045import org.nuxeo.apidoc.seam.SeamRuntimeIntrospector;
046import org.nuxeo.apidoc.snapshot.DistributionSnapshot;
047import org.nuxeo.ecm.automation.AutomationService;
048import org.nuxeo.ecm.automation.OperationDocumentation;
049import org.nuxeo.ecm.automation.OperationException;
050import org.nuxeo.ecm.automation.OperationType;
051import org.nuxeo.ecm.core.api.NuxeoException;
052import org.nuxeo.runtime.api.Framework;
053
054import com.fasterxml.jackson.annotation.JsonCreator;
055import com.fasterxml.jackson.annotation.JsonIgnore;
056import com.fasterxml.jackson.annotation.JsonProperty;
057
058public class RuntimeSnapshot extends BaseNuxeoArtifact implements DistributionSnapshot {
059
060    public static final String VIRTUAL_BUNDLE_GROUP = "grp:org.nuxeo.misc";
061
062    protected ServerInfo serverInfo;
063
064    protected Date created;
065
066    protected final List<String> bundleIds = new ArrayList<>();
067
068    protected final List<String> javaComponentsIds = new ArrayList<>();
069
070    protected final Map<String, String> components2Bundles = new HashMap<>();
071
072    protected final Map<String, String> services2Components = new HashMap<>();
073
074    protected final Map<String, ExtensionPointInfo> extensionPoints = new HashMap<>();
075
076    protected final Map<String, ExtensionInfo> contributions = new HashMap<>();
077
078    protected final Map<String, List<String>> mavenGroups = new HashMap<>();
079
080    protected final Map<String, List<String>> mavenSubGroups = new HashMap<>();
081
082    protected final List<BundleGroup> bundleGroups = new ArrayList<>();
083
084    protected boolean seamInitialized = false;
085
086    protected List<SeamComponentInfo> seamComponents = new ArrayList<>();
087
088    protected boolean opsInitialized = false;
089
090    protected final List<OperationInfo> operations = new ArrayList<>();
091
092    protected JavaDocHelper jdocHelper;
093
094    protected final List<Class<?>> spi = new ArrayList<>();
095
096    public static RuntimeSnapshot build() {
097        return new RuntimeSnapshot();
098    }
099
100    @JsonCreator
101    private RuntimeSnapshot(@JsonProperty("serverInfo") ServerInfo serverInfo,
102            @JsonProperty("creationDate") Date created,
103            @JsonProperty("seamComponents") List<SeamComponentInfo> seamComponents,
104            @JsonProperty("operations") List<OperationInfo> operations) {
105        this.serverInfo = serverInfo;
106        this.created = created;
107        index();
108        this.seamComponents.addAll(seamComponents);
109        this.operations.addAll(operations);
110    }
111
112    protected RuntimeSnapshot() {
113        serverInfo = ServerInfo.build();
114        created = new Date();
115
116        index();
117    }
118
119    @Override
120    public ServerInfo getServerInfo() {
121        return serverInfo;
122    }
123
124    @Override
125    @JsonIgnore
126    public String getVersion() {
127        return serverInfo.getVersion();
128    }
129
130    @Override
131    @JsonIgnore
132    public String getName() {
133        return serverInfo.getName();
134    }
135
136    protected void index() {
137        spi.addAll(serverInfo.getAllSpi());
138        for (BundleInfo bInfo : serverInfo.getBundles()) {
139            bundleIds.add(bInfo.getId());
140
141            String groupId = bInfo.getGroupId();
142            if (groupId != null) {
143                groupId = "grp:" + groupId;
144            }
145            String artifactId = bInfo.getArtifactId();
146
147            if (groupId == null || artifactId == null) {
148                groupId = VIRTUAL_BUNDLE_GROUP;
149                ((BundleInfoImpl) bInfo).setGroupId(groupId);
150            }
151            if (!mavenGroups.containsKey(groupId)) {
152                mavenGroups.put(groupId, new ArrayList<String>());
153            }
154            mavenGroups.get(groupId).add(bInfo.getId());
155
156            for (ComponentInfo cInfo : bInfo.getComponents()) {
157                components2Bundles.put(cInfo.getId(), bInfo.getId());
158                if (!cInfo.isXmlPureComponent()) {
159                    javaComponentsIds.add(cInfo.getId());
160                }
161
162                for (ServiceInfo sInfo : cInfo.getServices()) {
163                    if (sInfo.isOverriden()) {
164                        continue;
165                    }
166                    services2Components.put(sInfo.getId(), cInfo.getId());
167                }
168
169                for (ExtensionPointInfo epi : cInfo.getExtensionPoints()) {
170                    extensionPoints.put(epi.getId(), epi);
171                }
172
173                for (ExtensionInfo ei : cInfo.getExtensions()) {
174                    contributions.put(ei.getId(), ei);
175                }
176            }
177        }
178        // post process bundle groups
179        List<String> mvnGroupNames = new ArrayList<>();
180        mvnGroupNames.addAll(mavenGroups.keySet());
181
182        for (String mvnGroupName : mvnGroupNames) {
183            List<String> artifactIds = mavenGroups.get(mvnGroupName);
184            Collections.sort(artifactIds);
185
186            List<String> subGroups = new ArrayList<>();
187
188            for (String id : artifactIds) {
189                if (id.endsWith(".api")) {
190                    String grp = "grp:" + id.substring(0, id.length() - 4);
191
192                    if (grp.equals(mvnGroupName)) {
193                        continue;
194                    }
195
196                    subGroups.add(grp);
197                }
198            }
199
200            if (subGroups.size() < 2) {
201                // no need to split the maven group into subGroups
202            } else {
203                for (String grp : subGroups) {
204                    List<String> grpArtifactIds = new ArrayList<>();
205                    for (String aid : artifactIds) {
206                        if (aid.startsWith(grp) || ("grp:" + aid).startsWith(grp)) {
207                            grpArtifactIds.add(aid);
208                        }
209                    }
210                    if (grpArtifactIds.size() > 0) {
211                        for (String aid : grpArtifactIds) {
212                            artifactIds.remove(aid);
213                        }
214                        mavenSubGroups.put(grp, grpArtifactIds);
215                        artifactIds.add(grp);
216                    }
217                }
218            }
219        }
220
221        for (String grpId : mavenGroups.keySet()) {
222            BundleGroupImpl bGroup = buildBundleGroup(grpId, serverInfo.getVersion());
223            bundleGroups.add(bGroup);
224        }
225
226    }
227
228    protected BundleGroupImpl buildBundleGroup(String id, String version) {
229        BundleGroupImpl bGroup = new BundleGroupImpl(id, version);
230        for (String aid : getBundleGroupChildren(id)) {
231            if (aid.startsWith("grp:")) {
232                BundleGroupImpl newGroup = buildBundleGroup(aid, version);
233                bGroup.add(newGroup);
234                newGroup.addParent(bGroup.getId());
235            } else {
236                bGroup.add(aid);
237                ((BundleInfoImpl) getBundle(aid)).setBundleGroup(bGroup);
238                BundleInfo bi = getBundle(aid);
239                bGroup.addLiveDoc(bi.getParentLiveDoc());
240            }
241        }
242        return bGroup;
243    }
244
245    @Override
246    @JsonIgnore
247    public List<BundleGroup> getBundleGroups() {
248        return bundleGroups;
249    }
250
251    @Override
252    public BundleGroup getBundleGroup(String groupId) {
253        BundleGroupTreeHelper bgth = new BundleGroupTreeHelper(this);
254        List<BundleGroupFlatTree> tree = bgth.getBundleGroupTree();
255
256        for (BundleGroupFlatTree info : tree) {
257            if (info.getGroup().getId().equals(groupId)) {
258                return info.getGroup();
259            }
260        }
261        if (!groupId.startsWith("grp:")) {
262            return getBundleGroup("grp:" + groupId);
263        }
264        return null;
265    }
266
267    protected void browseBundleGroup(BundleGroup group, int level, List<BundleGroupFlatTree> tree) {
268        BundleGroupFlatTree info = new BundleGroupFlatTree(group, level);
269        tree.add(info);
270
271        for (BundleGroup subGroup : group.getSubGroups()) {
272            browseBundleGroup(subGroup, level + 1, tree);
273        }
274    }
275
276    @Override
277    @JsonIgnore
278    public List<String> getBundleIds() {
279        List<String> bundlesIds = new ArrayList<>();
280
281        for (BundleInfo info : serverInfo.getBundles()) {
282            bundlesIds.add(info.getId());
283
284        }
285        Collections.sort(bundlesIds);
286        return bundlesIds;
287    }
288
289    @Override
290    public BundleInfo getBundle(String id) {
291        return serverInfo.getBundle(id);
292    }
293
294    @Override
295    @JsonIgnore
296    public List<String> getComponentIds() {
297        List<String> componentsIds = new ArrayList<>();
298        componentsIds.addAll(components2Bundles.keySet());
299        Collections.sort(componentsIds);
300        return componentsIds;
301    }
302
303    @Override
304    public ComponentInfo getComponent(String id) {
305        String bundleId = components2Bundles.get(id);
306        if (bundleId == null) {
307            return null;
308        }
309        BundleInfo bi = getBundle(bundleId);
310
311        for (ComponentInfo ci : bi.getComponents()) {
312            if (ci.getId().equals(id)) {
313                return ci;
314            }
315        }
316        return null;
317    }
318
319    @Override
320    @JsonIgnore
321    public List<String> getServiceIds() {
322        List<String> serviceIds = new ArrayList<>();
323        serviceIds.addAll(services2Components.keySet());
324        Collections.sort(serviceIds);
325        return serviceIds;
326    }
327
328    @Override
329    @JsonIgnore
330    public List<String> getExtensionPointIds() {
331        List<String> epIds = new ArrayList<>();
332        epIds.addAll(extensionPoints.keySet());
333        Collections.sort(epIds);
334        return epIds;
335    }
336
337    @Override
338    @JsonIgnore
339    public ExtensionPointInfo getExtensionPoint(String id) {
340        return extensionPoints.get(id);
341    }
342
343    @Override
344    @JsonIgnore
345    public List<String> getContributionIds() {
346        List<String> contribIds = new ArrayList<>();
347        contribIds.addAll(contributions.keySet());
348        Collections.sort(contribIds);
349        return contribIds;
350    }
351
352    @Override
353    @JsonIgnore
354    public List<ExtensionInfo> getContributions() {
355        List<ExtensionInfo> contribs = new ArrayList<>();
356        contribs.addAll(contributions.values());
357        // TODO sort
358        return contribs;
359    }
360
361    @Override
362    public ExtensionInfo getContribution(String id) {
363        return contributions.get(id);
364    }
365
366    public List<String> getBundleGroupIds() {
367        List<String> grpIds = new ArrayList<>();
368        grpIds.addAll(mavenGroups.keySet());
369        Collections.sort(grpIds);
370        return grpIds;
371    }
372
373    @Override
374    public List<String> getBundleGroupChildren(String groupId) {
375        List<String> res = mavenSubGroups.get(groupId);
376        if (res == null) {
377            // String grpId = groupId.substring(4);
378            res = mavenGroups.get(groupId);
379        }
380
381        if (res != null) {
382            return res;
383        } else {
384            return new ArrayList<>();
385        }
386    }
387
388    @Override
389    @JsonIgnore
390    public String getKey() {
391        return getName() + "-" + getVersion();
392    }
393
394    @Override
395    @JsonIgnore
396    public List<Class<?>> getSpi() {
397        return spi;
398    }
399
400    @Override
401    @JsonIgnore
402    public String getId() {
403        return getKey();
404    }
405
406    @Override
407    @JsonIgnore
408    public String getArtifactType() {
409        return TYPE_NAME;
410    }
411
412    @Override
413    public ServiceInfo getService(String id) {
414        String cId = services2Components.get(id);
415        if (cId == null) {
416            return null;
417        }
418
419        for (ServiceInfo si : getComponent(cId).getServices()) {
420            if (id.equals(si.getId())) {
421                return si;
422            }
423        }
424        return null;
425    }
426
427    @Override
428    @JsonIgnore
429    public List<String> getJavaComponentIds() {
430        return javaComponentsIds;
431    }
432
433    @Override
434    @JsonIgnore
435    public List<String> getXmlComponentIds() {
436        List<String> result = new ArrayList<>();
437
438        for (String cId : getComponentIds()) {
439            if (!javaComponentsIds.contains(cId)) {
440                result.add(cId);
441            }
442        }
443        return result;
444    }
445
446    @Override
447    public Date getCreationDate() {
448        return created;
449    }
450
451    @Override
452    @JsonIgnore
453    public Date getReleaseDate() {
454        return null;
455    }
456
457    @Override
458    @JsonIgnore
459    public boolean isLive() {
460        return true;
461    }
462
463    @Override
464    @JsonIgnore
465    public String getHierarchyPath() {
466        return null;
467    }
468
469    public void initSeamComponents(HttpServletRequest request) {
470        if (seamInitialized) {
471            return;
472        }
473        seamComponents = SeamRuntimeIntrospector.listNuxeoComponents(request);
474        for (SeamComponentInfo seamComp : seamComponents) {
475            ((SeamComponentInfoImpl) seamComp).setVersion(getVersion());
476        }
477        seamInitialized = true;
478    }
479
480    public void initOperations() {
481        if (opsInitialized) {
482            return;
483        }
484        AutomationService service = Framework.getService(AutomationService.class);
485        if (service == null) {
486            return;
487        }
488        OperationType[] ops = service.getOperations();
489        for (OperationType op : ops) {
490            OperationDocumentation documentation;
491            try {
492                documentation = op.getDocumentation();
493            } catch (OperationException e) {
494                throw new NuxeoException(e);
495            }
496            operations.add(new OperationInfoImpl(documentation, getVersion(), op.getType().getCanonicalName(),
497                    op.getContributingComponent()));
498        }
499        opsInitialized = true;
500    }
501
502    @Override
503    public SeamComponentInfo getSeamComponent(String id) {
504        for (SeamComponentInfo sci : getSeamComponents()) {
505            if (sci.getId().equals(id)) {
506                return sci;
507            }
508        }
509        return null;
510    }
511
512    @Override
513    @JsonIgnore
514    public List<String> getSeamComponentIds() {
515        List<String> ids = new ArrayList<>();
516        for (SeamComponentInfo sci : getSeamComponents()) {
517            ids.add(sci.getId());
518        }
519        return ids;
520    }
521
522    @Override
523    public List<SeamComponentInfo> getSeamComponents() {
524        return seamComponents;
525    }
526
527    @Override
528    public boolean containsSeamComponents() {
529        return getSeamComponentIds().size() > 0;
530    }
531
532    @Override
533    public OperationInfo getOperation(String id) {
534        if (id.startsWith(OperationInfo.ARTIFACT_PREFIX)) {
535            id = id.substring(OperationInfo.ARTIFACT_PREFIX.length());
536        }
537        for (OperationInfo op : getOperations()) {
538            if (op.getName().equals(id)) {
539                return op;
540            }
541
542            String finalId = id;
543            Optional<String> first = Arrays.stream(op.getAliases()).filter(s -> s.equals(finalId)).findFirst();
544            if (first.isPresent()) {
545                return op;
546            }
547        }
548        return null;
549    }
550
551    @Override
552    public List<OperationInfo> getOperations() {
553        initOperations();
554        return operations;
555    }
556
557    public JavaDocHelper getJavaDocHelper() {
558        if (jdocHelper == null) {
559            jdocHelper = JavaDocHelper.getHelper(getName(), getVersion());
560        }
561        return jdocHelper;
562    }
563
564    @Override
565    public void cleanPreviousArtifacts() {
566        // Can't delete anything in a runtime Snapshot
567        throw new UnsupportedOperationException();
568    }
569
570    @Override
571    @JsonIgnore
572    public boolean isLatestFT() {
573        return false;
574    }
575
576    @Override
577    @JsonIgnore
578    public boolean isLatestLTS() {
579        return false;
580    }
581
582    final List<String> aliases = new LinkedList<>(Collections.singletonList("current"));
583
584    @Override
585    @JsonIgnore
586    public List<String> getAliases() {
587        return aliases;
588    }
589
590    @Override
591    @JsonIgnore
592    public boolean isHidden() {
593        return false;
594    }
595}