001/*
002 * (C) Copyright 2014-2018 Nuxeo (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 *     bstefanescu
018 *     vpasquier <vpasquier@nuxeo.com>
019 */
020package org.nuxeo.ecm.automation.server.jaxrs.doc;
021
022import java.io.ByteArrayOutputStream;
023import java.io.IOException;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.LinkedHashMap;
030import java.util.List;
031import java.util.Map;
032
033import javax.servlet.http.HttpServletRequest;
034import javax.ws.rs.GET;
035import javax.ws.rs.Path;
036import javax.ws.rs.Produces;
037import javax.ws.rs.QueryParam;
038import javax.ws.rs.WebApplicationException;
039import javax.ws.rs.core.Response;
040
041import org.apache.commons.lang3.StringUtils;
042import org.apache.commons.logging.Log;
043import org.apache.commons.logging.LogFactory;
044import org.nuxeo.ecm.automation.AutomationService;
045import org.nuxeo.ecm.automation.OperationDocumentation;
046import org.nuxeo.ecm.automation.OperationException;
047import org.nuxeo.ecm.automation.core.trace.Trace;
048import org.nuxeo.ecm.automation.core.trace.TracerFactory;
049import org.nuxeo.ecm.automation.io.yaml.YamlWriter;
050import org.nuxeo.ecm.core.api.CoreSession;
051import org.nuxeo.ecm.core.api.NuxeoException;
052import org.nuxeo.ecm.platform.web.common.RequestContext;
053import org.nuxeo.ecm.webengine.JsonFactoryManager;
054import org.nuxeo.ecm.webengine.WebEngine;
055import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory;
056import org.nuxeo.ecm.webengine.model.Template;
057import org.nuxeo.ecm.webengine.model.WebObject;
058import org.nuxeo.ecm.webengine.model.impl.AbstractResource;
059import org.nuxeo.ecm.webengine.model.impl.ResourceTypeImpl;
060import org.nuxeo.runtime.api.Framework;
061
062/**
063 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
064 */
065@WebObject(type = "doc")
066@Produces("text/html;charset=UTF-8")
067public class DocResource extends AbstractResource<ResourceTypeImpl> {
068
069    private final Log log = LogFactory.getLog(DocResource.class);
070
071    protected AutomationService service;
072
073    protected List<OperationDocumentation> ops;
074
075    @Override
076    public void initialize(Object... args) {
077        try {
078            service = Framework.getService(AutomationService.class);
079            ops = service.getDocumentation();
080        } catch (OperationException e) {
081            log.error("Failed to get automation service", e);
082            throw new NuxeoException(e);
083        }
084    }
085
086    protected Template getTemplateFor(String browse) {
087        return getTemplateView("index").arg("browse", browse);
088    }
089
090    protected Template getTemplateView(String name) {
091        Map<String, List<OperationDocumentation>> cats = new HashMap<>();
092        for (OperationDocumentation op : ops) {
093            cats.computeIfAbsent(op.getCategory(), k -> new ArrayList<>()).add(op);
094        }
095        // sort categories
096        List<String> catNames = new ArrayList<>();
097        catNames.addAll(cats.keySet());
098        Collections.sort(catNames);
099        Map<String, List<OperationDocumentation>> scats = new LinkedHashMap<>();
100        for (String catName : catNames) {
101            scats.put(catName, cats.get(catName));
102        }
103        return getView(name).arg("categories", scats).arg("operations", ops);
104    }
105
106    @GET
107    public Object doGet(@QueryParam("id") String id, @QueryParam("browse") String browse) {
108        if (id == null) {
109            return getTemplateFor(browse);
110        } else {
111            OperationDocumentation opDoc = null;
112            for (OperationDocumentation op : ops) {
113                if (op.getId().equals(id)) {
114                    opDoc = op;
115                    break;
116                }
117            }
118            if (opDoc == null) {
119                throw new WebApplicationException(Response.status(404).build());
120            }
121            Template tpl = getTemplateFor(browse);
122            tpl.arg("operation", opDoc);
123            CoreSession session = SessionFactory.getSession();
124            if (session.getPrincipal().isAdministrator()) {
125                // add yaml format - chains are exposing their operations
126                // so this information should be restricted to administrators.
127                try {
128                    ByteArrayOutputStream out = new ByteArrayOutputStream();
129                    YamlWriter.toYaml(out, opDoc);
130                    tpl.arg("yaml", out.toString());
131                } catch (IOException e) {
132                    throw new NuxeoException(e);
133                }
134            }
135            return tpl;
136        }
137    }
138
139    protected boolean canManageTraces() {
140        return WebEngine.getActiveContext().getPrincipal().isAdministrator();
141    }
142
143    @GET
144    @Path("/wiki")
145    public Object doGetWiki() {
146        return getTemplateView("wiki");
147    }
148
149    public boolean isTraceEnabled() {
150        TracerFactory tracerFactory = Framework.getService(TracerFactory.class);
151        return tracerFactory.getRecordingState();
152    }
153
154    @GET
155    @Path("/toggleTraces")
156    public Object toggleTraces() {
157        if (!canManageTraces()) {
158            return "You can not manage traces";
159        }
160        TracerFactory tracerFactory = Framework.getService(TracerFactory.class);
161        tracerFactory.toggleRecording();
162        HttpServletRequest request = RequestContext.getActiveContext().getRequest();
163        String url = request.getHeader("Referer");
164        try {
165            return Response.seeOther(new URI(url)).build();
166        } catch (URISyntaxException e) {
167            throw new RuntimeException(e);
168        }
169    }
170
171    @GET
172    @Path("/toggleStackDisplay")
173    @Produces("text/plain")
174    public Object toggleStackDisplay() {
175        if (!canManageTraces()) {
176            return "You can not manage json exception stack display";
177        }
178        JsonFactoryManager jsonFactoryManager = Framework.getService(JsonFactoryManager.class);
179        return String.valueOf(jsonFactoryManager.toggleStackDisplay());
180    }
181
182    @GET
183    @Path("/traces")
184    @Produces("text/plain")
185    public String doGetTrace(@QueryParam("opId") String opId) {
186        if (!canManageTraces()) {
187            return "You can not manage traces";
188        }
189        TracerFactory tracerFactory = Framework.getService(TracerFactory.class);
190        Trace trace = tracerFactory.getTrace(opId);
191        if (trace == null) {
192            return "no trace";
193        }
194        return tracerFactory.print(trace);
195    }
196
197    public String[] getInputs(OperationDocumentation op) {
198        if (op == null) {
199            throw new IllegalArgumentException("Operation must not be null");
200        }
201        if (op.signature == null || op.signature.length == 0) {
202            return new String[0];
203        }
204        String[] result = new String[op.signature.length / 2];
205        for (int i = 0, k = 0; i < op.signature.length; i += 2, k++) {
206            result[k] = op.signature[i];
207        }
208        return result;
209    }
210
211    public String[] getOutputs(OperationDocumentation op) {
212        if (op == null) {
213            throw new IllegalArgumentException("Operation must not be null");
214        }
215        if (op.signature == null || op.signature.length == 0) {
216            return new String[0];
217        }
218        String[] result = new String[op.signature.length / 2];
219        for (int i = 1, k = 0; i < op.signature.length; i += 2, k++) {
220            result[k] = op.signature[i];
221        }
222        return result;
223    }
224
225    public String getInputsAsString(OperationDocumentation op) {
226        String[] result = getInputs(op);
227        if (result == null || result.length == 0) {
228            return "void";
229        }
230        return StringUtils.join(result, ", ");
231    }
232
233    public String getOutputsAsString(OperationDocumentation op) {
234        String[] result = getOutputs(op);
235        if (result == null || result.length == 0) {
236            return "void";
237        }
238        return StringUtils.join(result, ", ");
239    }
240
241    public String getParamDefaultValue(OperationDocumentation.Param param) {
242        if (param.values != null && param.values.length > 0) {
243            return StringUtils.join(param.values, ", ");
244        }
245        return "";
246    }
247
248    public boolean hasOperation(OperationDocumentation op) {
249        if (op == null) {
250            throw new IllegalArgumentException("Operation must not be null");
251        }
252        if (op.getOperations() == null || op.getOperations().length == 0) {
253            return false;
254        }
255        return true;
256    }
257}