001/* 002 * (C) Copyright 2014-2015 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 * <a href="mailto:grenard@nuxeo.com">Guillaume Renard</a> 018 * 019 */ 020 021package org.nuxeo.ecm.platform.routing.core.io; 022 023import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; 024import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON; 025import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE; 026 027import java.io.Closeable; 028import java.io.IOException; 029import java.io.OutputStream; 030 031import javax.inject.Inject; 032 033import org.apache.commons.lang.StringUtils; 034import org.codehaus.jackson.JsonGenerationException; 035import org.codehaus.jackson.JsonGenerator; 036import org.nuxeo.ecm.core.api.CoreSession; 037import org.nuxeo.ecm.core.api.IdRef; 038import org.nuxeo.ecm.core.api.NuxeoGroup; 039import org.nuxeo.ecm.core.api.NuxeoPrincipal; 040import org.nuxeo.ecm.core.api.model.Property; 041import org.nuxeo.ecm.core.io.marshallers.json.ExtensibleEntityJsonWriter; 042import org.nuxeo.ecm.core.io.marshallers.json.OutputStreamWithJsonWriter; 043import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter; 044import org.nuxeo.ecm.core.io.registry.MarshallerRegistry; 045import org.nuxeo.ecm.core.io.registry.Writer; 046import org.nuxeo.ecm.core.io.registry.context.RenderingContext; 047import org.nuxeo.ecm.core.io.registry.context.RenderingContext.SessionWrapper; 048import org.nuxeo.ecm.core.io.registry.reflect.Setup; 049import org.nuxeo.ecm.core.schema.SchemaManager; 050import org.nuxeo.ecm.core.schema.types.CompositeType; 051import org.nuxeo.ecm.core.schema.types.Field; 052import org.nuxeo.ecm.core.schema.types.Schema; 053import org.nuxeo.ecm.core.schema.utils.DateParser; 054import org.nuxeo.ecm.platform.actions.ActionContext; 055import org.nuxeo.ecm.platform.actions.ELActionContext; 056import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 057import org.nuxeo.ecm.platform.routing.api.DocumentRoute; 058import org.nuxeo.ecm.platform.routing.api.DocumentRoutingConstants; 059import org.nuxeo.ecm.platform.routing.core.impl.GraphNode; 060import org.nuxeo.ecm.platform.routing.core.impl.GraphNode.Button; 061import org.nuxeo.ecm.platform.routing.core.impl.GraphRoute; 062import org.nuxeo.ecm.platform.task.Task; 063import org.nuxeo.ecm.platform.task.TaskComment; 064import org.nuxeo.ecm.platform.usermanager.UserManager; 065import org.nuxeo.runtime.api.Framework; 066 067/** 068 * @since 7.2 069 */ 070@Setup(mode = SINGLETON, priority = REFERENCE) 071public class TaskWriter extends ExtensibleEntityJsonWriter<Task> { 072 073 public static final String FETCH_ACTORS = "actors"; 074 075 public static final String TARGET_DOCUMENT_IDS = "targetDocumentIds"; 076 077 public static final String FETCH_TARGET_DOCUMENT = TARGET_DOCUMENT_IDS; 078 079 protected static final String USER_PREFIX = "user"; 080 081 protected static final String GROUP_PREFIX = "group"; 082 083 protected static final String SEPARATOR = ":"; 084 085 @Inject 086 private SchemaManager schemaManager; 087 088 @Inject 089 UserManager userManager; 090 091 public TaskWriter() { 092 super(ENTITY_TYPE, Task.class); 093 } 094 095 public static final String ENTITY_TYPE = "task"; 096 097 @Override 098 public void writeEntityBody(Task item, JsonGenerator jg) throws IOException { 099 GraphRoute workflowInstance = null; 100 GraphNode node = null; 101 String workflowInstanceId = item.getProcessId(); 102 final String nodeId = item.getVariable(DocumentRoutingConstants.TASK_NODE_ID_KEY); 103 try (SessionWrapper wrapper = ctx.getSession(item.getDocument())) { 104 if (StringUtils.isNotBlank(workflowInstanceId)) { 105 NodeAccessRunner nodeAccessRunner = new NodeAccessRunner(wrapper.getSession(), workflowInstanceId, 106 nodeId); 107 nodeAccessRunner.runUnrestricted(); 108 workflowInstance = nodeAccessRunner.getWorkflowInstance(); 109 node = nodeAccessRunner.getNode(); 110 } 111 112 jg.writeStringField("id", item.getDocument().getId()); 113 jg.writeStringField("name", item.getName()); 114 jg.writeStringField("workflowInstanceId", workflowInstanceId); 115 if (workflowInstance != null) { 116 jg.writeStringField("workflowModelName", workflowInstance.getModelName()); 117 } 118 jg.writeStringField("state", item.getDocument().getCurrentLifeCycleState()); 119 jg.writeStringField("directive", item.getDirective()); 120 jg.writeStringField("created", DateParser.formatW3CDateTime(item.getCreated())); 121 jg.writeStringField("dueDate", DateParser.formatW3CDateTime(item.getDueDate())); 122 jg.writeStringField("nodeName", item.getVariable(DocumentRoutingConstants.TASK_NODE_ID_KEY)); 123 124 jg.writeArrayFieldStart(TARGET_DOCUMENT_IDS); 125 final boolean isFetchTargetDocumentIds = ctx.getFetched(ENTITY_TYPE).contains(FETCH_TARGET_DOCUMENT); 126 for (String docId : item.getTargetDocumentsIds()) { 127 IdRef idRef = new IdRef(docId); 128 if (wrapper.getSession().exists(idRef)) { 129 if (isFetchTargetDocumentIds) { 130 writeEntity(wrapper.getSession().getDocument(idRef), jg); 131 } else { 132 jg.writeStartObject(); 133 jg.writeStringField("id", docId); 134 jg.writeEndObject(); 135 } 136 } 137 } 138 jg.writeEndArray(); 139 140 jg.writeArrayFieldStart("actors"); 141 final boolean isFetchActors = ctx.getFetched(ENTITY_TYPE).contains(FETCH_ACTORS); 142 for (String actorId : item.getActors()) { 143 if (isFetchActors) { 144 if (actorId.startsWith(USER_PREFIX + SEPARATOR)) { 145 actorId = actorId.substring(USER_PREFIX.length() + SEPARATOR.length()); 146 NuxeoPrincipal user = userManager.getPrincipal(actorId); 147 if (user != null) { 148 writeEntity(user, jg); 149 continue; 150 } 151 } else if (actorId.startsWith(GROUP_PREFIX + SEPARATOR)) { 152 actorId = actorId.substring(GROUP_PREFIX.length() + SEPARATOR.length()); 153 NuxeoGroup group = userManager.getGroup(actorId); 154 if (group != null) { 155 writeEntity(group, jg); 156 continue; 157 } 158 } else { 159 NuxeoPrincipal user = userManager.getPrincipal(actorId); 160 if (user != null) { 161 writeEntity(user, jg); 162 continue; 163 } else { 164 NuxeoGroup group = userManager.getGroup(actorId); 165 if (group != null) { 166 writeEntity(group, jg); 167 continue; 168 } 169 } 170 } 171 } 172 jg.writeStartObject(); 173 jg.writeStringField("id", actorId); 174 jg.writeEndObject(); 175 } 176 jg.writeEndArray(); 177 178 jg.writeArrayFieldStart("comments"); 179 for (TaskComment comment : item.getComments()) { 180 jg.writeStartObject(); 181 jg.writeStringField("author", comment.getAuthor()); 182 jg.writeStringField("text", comment.getText()); 183 jg.writeStringField("date", DateParser.formatW3CDateTime(comment.getCreationDate().getTime())); 184 jg.writeEndObject(); 185 } 186 jg.writeEndArray(); 187 188 jg.writeFieldName("variables"); 189 jg.writeStartObject(); 190 // add nodeVariables 191 if (node != null) { 192 writeTaskVariables(node, jg, registry, ctx, schemaManager); 193 } 194 // add workflow variables 195 if (workflowInstance != null) { 196 writeWorkflowVariables(workflowInstance, node, jg, registry, ctx, schemaManager); 197 } 198 jg.writeEndObject(); 199 200 if (node != null) { 201 jg.writeFieldName("taskInfo"); 202 jg.writeStartObject(); 203 final ActionManager actionManager = Framework.getService(ActionManager.class); 204 jg.writeArrayFieldStart("taskActions"); 205 for (Button button : node.getTaskButtons()) { 206 if (StringUtils.isBlank(button.getFilter()) || actionManager.checkFilter(button.getFilter(), 207 createActionContext(wrapper.getSession()))) { 208 jg.writeStartObject(); 209 jg.writeStringField("name", button.getName()); 210 jg.writeStringField("url", ctx.getBaseUrl() + "api/v1/task/" + item.getDocument().getId() + "/" 211 + button.getName()); 212 jg.writeStringField("label", button.getLabel()); 213 jg.writeEndObject(); 214 } 215 } 216 jg.writeEndArray(); 217 218 jg.writeFieldName("layoutResource"); 219 jg.writeStartObject(); 220 jg.writeStringField("name", node.getTaskLayout()); 221 jg.writeStringField("url", 222 ctx.getBaseUrl() + "site/layout-manager/layouts/?layoutName=" + node.getTaskLayout()); 223 jg.writeEndObject(); 224 225 jg.writeArrayFieldStart("schemas"); 226 for (String schema : node.getDocument().getSchemas()) { 227 // TODO only keep functional schema once adaptation done 228 jg.writeStartObject(); 229 jg.writeStringField("name", schema); 230 jg.writeStringField("url", ctx.getBaseUrl() + "api/v1/config/schemas/" + schema); 231 jg.writeEndObject(); 232 } 233 jg.writeEndArray(); 234 235 jg.writeEndObject(); 236 } 237 } 238 239 } 240 241 protected static ActionContext createActionContext(CoreSession session) { 242 ActionContext actionContext = new ELActionContext(); 243 actionContext.setDocumentManager(session); 244 actionContext.setCurrentPrincipal((NuxeoPrincipal) session.getPrincipal()); 245 return actionContext; 246 } 247 248 /** 249 * @since 8.3 250 */ 251 public static void writeTaskVariables(GraphNode node, JsonGenerator jg, MarshallerRegistry registry, 252 RenderingContext ctx, SchemaManager schemaManager) throws IOException, JsonGenerationException { 253 if (node == null || node.getDocument() == null) { 254 return; 255 } 256 String facet = (String) node.getDocument().getPropertyValue(GraphNode.PROP_VARIABLES_FACET); 257 if (StringUtils.isNotBlank(facet)) { 258 259 CompositeType type = schemaManager.getFacet(facet); 260 if (type != null) { 261 boolean hasFacet = node.getDocument().hasFacet(facet); 262 263 Writer<Property> propertyWriter = registry.getWriter(ctx, Property.class, APPLICATION_JSON_TYPE); 264 // provides the current route to the property marshaller 265 try (Closeable resource = ctx.wrap() 266 .with(DocumentModelJsonWriter.ENTITY_TYPE, node.getDocument()) 267 .open()) { 268 for (Field f : type.getFields()) { 269 String name = f.getName().getLocalName(); 270 Property property = hasFacet ? node.getDocument().getProperty(name) : null; 271 OutputStream out = new OutputStreamWithJsonWriter(jg); 272 jg.writeFieldName(name); 273 propertyWriter.write(property, Property.class, Property.class, APPLICATION_JSON_TYPE, out); 274 } 275 } 276 } 277 } 278 } 279 280 /** 281 * @since 8.3 282 */ 283 public static void writeWorkflowVariables(DocumentRoute route, GraphNode node, JsonGenerator jg, 284 MarshallerRegistry registry, RenderingContext ctx, SchemaManager schemaManager) 285 throws IOException, JsonGenerationException { 286 String facet = (String) route.getDocument().getPropertyValue(GraphRoute.PROP_VARIABLES_FACET); 287 if (StringUtils.isNotBlank(facet)) { 288 289 CompositeType type = schemaManager.getFacet(facet); 290 if (type != null) { 291 292 final String transientSchemaName = DocumentRoutingConstants.GLOBAL_VAR_SCHEMA_PREFIX + node.getId(); 293 final Schema transientSchema = schemaManager.getSchema(transientSchemaName); 294 if (transientSchema == null) { 295 return; 296 } 297 298 boolean hasFacet = route.getDocument().hasFacet(facet); 299 300 Writer<Property> propertyWriter = registry.getWriter(ctx, Property.class, APPLICATION_JSON_TYPE); 301 // provides the current route to the property marshaller 302 try (Closeable resource = ctx.wrap() 303 .with(DocumentModelJsonWriter.ENTITY_TYPE, route.getDocument()) 304 .open()) { 305 for (Field f : type.getFields()) { 306 String name = f.getName().getLocalName(); 307 if (transientSchema.hasField(name)) { 308 Property property = hasFacet ? route.getDocument().getProperty(name) : null; 309 OutputStream out = new OutputStreamWithJsonWriter(jg); 310 jg.writeFieldName(name); 311 propertyWriter.write(property, Property.class, Property.class, APPLICATION_JSON_TYPE, out); 312 } 313 } 314 } 315 } 316 } 317 } 318}