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}