001/*
002 * Copyright (c) 2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 *     vpasquier <vpasquier@nuxeo.com>
012 */
013package org.nuxeo.ecm.automation.server.jaxrs.doc;
014
015import java.io.ByteArrayOutputStream;
016import java.io.IOException;
017import java.net.URI;
018import java.net.URISyntaxException;
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025
026import javax.servlet.http.HttpServletRequest;
027import javax.ws.rs.GET;
028import javax.ws.rs.Path;
029import javax.ws.rs.Produces;
030import javax.ws.rs.QueryParam;
031import javax.ws.rs.WebApplicationException;
032import javax.ws.rs.core.Response;
033
034import org.apache.commons.lang.StringUtils;
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.ecm.automation.AutomationService;
038import org.nuxeo.ecm.automation.OperationDocumentation;
039import org.nuxeo.ecm.automation.OperationException;
040import org.nuxeo.ecm.automation.core.trace.Trace;
041import org.nuxeo.ecm.automation.core.trace.TracerFactory;
042import org.nuxeo.ecm.automation.io.yaml.YamlWriter;
043import org.nuxeo.ecm.core.api.CoreSession;
044import org.nuxeo.ecm.core.api.NuxeoPrincipal;
045import org.nuxeo.ecm.webengine.JsonFactoryManager;
046import org.nuxeo.ecm.webengine.WebEngine;
047import org.nuxeo.ecm.webengine.WebException;
048import org.nuxeo.ecm.webengine.jaxrs.context.RequestContext;
049import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory;
050import org.nuxeo.ecm.webengine.model.Template;
051import org.nuxeo.ecm.webengine.model.WebObject;
052import org.nuxeo.ecm.webengine.model.impl.AbstractResource;
053import org.nuxeo.ecm.webengine.model.impl.ResourceTypeImpl;
054import org.nuxeo.runtime.api.Framework;
055
056/**
057 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
058 */
059@WebObject(type = "doc")
060@Produces("text/html;charset=UTF-8")
061public class DocResource extends AbstractResource<ResourceTypeImpl> {
062
063    private final Log log = LogFactory.getLog(DocResource.class);
064
065    protected AutomationService service;
066
067    protected List<OperationDocumentation> ops;
068
069    @Override
070    public void initialize(Object... args) {
071        try {
072            service = Framework.getLocalService(AutomationService.class);
073            ops = service.getDocumentation();
074        } catch (OperationException e) {
075            log.error("Failed to get automation service", e);
076            throw WebException.wrap(e);
077        }
078    }
079
080    protected Template getTemplateFor(String browse) {
081        return getTemplateView("index").arg("browse", browse);
082    }
083
084    protected Template getTemplateView(String name) {
085        Map<String, List<OperationDocumentation>> cats = new HashMap<String, List<OperationDocumentation>>();
086        for (OperationDocumentation op : ops) {
087            List<OperationDocumentation> list = cats.get(op.getCategory());
088            if (list == null) {
089                list = new ArrayList<OperationDocumentation>();
090                cats.put(op.getCategory(), list);
091            }
092            list.add(op);
093        }
094        // sort categories
095        List<String> catNames = new ArrayList<>();
096        catNames.addAll(cats.keySet());
097        Collections.sort(catNames);
098        Map<String, List<OperationDocumentation>> scats = new LinkedHashMap<String, List<OperationDocumentation>>();
099        for (String catName : catNames) {
100            scats.put(catName, cats.get(catName));
101        }
102        return getView(name).arg("categories", scats).arg("operations", ops);
103    }
104
105    @GET
106    public Object doGet(@QueryParam("id") String id, @QueryParam("browse") String browse) {
107        if (id == null) {
108            return getTemplateFor(browse);
109        } else {
110            OperationDocumentation opDoc = null;
111            for (OperationDocumentation op : ops) {
112                if (op.getId().equals(id)) {
113                    opDoc = op;
114                    break;
115                }
116            }
117            if (opDoc == null) {
118                throw new WebApplicationException(Response.status(404).build());
119            }
120            Template tpl = getTemplateFor(browse);
121            tpl.arg("operation", opDoc);
122            CoreSession session = SessionFactory.getSession();
123            if (((NuxeoPrincipal) session.getPrincipal()).isAdministrator()) {
124                // add yaml format - chains are exposing their operations
125                // so this information should be restricted to administrators.
126                try {
127                    ByteArrayOutputStream out = new ByteArrayOutputStream();
128                    YamlWriter.toYaml(out, opDoc);
129                    tpl.arg("yaml", out.toString());
130                } catch (IOException e) {
131                    throw WebException.wrap(e);
132                }
133            }
134            return tpl;
135        }
136    }
137
138    protected boolean canManageTraces() {
139        return ((NuxeoPrincipal) WebEngine.getActiveContext().getPrincipal()).isAdministrator();
140    }
141
142    @GET
143    @Path("/wiki")
144    public Object doGetWiki() {
145        return getTemplateView("wiki");
146    }
147
148    public boolean isTraceEnabled() {
149        TracerFactory tracerFactory = Framework.getLocalService(TracerFactory.class);
150        return tracerFactory.getRecordingState();
151    }
152
153    @GET
154    @Path("/toggleTraces")
155    public Object toggleTraces() {
156        if (!canManageTraces()) {
157            return "You can not manage traces";
158        }
159        TracerFactory tracerFactory = Framework.getLocalService(TracerFactory.class);
160        tracerFactory.toggleRecording();
161        HttpServletRequest request = RequestContext.getActiveContext().getRequest();
162        String url = request.getHeader("Referer");
163        try {
164            return Response.seeOther(new URI(url)).build();
165        } catch (URISyntaxException e) {
166            throw new RuntimeException(e);
167        }
168    }
169
170    @GET
171    @Path("/toggleStackDisplay")
172    @Produces("text/plain")
173    public Object toggleStackDisplay() {
174        if (!canManageTraces()) {
175            return "You can not manage json exception stack display";
176        }
177        JsonFactoryManager jsonFactoryManager = Framework.getLocalService(JsonFactoryManager.class);
178        return String.valueOf(jsonFactoryManager.toggleStackDisplay());
179    }
180
181    @GET
182    @Path("/traces")
183    @Produces("text/plain")
184    public String doGetTrace(@QueryParam("opId") String opId) {
185        if (!canManageTraces()) {
186            return "You can not manage traces";
187        }
188        TracerFactory tracerFactory = Framework.getLocalService(TracerFactory.class);
189        Trace trace = tracerFactory.getTrace(opId);
190        if (trace != null) {
191            return tracerFactory.getTrace(opId).getFormattedText();
192        } else {
193            return "no trace";
194        }
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}