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 <[email protected]> 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.webengine.JsonFactoryManager; 053import org.nuxeo.ecm.webengine.WebEngine; 054import org.nuxeo.ecm.webengine.jaxrs.context.RequestContext; 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:[email protected]">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}