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