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.browse;
020
021import java.io.IOException;
022import java.io.Reader;
023import java.io.StringReader;
024import java.io.StringWriter;
025import java.net.URI;
026import java.net.URISyntaxException;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.stream.Collectors;
033
034import javax.ws.rs.FormParam;
035import javax.ws.rs.GET;
036import javax.ws.rs.POST;
037import javax.ws.rs.Path;
038import javax.ws.rs.PathParam;
039import javax.ws.rs.Produces;
040import javax.ws.rs.QueryParam;
041import javax.ws.rs.core.Response;
042
043import org.json.JSONArray;
044import org.json.JSONException;
045import org.json.JSONObject;
046import org.nuxeo.apidoc.api.BundleGroup;
047import org.nuxeo.apidoc.api.BundleGroupFlatTree;
048import org.nuxeo.apidoc.api.BundleGroupTreeHelper;
049import org.nuxeo.apidoc.api.BundleInfo;
050import org.nuxeo.apidoc.api.ComponentInfo;
051import org.nuxeo.apidoc.api.DocumentationItem;
052import org.nuxeo.apidoc.api.ExtensionInfo;
053import org.nuxeo.apidoc.api.ExtensionPointInfo;
054import org.nuxeo.apidoc.api.NuxeoArtifact;
055import org.nuxeo.apidoc.api.OperationInfo;
056import org.nuxeo.apidoc.api.SeamComponentInfo;
057import org.nuxeo.apidoc.api.ServiceInfo;
058import org.nuxeo.apidoc.documentation.DocumentationService;
059import org.nuxeo.apidoc.search.ArtifactSearcher;
060import org.nuxeo.apidoc.snapshot.DistributionSnapshot;
061import org.nuxeo.apidoc.snapshot.SnapshotManager;
062import org.nuxeo.apidoc.tree.TreeHelper;
063import org.nuxeo.ecm.automation.OperationException;
064import org.nuxeo.ecm.core.api.NuxeoException;
065import org.nuxeo.ecm.platform.rendering.wiki.WikiSerializer;
066import org.nuxeo.ecm.webengine.model.Resource;
067import org.nuxeo.ecm.webengine.model.WebObject;
068import org.nuxeo.ecm.webengine.model.exceptions.WebResourceNotFoundException;
069import org.nuxeo.ecm.webengine.model.impl.DefaultObject;
070import org.nuxeo.runtime.api.Framework;
071import org.wikimodel.wem.WikiParserException;
072
073@WebObject(type = "apibrowser")
074public class ApiBrowser extends DefaultObject {
075
076    protected String distributionId;
077
078    protected boolean embeddedMode = false;
079
080    protected SnapshotManager getSnapshotManager() {
081        return Framework.getLocalService(SnapshotManager.class);
082    }
083
084    protected ArtifactSearcher getSearcher() {
085        return Framework.getLocalService(ArtifactSearcher.class);
086    }
087
088    @Override
089    protected void initialize(Object... args) {
090        distributionId = (String) args[0];
091        if (args.length > 1) {
092            Boolean embed = (Boolean) args[1];
093            embeddedMode = embed != null && embed;
094        }
095    }
096
097    @GET
098    @Produces("text/plain")
099    @Path("tree")
100    public Object tree(@QueryParam("root") String source) {
101        return TreeHelper.updateTree(getContext(), source);
102    }
103
104    @GET
105    @Produces("text/html")
106    @Path("treeView")
107    public Object treeView() {
108        return getView("tree").arg(Distribution.DIST_ID, ctx.getProperty(Distribution.DIST_ID));
109    }
110
111    @GET
112    @Produces("text/html")
113    public Object doGet() {
114        if (embeddedMode) {
115            DistributionSnapshot snap = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession());
116            Map<String, Integer> stats = new HashMap<String, Integer>();
117            stats.put("bundles", snap.getBundleIds().size());
118            stats.put("jComponents", snap.getJavaComponentIds().size());
119            stats.put("xComponents", snap.getXmlComponentIds().size());
120            stats.put("services", snap.getServiceIds().size());
121            stats.put("xps", snap.getExtensionPointIds().size());
122            stats.put("contribs", snap.getComponentIds().size());
123            return getView("indexSimple").arg(Distribution.DIST_ID, ctx.getProperty(Distribution.DIST_ID)).arg("stats",
124                    stats);
125        } else {
126            return getView("index").arg(Distribution.DIST_ID, ctx.getProperty(Distribution.DIST_ID));
127        }
128    }
129
130    @GET
131    @Produces("text/html")
132    @Path("listBundleGroups")
133    public Object getMavenGroups() {
134        BundleGroupTreeHelper bgth = new BundleGroupTreeHelper(getSnapshotManager().getSnapshot(distributionId,
135                ctx.getCoreSession()));
136        List<BundleGroupFlatTree> tree = bgth.getBundleGroupTree();
137        return getView("listBundleGroups").arg("tree", tree).arg(Distribution.DIST_ID,
138                ctx.getProperty(Distribution.DIST_ID));
139    }
140
141    public Map<String, DocumentationItem> getDescriptions(String targetType) {
142        DocumentationService ds = Framework.getLocalService(DocumentationService.class);
143        return ds.getAvailableDescriptions(getContext().getCoreSession(), targetType);
144    }
145
146    @GET
147    @Produces("text/html")
148    @Path("listBundles")
149    public Object getBundles() {
150        List<String> bundleIds = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getBundleIds();
151        return getView("listBundles").arg("bundleIds", bundleIds).arg(Distribution.DIST_ID,
152                ctx.getProperty(Distribution.DIST_ID));
153    }
154
155    @GET
156    @Produces("text/html")
157    @Path("listComponents")
158    public Object getComponents() {
159        List<String> javaComponentIds = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getJavaComponentIds();
160        List<ArtifactLabel> javaLabels = new ArrayList<ArtifactLabel>();
161        for (String id : javaComponentIds) {
162            javaLabels.add(ArtifactLabel.createLabelFromComponent(id));
163        }
164
165        List<String> xmlComponentIds = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getXmlComponentIds();
166        List<ArtifactLabel> xmlLabels = new ArrayList<ArtifactLabel>();
167        for (String id : xmlComponentIds) {
168            xmlLabels.add(ArtifactLabel.createLabelFromComponent(id));
169        }
170
171        Collections.sort(javaLabels);
172        Collections.sort(xmlLabels);
173
174        return getView("listComponents").arg("javaComponents", javaLabels).arg("xmlComponents", xmlLabels).arg(
175                Distribution.DIST_ID, ctx.getProperty(Distribution.DIST_ID));
176    }
177
178    @GET
179    @Produces("text/html")
180    @Path("listServices")
181    public Object getServices() {
182        List<String> serviceIds = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getServiceIds();
183
184        List<ArtifactLabel> serviceLabels = new ArrayList<ArtifactLabel>();
185
186        for (String id : serviceIds) {
187            serviceLabels.add(ArtifactLabel.createLabelFromService(id));
188        }
189        Collections.sort(serviceLabels);
190
191        return getView("listServices").arg("services", serviceLabels).arg(Distribution.DIST_ID,
192                ctx.getProperty(Distribution.DIST_ID));
193    }
194
195    protected Map<String, String> getRenderedDescriptions(String type) {
196
197        Map<String, DocumentationItem> descs = getDescriptions(type);
198        Map<String, String> result = new HashMap<String, String>();
199
200        for (String key : descs.keySet()) {
201            DocumentationItem docItem = descs.get(key);
202            String content = docItem.getContent();
203            if ("wiki".equals(docItem.getRenderingType())) {
204                Reader reader = new StringReader(content);
205                WikiSerializer engine = new WikiSerializer();
206                StringWriter writer = new StringWriter();
207                try {
208                    engine.serialize(reader, writer);
209                } catch (IOException | WikiParserException e) {
210                    throw new NuxeoException(e);
211                }
212                content = writer.getBuffer().toString();
213            } else {
214                content = "<div class='doc'>" + content + "</div>";
215            }
216            result.put(key, content);
217        }
218        return result;
219    }
220
221    @GET
222    @Produces("text/plain")
223    @Path("feedServices")
224    public String feedServices() throws JSONException {
225        List<String> serviceIds = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getServiceIds();
226
227        Map<String, String> descs = getRenderedDescriptions("NXService");
228
229        List<ArtifactLabel> serviceLabels = new ArrayList<ArtifactLabel>();
230
231        for (String id : serviceIds) {
232            serviceLabels.add(ArtifactLabel.createLabelFromService(id));
233        }
234        Collections.sort(serviceLabels);
235
236        JSONArray array = new JSONArray();
237
238        for (ArtifactLabel label : serviceLabels) {
239            JSONObject object = new JSONObject();
240            object.put("id", label.getId());
241            object.put("label", label.getLabel());
242            object.put("desc", descs.get(label.id));
243            object.put("url", "http://explorer.nuxeo.org/nuxeo/site/distribution/current/service2Bundle/" + label.id);
244            array.put(object);
245        }
246
247        return array.toString();
248    }
249
250    @GET
251    @Produces("text/plain")
252    @Path("feedExtensionPoints")
253    public String feedExtensionPoints() throws JSONException {
254        List<String> epIds = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getExtensionPointIds();
255
256        Map<String, String> descs = getRenderedDescriptions("NXExtensionPoint");
257
258        List<ArtifactLabel> labels = new ArrayList<ArtifactLabel>();
259
260        for (String id : epIds) {
261            labels.add(ArtifactLabel.createLabelFromExtensionPoint(id));
262        }
263        Collections.sort(labels);
264
265        JSONArray array = new JSONArray();
266
267        for (ArtifactLabel label : labels) {
268            JSONObject object = new JSONObject();
269            object.put("id", label.getId());
270            object.put("label", label.getLabel());
271            object.put("desc", descs.get(label.id));
272            object.put("url", "http://explorer.nuxeo.org/nuxeo/site/distribution/current/extensionPoint2Component/"
273                    + label.id);
274            array.put(object);
275        }
276
277        return array.toString();
278    }
279
280    @GET
281    @Produces("text/html")
282    @Path("listContributions")
283    public Object getContributions() {
284        DistributionSnapshot snapshot = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession());
285        List<String> cIds = snapshot.getContributionIds();
286        return getView("listContributions").arg("contributions", snapshot.getContributions()).arg(
287                Distribution.DIST_ID, ctx.getProperty(Distribution.DIST_ID));
288    }
289
290    @GET
291    @Produces("text/html")
292    @Path("listExtensionPoints")
293    public Object getExtensionPoints() {
294        List<String> epIds = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getExtensionPointIds();
295
296        List<ArtifactLabel> labels = epIds.stream().map(ArtifactLabel::createLabelFromExtensionPoint).collect(Collectors.toList());
297
298        Collections.sort(labels);
299        return getView("listExtensionPoints").arg("eps", labels).arg(Distribution.DIST_ID,
300                ctx.getProperty(Distribution.DIST_ID));
301    }
302
303    /**
304     * XXX Not used?
305     */
306    @POST
307    @Produces("text/html")
308    @Path("filterComponents")
309    public Object filterComponents(@FormParam("fulltext") String fulltext) {
310        List<NuxeoArtifact> artifacts = getSearcher().filterArtifact(getContext().getCoreSession(), distributionId,
311                ComponentInfo.TYPE_NAME, fulltext);
312
313        List<ArtifactLabel> xmlLabels = new ArrayList<>();
314        List<ArtifactLabel> javaLabels = new ArrayList<>();
315
316        for (NuxeoArtifact item : artifacts) {
317            ComponentInfo ci = (ComponentInfo) item;
318            if (ci.isXmlPureComponent()) {
319                xmlLabels.add(ArtifactLabel.createLabelFromComponent(ci.getId()));
320            } else {
321                javaLabels.add(ArtifactLabel.createLabelFromComponent(ci.getId()));
322            }
323        }
324        return getView("listComponents").arg("javaComponents", javaLabels).arg("xmlComponents", xmlLabels).arg(
325                Distribution.DIST_ID, ctx.getProperty(Distribution.DIST_ID)).arg("searchFilter", fulltext);
326    }
327
328    /**
329     * XXX Not used?
330     */
331    @POST
332    @Produces("text/html")
333    @Path("filterBundles")
334    public Object filterBundles(@FormParam("fulltext") String fulltext) {
335        List<NuxeoArtifact> artifacts = getSearcher().filterArtifact(getContext().getCoreSession(), distributionId,
336                BundleInfo.TYPE_NAME, fulltext);
337        List<String> bundleIds = new ArrayList<String>();
338        for (NuxeoArtifact item : artifacts) {
339            bundleIds.add(item.getId());
340        }
341        return getView("listBundles").arg("bundleIds", bundleIds).arg(Distribution.DIST_ID,
342                ctx.getProperty(Distribution.DIST_ID)).arg("searchFilter", fulltext);
343    }
344
345    /**
346     * XXX Not used?
347     */
348    @POST
349    @Produces("text/html")
350    @Path("filterServices")
351    public Object filterServices() {
352        String fulltext = getContext().getForm().getFormProperty("fulltext");
353        List<NuxeoArtifact> artifacts = getSearcher().filterArtifact(getContext().getCoreSession(), distributionId,
354                ServiceInfo.TYPE_NAME, fulltext);
355        List<String> serviceIds = new ArrayList<String>();
356        for (NuxeoArtifact item : artifacts) {
357            serviceIds.add(item.getId());
358        }
359        List<ArtifactLabel> serviceLabels = new ArrayList<ArtifactLabel>();
360
361        for (String id : serviceIds) {
362            serviceLabels.add(ArtifactLabel.createLabelFromService(id));
363        }
364        return getView("listServices").arg("services", serviceLabels).arg(Distribution.DIST_ID,
365                ctx.getProperty(Distribution.DIST_ID)).arg("searchFilter", fulltext);
366    }
367
368    @POST
369    @Produces("text/html")
370    @Path("filterExtensionPoints")
371    public Object filterExtensionPoints(@FormParam("fulltext") String fulltext) {
372        List<NuxeoArtifact> artifacts = getSearcher().filterArtifact(getContext().getCoreSession(), distributionId,
373                ExtensionPointInfo.TYPE_NAME, fulltext);
374        List<String> eps = artifacts.stream().map(NuxeoArtifact::getId).collect(Collectors.toList());
375        List<ArtifactLabel> labels = eps.stream().map(ArtifactLabel::createLabelFromExtensionPoint).collect(Collectors.toList());
376        return getView("listExtensionPoints").arg("eps", labels).arg(Distribution.DIST_ID,
377                ctx.getProperty(Distribution.DIST_ID)).arg("searchFilter", fulltext);
378    }
379
380    @POST
381    @Produces("text/html")
382    @Path("filterContributions")
383    public Object filterContributions(@FormParam("fulltext") String fulltext) {
384        List<NuxeoArtifact> artifacts = getSearcher().filterArtifact(getContext().getCoreSession(), distributionId,
385                ExtensionInfo.TYPE_NAME, fulltext);
386        return getView("listContributions").arg("contributions", artifacts).arg(Distribution.DIST_ID,
387                ctx.getProperty(Distribution.DIST_ID)).arg("searchFilter", fulltext);
388    }
389
390    @Path("doc")
391    public Resource viewDoc() {
392        return ctx.newObject("documentation");
393    }
394
395    @GET
396    @Produces("text/html")
397    @Path("service2Bundle/{serviceId}")
398    public Object service2Bundle(@PathParam("serviceId") String serviceId) {
399
400        ServiceInfo si = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getService(serviceId);
401        if (si == null) {
402            return null;
403        }
404        String cid = si.getComponentId();
405
406        ComponentInfo ci = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getComponent(cid);
407        String bid = ci.getBundle().getId();
408
409        org.nuxeo.common.utils.Path target = new org.nuxeo.common.utils.Path(getContext().getRoot().getName());
410        target = target.append(distributionId);
411        target = target.append("viewBundle");
412        target = target.append(bid + "#Service." + serviceId);
413        try {
414            return Response.seeOther(new URI(target.toString())).build();
415        } catch (URISyntaxException e) {
416            throw new NuxeoException(e);
417        }
418    }
419
420    @GET
421    @Produces("text/html")
422    @Path("extensionPoint2Component/{epId}")
423    public Object extensionPoint2Component(@PathParam("epId") String epId) {
424
425        ExtensionPointInfo epi = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession()).getExtensionPoint(
426                epId);
427        if (epi == null) {
428            return null;
429        }
430        String cid = epi.getComponent().getId();
431
432        org.nuxeo.common.utils.Path target = new org.nuxeo.common.utils.Path(getContext().getRoot().getName());
433        target = target.append(distributionId);
434        target = target.append("viewComponent");
435        target = target.append(cid + "#extensionPoint." + epId);
436        try {
437            return Response.seeOther(new URI(target.toString())).build();
438        } catch (URISyntaxException e) {
439            throw new NuxeoException(e);
440        }
441    }
442
443    @Path("viewBundle/{bundleId}")
444    public Resource viewBundle(@PathParam("bundleId") String bundleId) {
445        NuxeoArtifactWebObject wo = (NuxeoArtifactWebObject) ctx.newObject("bundle", bundleId);
446        NuxeoArtifact nxItem = wo.getNxArtifact();
447        if (nxItem == null) {
448            throw new WebResourceNotFoundException(bundleId);
449        }
450        TreeHelper.updateTree(getContext(), nxItem.getHierarchyPath());
451        return wo;
452    }
453
454    @Path("viewComponent/{componentId}")
455    public Resource viewComponent(@PathParam("componentId") String componentId) {
456        NuxeoArtifactWebObject wo = (NuxeoArtifactWebObject) ctx.newObject("component", componentId);
457        NuxeoArtifact nxItem = wo.getNxArtifact();
458        if (nxItem == null) {
459            throw new WebResourceNotFoundException(componentId);
460        }
461        TreeHelper.updateTree(getContext(), nxItem.getHierarchyPath());
462        return wo;
463    }
464
465    @Path("viewSeamComponent/{componentId}")
466    public Resource viewSeamComponent(@PathParam("componentId") String componentId) {
467        return (NuxeoArtifactWebObject) ctx.newObject("seamComponent", componentId);
468    }
469
470    @Path("viewOperation/{opId}")
471    public Resource viewOperation(@PathParam("opId") String opId) {
472        return (NuxeoArtifactWebObject) ctx.newObject("operation", opId);
473    }
474
475    @Path("viewService/{serviceId}")
476    public Resource viewService(@PathParam("serviceId") String serviceId) {
477        NuxeoArtifactWebObject wo = (NuxeoArtifactWebObject) ctx.newObject("service", serviceId);
478        NuxeoArtifact nxItem = wo.getNxArtifact();
479        if (nxItem == null) {
480            throw new WebResourceNotFoundException(serviceId);
481        }
482        TreeHelper.updateTree(getContext(), nxItem.getHierarchyPath());
483        return wo;
484    }
485
486    @Path("viewExtensionPoint/{epId}")
487    public Resource viewExtensionPoint(@PathParam("epId") String epId) {
488        NuxeoArtifactWebObject wo = (NuxeoArtifactWebObject) ctx.newObject("extensionPoint", epId);
489        NuxeoArtifact nxItem = wo.getNxArtifact();
490        if (nxItem == null) {
491            throw new WebResourceNotFoundException(epId);
492        }
493        TreeHelper.updateTree(getContext(), nxItem.getHierarchyPath());
494        return wo;
495    }
496
497    @Path("viewContribution/{cId}")
498    public Resource viewContribution(@PathParam("cId") String cId) {
499        NuxeoArtifactWebObject wo = (NuxeoArtifactWebObject) ctx.newObject("contribution", cId);
500        NuxeoArtifact nxItem = wo.getNxArtifact();
501        if (nxItem == null) {
502            throw new WebResourceNotFoundException(cId);
503        }
504        TreeHelper.updateTree(getContext(), nxItem.getHierarchyPath());
505        return wo;
506    }
507
508    @Path("viewBundleGroup/{gId}")
509    public Resource viewBundleGroup(@PathParam("gId") String gId) {
510        NuxeoArtifactWebObject wo = (NuxeoArtifactWebObject) ctx.newObject("bundleGroup", gId);
511        NuxeoArtifact nxItem = wo.getNxArtifact();
512        if (nxItem == null) {
513            throw new WebResourceNotFoundException(gId);
514        }
515        TreeHelper.updateTree(getContext(), nxItem.getHierarchyPath());
516        return wo;
517    }
518
519    @Path("viewArtifact/{id}")
520    public Object viewArtifact(@PathParam("id") String id) {
521        DistributionSnapshot snap = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession());
522
523        BundleGroup bg = snap.getBundleGroup(id);
524        if (bg != null) {
525            return viewBundleGroup(id);
526        }
527
528        BundleInfo bi = snap.getBundle(id);
529        if (bi != null) {
530            return viewBundle(id);
531        }
532
533        ComponentInfo ci = snap.getComponent(id);
534        if (ci != null) {
535            return viewComponent(id);
536        }
537
538        ServiceInfo si = snap.getService(id);
539        if (si != null) {
540            return viewService(id);
541        }
542
543        ExtensionPointInfo epi = snap.getExtensionPoint(id);
544        if (epi != null) {
545            return viewExtensionPoint(id);
546        }
547
548        ExtensionInfo ei = snap.getContribution(id);
549        if (ei != null) {
550            return viewContribution(id);
551        }
552
553        return Response.status(404).build();
554    }
555
556    public String getLabel(String id) {
557        return null;
558    }
559
560    @GET
561    @Produces("text/html")
562    @Path("listSeamComponents")
563    public Object listSeamComponents() {
564        return dolistSeamComponents("listSeamComponents", false);
565    }
566
567    @GET
568    @Produces("text/html")
569    @Path("listSeamComponentsSimple")
570    public Object listSeamComponentsSimple() {
571        return dolistSeamComponents("listSeamComponentsSimple", true);
572    }
573
574    protected Object dolistSeamComponents(String view, boolean hideNav) {
575
576        getSnapshotManager().initSeamContext(getContext().getRequest());
577
578        DistributionSnapshot snap = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession());
579        List<SeamComponentInfo> seamComponents = snap.getSeamComponents();
580        return getView(view).arg("seamComponents", seamComponents).arg(Distribution.DIST_ID,
581                ctx.getProperty(Distribution.DIST_ID)).arg("hideNav", Boolean.valueOf(hideNav));
582    }
583
584    @GET
585    @Produces("text/html")
586    @Path("listOperations")
587    public Object listOperations() {
588        DistributionSnapshot snap = getSnapshotManager().getSnapshot(distributionId, ctx.getCoreSession());
589        List<OperationInfo> operations = snap.getOperations();
590        return getView("listOperations").arg("operations", operations).arg(Distribution.DIST_ID,
591                ctx.getProperty(Distribution.DIST_ID)).arg("hideNav", Boolean.valueOf(false));
592    }
593
594}