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}