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