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