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