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 public void writeEntityBody(Task item, JsonGenerator jg) throws IOException { 098 GraphRoute workflowInstance = null; 099 GraphNode node = null; 100 String workflowInstanceId = item.getProcessId(); 101 final String nodeId = item.getVariable(DocumentRoutingConstants.TASK_NODE_ID_KEY); 102 try (SessionWrapper wrapper = ctx.getSession(item.getDocument())) { 103 if (StringUtils.isNotBlank(workflowInstanceId)) { 104 NodeAccessRunner nodeAccessRunner = new NodeAccessRunner(wrapper.getSession(), workflowInstanceId, 105 nodeId); 106 nodeAccessRunner.runUnrestricted(); 107 workflowInstance = nodeAccessRunner.getWorkflowInstance(); 108 node = nodeAccessRunner.getNode(); 109 } 110 111 jg.writeStringField("id", item.getDocument().getId()); 112 jg.writeStringField("name", item.getName()); 113 jg.writeStringField("workflowInstanceId", workflowInstanceId); 114 if (workflowInstance != null) { 115 jg.writeStringField("workflowModelName", workflowInstance.getModelName()); 116 } 117 jg.writeStringField("state", item.getDocument().getCurrentLifeCycleState()); 118 jg.writeStringField("directive", item.getDirective()); 119 jg.writeStringField("created", DateParser.formatW3CDateTime(item.getCreated())); 120 jg.writeStringField("dueDate", DateParser.formatW3CDateTime(item.getDueDate())); 121 jg.writeStringField("nodeName", item.getVariable(DocumentRoutingConstants.TASK_NODE_ID_KEY)); 122 123 jg.writeArrayFieldStart(TARGET_DOCUMENT_IDS); 124 final boolean isFetchTargetDocumentIds = ctx.getFetched(ENTITY_TYPE).contains(FETCH_TARGET_DOCUMENT); 125 for (String docId : item.getTargetDocumentsIds()) { 126 if (isFetchTargetDocumentIds) { 127 IdRef idRef = new IdRef(docId); 128 if (wrapper.getSession().exists(idRef)) { 129 writeEntity(wrapper.getSession().getDocument(idRef), jg); 130 break; 131 } 132 } 133 jg.writeStartObject(); 134 jg.writeStringField("id", docId); 135 jg.writeEndObject(); 136 } 137 jg.writeEndArray(); 138 139 jg.writeArrayFieldStart("actors"); 140 final boolean isFetchActors = ctx.getFetched(ENTITY_TYPE).contains(FETCH_ACTORS); 141 for (String actorId : item.getActors()) { 142 if (isFetchActors) { 143 if (actorId.startsWith(USER_PREFIX + SEPARATOR)) { 144 actorId = actorId.substring(USER_PREFIX.length() + SEPARATOR.length()); 145 NuxeoPrincipal user = userManager.getPrincipal(actorId); 146 if (user != null) { 147 writeEntity(user, jg); 148 continue; 149 } 150 } else if (actorId.startsWith(GROUP_PREFIX + SEPARATOR)) { 151 actorId = actorId.substring(GROUP_PREFIX.length() + SEPARATOR.length()); 152 NuxeoGroup group = userManager.getGroup(actorId); 153 if (group != null) { 154 writeEntity(group, jg); 155 continue; 156 } 157 } else { 158 NuxeoPrincipal user = userManager.getPrincipal(actorId); 159 if (user != null) { 160 writeEntity(user, jg); 161 continue; 162 } else { 163 NuxeoGroup group = userManager.getGroup(actorId); 164 if (group != null) { 165 writeEntity(group, jg); 166 continue; 167 } 168 } 169 } 170 } 171 jg.writeStartObject(); 172 jg.writeStringField("id", actorId); 173 jg.writeEndObject(); 174 } 175 jg.writeEndArray(); 176 177 jg.writeArrayFieldStart("comments"); 178 for (TaskComment comment : item.getComments()) { 179 jg.writeStartObject(); 180 jg.writeStringField("author", comment.getAuthor()); 181 jg.writeStringField("text", comment.getText()); 182 jg.writeStringField("date", DateParser.formatW3CDateTime(comment.getCreationDate().getTime())); 183 jg.writeEndObject(); 184 } 185 jg.writeEndArray(); 186 187 jg.writeFieldName("variables"); 188 jg.writeStartObject(); 189 // add nodeVariables 190 writeTaskVariables(node, jg, registry, ctx, schemaManager); 191 // add workflow variables 192 if (workflowInstance != null) { 193 writeWorkflowVariables(workflowInstance, node, jg, registry, ctx, schemaManager); 194 } 195 jg.writeEndObject(); 196 197 jg.writeFieldName("taskInfo"); 198 jg.writeStartObject(); 199 final ActionManager actionManager = Framework.getService(ActionManager.class); 200 jg.writeArrayFieldStart("taskActions"); 201 for (Button button : node.getTaskButtons()) { 202 if (StringUtils.isBlank(button.getFilter()) 203 || actionManager.checkFilter(button.getFilter(), createActionContext(wrapper.getSession()))) { 204 jg.writeStartObject(); 205 jg.writeStringField("name", button.getName()); 206 jg.writeStringField("url", 207 ctx.getBaseUrl() + "api/v1/task/" + item.getDocument().getId() + "/" + button.getName()); 208 jg.writeStringField("label", button.getLabel()); 209 jg.writeEndObject(); 210 } 211 } 212 jg.writeEndArray(); 213 214 jg.writeFieldName("layoutResource"); 215 jg.writeStartObject(); 216 jg.writeStringField("name", node.getTaskLayout()); 217 jg.writeStringField("url", ctx.getBaseUrl() + "site/layout-manager/layouts/?layoutName=" + node.getTaskLayout()); 218 jg.writeEndObject(); 219 220 jg.writeArrayFieldStart("schemas"); 221 for (String schema : node.getDocument().getSchemas()) { 222 // TODO only keep functional schema once adaptation done 223 jg.writeStartObject(); 224 jg.writeStringField("name", schema); 225 jg.writeStringField("url", ctx.getBaseUrl() + "api/v1/config/schemas/" + schema); 226 jg.writeEndObject(); 227 } 228 jg.writeEndArray(); 229 230 jg.writeEndObject(); 231 } 232 233 } 234 235 protected static ActionContext createActionContext(CoreSession session) { 236 ActionContext actionContext = new ELActionContext(); 237 actionContext.setDocumentManager(session); 238 actionContext.setCurrentPrincipal((NuxeoPrincipal) session.getPrincipal()); 239 return actionContext; 240 } 241 242 /** 243 * @since 8.3 244 */ 245 public static void writeTaskVariables(GraphNode node, JsonGenerator jg, MarshallerRegistry registry, 246 RenderingContext ctx, SchemaManager schemaManager) throws IOException, JsonGenerationException { 247 String facet = (String) node.getDocument().getPropertyValue(GraphNode.PROP_VARIABLES_FACET); 248 if (StringUtils.isNotBlank(facet)) { 249 250 CompositeType type = schemaManager.getFacet(facet); 251 if (type != null) { 252 boolean hasFacet = node.getDocument().hasFacet(facet); 253 254 Writer<Property> propertyWriter = registry.getWriter(ctx, Property.class, APPLICATION_JSON_TYPE); 255 // provides the current route to the property marshaller 256 try (Closeable resource = ctx.wrap().with(DocumentModelJsonWriter.ENTITY_TYPE, 257 node.getDocument()).open()) { 258 for (Field f : type.getFields()) { 259 String name = f.getName().getLocalName(); 260 Property property = hasFacet ? node.getDocument().getProperty(name) : null; 261 OutputStream out = new OutputStreamWithJsonWriter(jg); 262 jg.writeFieldName(name); 263 propertyWriter.write(property, Property.class, Property.class, APPLICATION_JSON_TYPE, out); 264 } 265 } 266 } 267 } 268 } 269 270 /** 271 * @since 8.3 272 */ 273 public static void writeWorkflowVariables(DocumentRoute route, GraphNode node, JsonGenerator jg, 274 MarshallerRegistry registry, RenderingContext ctx, SchemaManager schemaManager) 275 throws IOException, JsonGenerationException { 276 String facet = (String) route.getDocument().getPropertyValue(GraphRoute.PROP_VARIABLES_FACET); 277 if (StringUtils.isNotBlank(facet)) { 278 279 CompositeType type = schemaManager.getFacet(facet); 280 if (type != null) { 281 282 final String transientSchemaName = DocumentRoutingConstants.GLOBAL_VAR_SCHEMA_PREFIX + node.getId(); 283 final Schema transientSchema = schemaManager.getSchema(transientSchemaName); 284 if (transientSchema == null) { 285 return; 286 } 287 288 boolean hasFacet = route.getDocument().hasFacet(facet); 289 290 Writer<Property> propertyWriter = registry.getWriter(ctx, Property.class, APPLICATION_JSON_TYPE); 291 // provides the current route to the property marshaller 292 try (Closeable resource = ctx.wrap().with(DocumentModelJsonWriter.ENTITY_TYPE, 293 route.getDocument()).open()) { 294 for (Field f : type.getFields()) { 295 String name = f.getName().getLocalName(); 296 if (transientSchema.hasField(name)) { 297 Property property = hasFacet ? route.getDocument().getProperty(name) : null; 298 OutputStream out = new OutputStreamWithJsonWriter(jg); 299 jg.writeFieldName(name); 300 propertyWriter.write(property, Property.class, Property.class, APPLICATION_JSON_TYPE, out); 301 } 302 } 303 } 304 } 305 } 306 } 307}