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