/* * Copyright (C) 2015 Jan Pokorsky * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package cz.cas.lib.proarc.webapp.server.rest; import cz.cas.lib.proarc.common.config.AppConfiguration; import cz.cas.lib.proarc.common.config.AppConfigurationException; import cz.cas.lib.proarc.common.config.AppConfigurationFactory; import cz.cas.lib.proarc.common.config.CatalogConfiguration; import cz.cas.lib.proarc.common.fedora.DigitalObjectValidationException.ValidationResult; import cz.cas.lib.proarc.common.workflow.WorkflowException; import cz.cas.lib.proarc.common.workflow.WorkflowManager; import cz.cas.lib.proarc.common.workflow.model.Job; import cz.cas.lib.proarc.common.workflow.model.JobFilter; import cz.cas.lib.proarc.common.workflow.model.JobView; import cz.cas.lib.proarc.common.workflow.model.Material; import cz.cas.lib.proarc.common.workflow.model.MaterialFilter; import cz.cas.lib.proarc.common.workflow.model.MaterialView; import cz.cas.lib.proarc.common.workflow.model.Task; import cz.cas.lib.proarc.common.workflow.model.TaskFilter; import cz.cas.lib.proarc.common.workflow.model.TaskParameterFilter; import cz.cas.lib.proarc.common.workflow.model.TaskParameterView; import cz.cas.lib.proarc.common.workflow.model.TaskView; import cz.cas.lib.proarc.common.workflow.model.WorkflowModelConsts; import cz.cas.lib.proarc.common.workflow.profile.JobDefinition; import cz.cas.lib.proarc.common.workflow.profile.JobDefinitionView; import cz.cas.lib.proarc.common.workflow.profile.WorkflowDefinition; import cz.cas.lib.proarc.common.workflow.profile.WorkflowProfileConsts; import cz.cas.lib.proarc.common.workflow.profile.WorkflowProfiles; import cz.cas.lib.proarc.webapp.server.ServerMessages; import cz.cas.lib.proarc.webapp.shared.rest.WorkflowResourceApi; import java.math.BigDecimal; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; /** * It allows to manage workflow remotely. * * @author Jan Pokorsky */ @Path(WorkflowResourceApi.PATH) public class WorkflowResource { private static final Logger LOG = Logger.getLogger(WorkflowResource.class.getName()); private final SessionContext session; private final HttpHeaders httpHeaders; private final WorkflowManager workflowManager; private final WorkflowProfiles workflowProfiles; private final AppConfiguration appConfig; public WorkflowResource( @Context HttpHeaders httpHeaders, @Context HttpServletRequest httpRequest ) throws AppConfigurationException { this.session = SessionContext.from(httpRequest); this.httpHeaders = httpHeaders; this.workflowManager = WorkflowManager.getInstance(); this.workflowProfiles = WorkflowProfiles.getInstance(); this.appConfig = AppConfigurationFactory.getInstance().defaultInstance(); } @GET @Produces({MediaType.APPLICATION_JSON}) public SmartGwtResponse<JobView> getJob( @QueryParam(WorkflowModelConsts.JOB_FILTER_ID) BigDecimal id, @QueryParam(WorkflowModelConsts.JOB_FILTER_CREATED) List<String> created, @QueryParam(WorkflowModelConsts.JOB_FILTER_LABEL) String label, @QueryParam(WorkflowModelConsts.JOB_FILTER_MODIFIED) List<String> modified, @QueryParam(WorkflowModelConsts.JOB_FILTER_PRIORITY) Integer priority, @QueryParam(WorkflowModelConsts.JOB_FILTER_PROFILENAME) String profileName, @QueryParam(WorkflowModelConsts.JOB_FILTER_STATE) Job.State state, @QueryParam(WorkflowModelConsts.JOB_FILTER_OWNERID) BigDecimal userId, @QueryParam(WorkflowModelConsts.JOB_FILTER_OFFSET) int startRow, @QueryParam(WorkflowModelConsts.JOB_FILTER_SORTBY) String sortBy ) { int pageSize = 100; JobFilter filter = new JobFilter(); filter.setLocale(session.getLocale(httpHeaders)); filter.setMaxCount(pageSize); filter.setOffset(startRow); filter.setSortBy(sortBy); filter.setId(id); filter.setCreated(created); filter.setLabel(label); filter.setModified(modified); filter.setPriority(priority); filter.setProfileName(profileName); filter.setState(state); filter.setUserId(userId); try { List<JobView> jobs = workflowManager.findJob(filter); int resultSize = jobs.size(); int endRow = startRow + resultSize; int total = (resultSize != pageSize) ? endRow : endRow + 1; return new SmartGwtResponse<JobView>( SmartGwtResponse.STATUS_SUCCESS, startRow, endRow, total, jobs); } catch (Exception ex) { LOG.log(Level.SEVERE, null, ex); return SmartGwtResponse.asError(ex.getMessage()); } } /** * Creates a new workflow job. * @param profileName profile name * @param metadata MODS * @param catalogId catalog ID * @return the job */ @POST @Produces({MediaType.APPLICATION_JSON}) public SmartGwtResponse<JobView> addJob( @FormParam(WorkflowResourceApi.NEWJOB_PROFILE) String profileName, @FormParam(WorkflowResourceApi.NEWJOB_METADATA) String metadata, @FormParam(WorkflowResourceApi.NEWJOB_CATALOGID) String catalogId ) { if (metadata == null) { return SmartGwtResponse.asError(WorkflowResourceApi.NEWJOB_METADATA + " - missing value! "); } CatalogConfiguration catalog = appConfig.getCatalogs().findConfiguration(catalogId); if (catalog == null) { return SmartGwtResponse.asError(WorkflowResourceApi.NEWJOB_CATALOGID + " - invalid value! " + catalogId); } WorkflowDefinition profiles = workflowProfiles.getProfiles(); if (profiles == null) { return profileError(); } JobDefinition profile = workflowProfiles.getProfile(profiles, profileName); if (profile == null) { return SmartGwtResponse.asError(WorkflowResourceApi.NEWJOB_PROFILE + " - invalid value! " + profileName); } try { Job job = workflowManager.addJob(profile, metadata, catalog, session.getUser()); JobFilter filter = new JobFilter(); filter.setLocale(session.getLocale(httpHeaders)); filter.setId(job.getId()); List<JobView> views = workflowManager.findJob(filter); return new SmartGwtResponse<JobView>(views); } catch (WorkflowException ex) { return toError(ex, WorkflowResourceApi.NEWJOB_PROFILE + ":" + profileName + ", " + WorkflowResourceApi.NEWJOB_CATALOGID + ":" + catalogId + ", " + WorkflowResourceApi.NEWJOB_METADATA + ":\n" + metadata); } } @PUT @Produces({MediaType.APPLICATION_JSON}) public SmartGwtResponse<JobView> updateJob( @FormParam(WorkflowModelConsts.JOB_ID) BigDecimal id, @FormParam(WorkflowModelConsts.JOB_LABEL) String label, @FormParam(WorkflowModelConsts.JOB_NOTE) String note, @FormParam(WorkflowModelConsts.JOB_FINANCED) String financed, @FormParam(WorkflowModelConsts.JOB_OWNERID) BigDecimal userId, @FormParam(WorkflowModelConsts.JOB_PRIORITY) Integer priority, @FormParam(WorkflowModelConsts.JOB_STATE) Job.State state, @FormParam(WorkflowModelConsts.JOB_TIMESTAMP) long timestamp ) { if (id == null) { return SmartGwtResponse.asError("Missing job ID!"); } WorkflowDefinition profiles = workflowProfiles.getProfiles(); if (profiles == null) { return profileError(); } Job job = new Job(); job.setId(id); job.setFinanced(financed); job.setLabel(label); job.setNote(note); job.setOwnerId(userId); job.setPriority(priority != null ? priority : 2); job.setState(state); job.setTimestamp(new Timestamp(timestamp)); try { workflowManager.updateJob(job); JobFilter jobFilter = new JobFilter(); jobFilter.setId(id); jobFilter.setLocale(session.getLocale(httpHeaders)); List<JobView> result = workflowManager.findJob(jobFilter); return new SmartGwtResponse<JobView>(result); } catch (WorkflowException ex) { return toError(ex, null); } } @Path(WorkflowResourceApi.TASK_PATH) @GET @Produces({MediaType.APPLICATION_JSON}) public SmartGwtResponse<TaskView> getTask( @QueryParam(WorkflowModelConsts.TASK_FILTER_CREATED) List<String> created, @QueryParam(WorkflowModelConsts.TASK_FILTER_ID) BigDecimal id, @QueryParam(WorkflowModelConsts.TASK_FILTER_JOBID) BigDecimal jobId, @QueryParam(WorkflowModelConsts.TASK_FILTER_JOBLABEL) String jobLabel, @QueryParam(WorkflowModelConsts.TASK_FILTER_MODIFIED) List<String> modified, @QueryParam(WorkflowModelConsts.TASK_FILTER_PRIORITY) List<Integer> priority, @QueryParam(WorkflowModelConsts.TASK_FILTER_PROFILENAME) List<String> profileName, @QueryParam(WorkflowModelConsts.TASK_FILTER_STATE) List<Task.State> state, @QueryParam(WorkflowModelConsts.TASK_FILTER_OWNERID) List<BigDecimal> userId, @QueryParam(WorkflowModelConsts.TASK_FILTER_OFFSET) int startRow, @QueryParam(WorkflowModelConsts.TASK_FILTER_SORTBY) String sortBy ) { int pageSize = 100; TaskFilter filter = new TaskFilter(); filter.setLocale(session.getLocale(httpHeaders)); filter.setMaxCount(pageSize); filter.setOffset(startRow); filter.setSortBy(sortBy); filter.setCreated(created); filter.setId(id); filter.setJobId(jobId); filter.setJobLabel(jobLabel); filter.setModified(modified); filter.setPriority(priority); filter.setProfileName(profileName); filter.setState(state); filter.setUserId(userId); WorkflowDefinition workflow = workflowProfiles.getProfiles(); if (workflow == null) { return profileError(); } try { List<TaskView> tasks = workflowManager.tasks().findTask(filter, workflow); int resultSize = tasks.size(); int endRow = startRow + resultSize; int total = (resultSize != pageSize) ? endRow : endRow + 1; return new SmartGwtResponse<TaskView>( SmartGwtResponse.STATUS_SUCCESS, startRow, endRow, total, tasks); } catch (Exception ex) { LOG.log(Level.SEVERE, null, ex); return SmartGwtResponse.asError(ex.getMessage()); } } @Path(WorkflowResourceApi.TASK_PATH) @POST @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) @Produces({MediaType.APPLICATION_JSON}) public SmartGwtResponse<TaskView> addTask( @FormParam(WorkflowModelConsts.TASK_JOBID) BigDecimal jobId, @FormParam(WorkflowModelConsts.TASK_PROFILENAME) String taskName ) { if (jobId == null || taskName == null) { return SmartGwtResponse.asError("Invalid parameters!"); } WorkflowDefinition workflow = workflowProfiles.getProfiles(); if (workflow == null) { return profileError(); } try { Task updatedTask = workflowManager.tasks().addTask(jobId, taskName, workflow, session.getUser()); TaskFilter taskFilter = new TaskFilter(); taskFilter.setId(updatedTask.getId()); taskFilter.setLocale(session.getLocale(httpHeaders)); List<TaskView> result = workflowManager.tasks().findTask(taskFilter, workflow); return new SmartGwtResponse<TaskView>(result); } catch (WorkflowException ex) { return toError(ex, "jobId: " + jobId + ", taskName: " + taskName); } } @Path(WorkflowResourceApi.TASK_PATH) @PUT @Consumes({MediaType.APPLICATION_JSON}) @Produces({MediaType.APPLICATION_JSON}) public SmartGwtResponse<TaskView> updateTask(TaskUpdate task) { if (task == null) { return SmartGwtResponse.asError("No task!"); } WorkflowDefinition workflow = workflowProfiles.getProfiles(); if (workflow == null) { return profileError(); } try { Task updatedTask = workflowManager.tasks().updateTask(task, task.params, workflow); TaskFilter taskFilter = new TaskFilter(); taskFilter.setId(updatedTask.getId()); taskFilter.setLocale(session.getLocale(httpHeaders)); List<TaskView> result = workflowManager.tasks().findTask(taskFilter, workflow); return new SmartGwtResponse<TaskView>(result); } catch (WorkflowException ex) { return toError(ex, null); } } @XmlAccessorType(XmlAccessType.NONE) public static class TaskUpdate extends Task { @XmlElement(name = WorkflowModelConsts.TASK_PARAMETERS) public Map<String, Object> params; } @Path(WorkflowResourceApi.MATERIAL_PATH) @GET @Produces({MediaType.APPLICATION_JSON}) public SmartGwtResponse<MaterialView> getMaterial( @QueryParam(WorkflowModelConsts.MATERIALFILTER_ID) BigDecimal id, @QueryParam(WorkflowModelConsts.MATERIALFILTER_JOBID) BigDecimal jobId, @QueryParam(WorkflowModelConsts.MATERIALFILTER_TASKID) BigDecimal taskId, @QueryParam(WorkflowModelConsts.MATERIALFILTER_OFFSET) int startRow, @QueryParam(WorkflowModelConsts.MATERIALFILTER_SORTBY) String sortBy ) { int pageSize = 100; MaterialFilter filter = new MaterialFilter(); filter.setLocale(session.getLocale(httpHeaders)); filter.setMaxCount(pageSize); filter.setOffset(startRow); filter.setSortBy(sortBy); filter.setId(id); filter.setJobId(jobId); filter.setTaskId(taskId); try { List<MaterialView> mvs = workflowManager.findMaterial(filter); int resultSize = mvs.size(); int endRow = startRow + resultSize; int total = (resultSize != pageSize) ? endRow : endRow + 1; return new SmartGwtResponse<MaterialView>( SmartGwtResponse.STATUS_SUCCESS, startRow, endRow, total, mvs); } catch (Exception ex) { LOG.log(Level.SEVERE, null, ex); return SmartGwtResponse.asError(ex.getMessage()); } } @Path(WorkflowResourceApi.MATERIAL_PATH) @PUT @Consumes({MediaType.APPLICATION_JSON}) @Produces({MediaType.APPLICATION_JSON}) public SmartGwtResponse<MaterialView> updateMaterial( MaterialView mv ) { if (mv == null || mv.getId() == null) { return SmartGwtResponse.asError("Invalid parameters!"); } WorkflowDefinition workflow = workflowProfiles.getProfiles(); if (workflow == null) { return profileError(); } try { Material updateMaterial = workflowManager.updateMaterial(mv); MaterialFilter filter = new MaterialFilter(); filter.setLocale(session.getLocale(httpHeaders)); filter.setId(updateMaterial.getId()); filter.setJobId(mv.getJobId()); filter.setTaskId(mv.getTaskId()); List<MaterialView> result = workflowManager.findMaterial(filter); return new SmartGwtResponse<MaterialView>(result); } catch (WorkflowException ex) { return toError(ex, null); } } @Path(WorkflowResourceApi.PARAMETER_PATH) @GET @Produces({MediaType.APPLICATION_JSON}) public SmartGwtResponse<TaskParameterView> getParameter( @QueryParam(WorkflowModelConsts.PARAMETERPROFILE_TASKID) BigDecimal taskId ) { if (taskId == null) { return SmartGwtResponse.asError("taskId is required!"); } int pageSize = 100; TaskParameterFilter filter = new TaskParameterFilter(); filter.setLocale(session.getLocale(httpHeaders)); filter.setMaxCount(pageSize); filter.setOffset(0); filter.setTaskId(taskId); try { List<TaskParameterView> params = workflowManager.findParameter(filter); return new SmartGwtResponse<TaskParameterView>(params); } catch (Exception ex) { LOG.log(Level.SEVERE, null, ex); return SmartGwtResponse.asError(ex.getMessage()); } } /** * Gets workflow profiles defined with {@link WorkflowProfiles} * @param name a profile name filter * @param disabled an availability filter * @return the list of profiles */ @Path(WorkflowResourceApi.PROFILE_PATH) @Produces({MediaType.APPLICATION_JSON}) @GET public SmartGwtResponse<JobDefinitionView> getProfiles( @QueryParam(WorkflowProfileConsts.NAME) String name, @QueryParam(WorkflowProfileConsts.DISABLED) Boolean disabled ) { WorkflowDefinition workflowDefinition = workflowProfiles.getProfiles(); if (workflowDefinition == null) { return profileError(); } String lang = session.getLocale(httpHeaders).getLanguage(); ArrayList<JobDefinitionView> profiles = new ArrayList<JobDefinitionView>(); for (JobDefinition job : workflowDefinition.getJobs()) { if ((name == null || name.equals(job.getName())) && (disabled == null || disabled == job.isDisabled())) { profiles.add(new JobDefinitionView(job, lang)); } } return new SmartGwtResponse<JobDefinitionView>(profiles); } private <T> SmartGwtResponse<T> profileError() { return toError( new WorkflowException("Invalid workflow.xml!") .addInvalidXml(), null); } private <T> SmartGwtResponse<T> toError(WorkflowException ex, String log) { if (ex.getValidations().isEmpty()) { LOG.log(Level.SEVERE, log, ex); return SmartGwtResponse.asError(ex.getMessage()); } StringBuilder sb = new StringBuilder(); Locale locale = session.getLocale(httpHeaders); ServerMessages msgs = ServerMessages.get(locale); for (ValidationResult validation : ex.getValidations()) { String msg; try { msg = msgs.getFormattedMessage(validation.getBundleKey(), validation.getValues()); } catch (MissingResourceException mrex) { LOG.log(Level.WARNING, validation.getBundleKey(), mrex); msg = validation.getBundleKey(); } if (sb.length() > 0) { sb.append("<p>"); } sb.append(msg); // log only unexpected errors if (WorkflowException.UNEXPECTED_ERROR_MSG_MSG.equals(validation.getBundleKey())) { LOG.log(Level.SEVERE, log, ex); } } return SmartGwtResponse.asError(sb.toString()); } }