Java tutorial
/* * Copyright (C) 2016 Singular Studios (a.k.a Atom Tecnologia) - www.opensingular.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.opensingular.flow.core; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableSet; import org.apache.commons.collections.CollectionUtils; import org.opensingular.flow.core.entity.TransitionType; import org.opensingular.flow.core.property.MetaDataRef; import org.opensingular.flow.core.variable.VarService; import org.opensingular.lib.commons.base.SingularException; import javax.annotation.Nonnull; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * <p>Esta classe representa o mapa de fluxo de uma dada definio de processo.</p> * * @author Daniel Bordin */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class FlowMap { private final ProcessDefinition<?> processDefinition; private final Map<String, STask<?>> tasksByName = new HashMap<>(); private final Map<String, STask<?>> tasksByAbbreviation = new HashMap<>(); private final Map<String, STaskEnd> endTasks = new HashMap<>(); private final Map<String, SProcessRole> rolesByAbbreviation = new HashMap<>(); private SStart start; private IRoleChangeListener roleChangeListener; private final Map<String, DashboardView> dashboardViews = new LinkedHashMap<>(); /** * <p>Instancia um novo mapa para a definio de processo especificado.</p> * * @param processDefinition a definio de processo especificado. */ public FlowMap(ProcessDefinition<?> processDefinition) { this.processDefinition = processDefinition; } /** * <p>Ponto de extenso para customizaes. Cria uma nova transio com as caractersticas * informadas.</p> * * @param origin tarefa de origem. * @param name o nome da transio. * @param destinarion a tarefa destino. * @param type o tipo de transio. * @return a nova transio criada. */ protected STransition newTransition(STask<?> origin, String name, STask<?> destinarion, TransitionType type) { return new STransition(origin, name, destinarion, type); } /** * <p>Retorna as tarefas definidas neste mapa. Apenas tarefas que no so do tipo fim * ({@link TaskType#END}) so retornadas.</p> * * @return as tarefas definidas. */ @Nonnull public Collection<STask<?>> getTasks() { return tasksByName.values(); } /** * <p>Retorna todas as tarefas definidas neste mapa.</p> * * @return todas as tarefas definidas. */ @Nonnull public Collection<STask<?>> getAllTasks() { return CollectionUtils.union(getTasks(), getEndTasks()); } /** * <p>Retorna as tarefas definidas neste mapa do tipo {@link TaskType#PEOPLE}.</p> * * @return as tarefas definidas do tipo {@link TaskType#PEOPLE} ou uma lista vazia. */ @Nonnull public Collection<STaskPeople> getPeopleTasks() { return (Collection<STaskPeople>) getTasks(TaskType.PEOPLE); } /** * Retorna as tarefas definidas neste mapa do tipo {@link TaskType#JAVA}. * * @return as tarefas definidas do tipo {@link TaskType#JAVA} ou uma lista vazia. */ @Nonnull public Collection<STaskJava> getJavaTasks() { return (Collection<STaskJava>) getTasks(TaskType.JAVA); } /** * <p>Retorna as tarefas definidas neste mapa do tipo {@link TaskType#WAIT}.</p> * * @return as tarefas definidas do tipo {@link TaskType#WAIT} ou uma lista vazia. */ @Nonnull public Collection<STaskWait> getWaitTasks() { return (Collection<STaskWait>) getTasks(TaskType.WAIT); } /** * Retorna as tarefas definidas neste mapa do tipo especificado. * * @param IEntityTaskType o tipo especificado. * @return as tarefas definidas do tipo especificado ou uma lista vazia */ @Nonnull public Collection<? extends STask<?>> getTasks(IEntityTaskType IEntityTaskType) { final Builder<STask<?>> builder = ImmutableList.builder(); for (final STask sTask : getTasks()) { if (sTask.getTaskType() == IEntityTaskType) { builder.add(sTask); } } return builder.build(); } /** * <p>Retorna as tarefas definidas neste mapa do tipo fim ({@link TaskType#END}).</p> * * @return as tarefas definidas do tipo fim. */ @Nonnull public Collection<STaskEnd> getEndTasks() { return endTasks.values(); } /** * <p>Verifica se h um papel definido com a sigla especificada.</p> * * @param sigla a sigla especificada. * @return {@code true} caso exista; {@code false} caso contrrio. */ public boolean hasRoleWithAbbreviation(String sigla) { return rolesByAbbreviation.containsKey(sigla.toLowerCase()); } /** * <p>Retorna o papel definido com a sigla especificada.</p> * * @param abbreviation a sigla especificada. * @return o papel definido; {@code null} caso no haja papel com a sigla especificada. */ public SProcessRole getRoleWithAbbreviation(String abbreviation) { return rolesByAbbreviation.get(abbreviation.toLowerCase()); } /** * <p>Retorna os papeis definidos. A coleo retornada do tipo {@link ImmutableSet}.</p> * * @return todos os papeis definidos. */ public Collection<SProcessRole> getRoles() { return ImmutableSet.copyOf(rolesByAbbreviation.values()); } /** * <p>Adiciona um novo papel a este mapa.</p> * * @param name o nome do papel. * @param abbreviation a sigla do papel. * @param userRoleSettingStrategy o {@link UserRoleSettingStrategy} do papel. * @param automaticUserAllocation indicador de alocao automtica. * @return o papel adicionado ao mapa. */ public SProcessRole addRoleDefinition(String name, String abbreviation, UserRoleSettingStrategy<? extends ProcessInstance> userRoleSettingStrategy, boolean automaticUserAllocation) { final SProcessRole processRole = new SProcessRole(name, abbreviation, userRoleSettingStrategy, automaticUserAllocation); if (hasRoleWithAbbreviation(processRole.getAbbreviation())) { throw new SingularFlowException( "Role with abbreviation '" + processRole.getAbbreviation() + "' already defined", this); } rolesByAbbreviation.put(processRole.getAbbreviation().toLowerCase(), processRole); return processRole; } /** * <p>Registra um <i>listener</i> para mudaas de papel.</p> * * @param <T> o tipo deste mapa de fluxo. * @param roleChangeListener o <i>listener</i> do tipo {@link IRoleChangeListener}. * @return este mapa com o <i>listener</i> registrado. */ public <T extends ProcessInstance> FlowMap setRoleChangeListener(IRoleChangeListener<T> roleChangeListener) { this.roleChangeListener = roleChangeListener; return this; } /** * <p>Notifica mudana de papel. Internamente notifica o <i>listener</i> registrado, caso exista.</p> * * @param instance a instncia de processo. * @param role o papel. * @param previousUser o usurio anteriormente atribudo ao papel. * @param newUser o novo usurio atribudo ao papel. */ public void notifyRoleChange(final ProcessInstance instance, final SProcessRole role, SUser previousUser, SUser newUser) { if (roleChangeListener != null) { roleChangeListener.execute(instance, role, previousUser, newUser); } } /** * <p>Adiciona uma nova tarefa.</p> * * @param task a tarefa para adicionar. * @return a tarefa adicionada. */ protected <T extends STask> T addTask(T task) { String name = task.getName(); String abbreviation = task.getAbbreviation(); if (tasksByAbbreviation.containsKey(abbreviation)) { throw new SingularFlowException("Task with abbreviation '" + abbreviation + "' already defined", this); } if (tasksByName.containsKey(name)) { throw new SingularFlowException("Task with name '" + name + "' already defined", this); } tasksByName.put(name, task); tasksByAbbreviation.put(abbreviation, task); return task; } /** * <p>Cria e adiciona uma nova tarefa do tipo {@link TaskType#PEOPLE}.</p> * * @param definition a definio da tarefa. * @return a nova tarefa criada e adicionada. */ public STaskPeople addPeopleTask(ITaskDefinition definition) { return addTask(new STaskPeople(this, definition.getName(), definition.getKey())); } /** * <p>Cria e adiciona uma nova tarefa do tipo {@link TaskType#JAVA}.</p> * * @param definition a definio da tarefa. * @return a nova tarefa criada e adicionada. */ public STaskJava addJavaTask(ITaskDefinition definition) { return addTask(new STaskJava(this, definition.getName(), definition.getKey())); } /** * <p>Cria e adiciona uma nova tarefa do tipo {@link TaskType#WAIT}.</p> * * @param definition a definio da tarefa. * @return a nova tarefa criada e adicionada. */ public STaskWait addWaitTask(ITaskDefinition definition) { return addWaitTask(definition, null); } /** * <p>Cria e adiciona uma nova tarefa do tipo {@link TaskType#WAIT}.</p> * * <p>Configura a estratgia de execuo conforme a especificada ({@link IExecutionDateStrategy}). * Isso define a data alvo de uma instncia desta tarefa.</p> * * @param <T> o tipo da instncia de processo. * @param definition a definio da tarefa. * @param dateExecutionStrategy a estratgia de execuo. * @return a nova tarefa criada e adicionada. */ public <T extends ProcessInstance> STaskWait addWaitTask(ITaskDefinition definition, IExecutionDateStrategy<T> dateExecutionStrategy) { return addTask(new STaskWait(this, definition.getName(), definition.getKey(), dateExecutionStrategy)); } /** * <p>Seleciona a tarefa inicial deste mapa.</p> * * @param initialTask a definio da tarefa que corresponde inicial. * @return a tarefa inicial. */ public SStart setStart(ITaskDefinition initialTask) { return setStart(getTask(initialTask)); } /** * <p>Seleciona a tarefa inicial deste mapa.</p> * * @param task a tarefa inicial. * @return a tarefa inicial. */ public SStart setStart(STask<?> task) { Objects.requireNonNull(task); if (task.getFlowMap() != this) { throw new SingularFlowException("The task does not belong to this flow", this); } else if (start != null) { throw new SingularFlowException("The start point is already setted", this); } start = new SStart(task); return start; } /** * <p>Retorna a tarefa inicial deste mapa.</p> * * @return a tarefa inicial. */ public SStart getStart() { if (start == null) { throw new SingularFlowException("Task inicial no definida no processo", this); } return start; } /** * <p>Retorna a definio de processo deste mapa.</p> * * @return a definio de processo. */ public ProcessDefinition<?> getProcessDefinition() { return processDefinition; } /** * <p>Cria e adiciona uma nova tarefa do tipo fim ({@link TaskType#END}).</p> * * @param definition a definio da tarefa. * @return a nova tarefa criada e adicionada. */ public STaskEnd addEnd(ITaskDefinition definition) { Objects.requireNonNull(definition.getKey()); Objects.requireNonNull(definition.getName()); if (endTasks.containsKey(definition.getName())) { throw new SingularFlowException("End task '" + definition.getName() + "' already defined", this); } final STaskEnd fim = new STaskEnd(this, definition.getName(), definition.getKey()); endTasks.put(definition.getName(), fim); tasksByAbbreviation.put(fim.getAbbreviation(), fim); return fim; } /** * <p>Retorna a tarefa deste mapa com a sigla especificada.</p> * * @param abbreviation a sigla especificada. * @return a tarefa deste mapa com a sigla especificada; ou {@code null} caso no a encontre. */ public Optional<STask<?>> getTaskByAbbreviation(String abbreviation) { return Optional.ofNullable(tasksByAbbreviation.get(abbreviation)); } /** * <p>Retorna a tarefa deste mapa com a sigla especificada.</p> * * @param abbreviation a sigla especificada. * @return a tarefa deste mapa com a sigla especificada. * @throws SingularFlowException caso no encontre tarefa com a sigla especificada. */ public STask<?> getTaskByAbbreviationOrException(String abbreviation) { return getTaskByAbbreviation(abbreviation).orElseThrow( () -> new SingularFlowException("Task with abbreviation '" + abbreviation + "' not found", this)); } /** * <p>Retorna a tarefa do tipo {@link TaskType#PEOPLE} deste mapa com a sigla especificada.</p> * * @param abbreviation a sigla especificada. * @return a tarefa deste mapa com a sigla especificada; ou {@code null} caso no a encontre. */ public Optional<STaskPeople> getPeopleTaskByAbbreviation(String abbreviation) { return getTaskByAbbreviation(abbreviation).map(task -> castCheck(task, STaskPeople.class, abbreviation)); } /** * <p>Retorna a tarefa do tipo {@link TaskType#PEOPLE} deste mapa com a sigla especificada.</p> * * @param abbreviation a sigla especificada. * @return a tarefa deste mapa com a sigla especificada. * @throws SingularFlowException caso no encontre tarefa com a sigla especificada. */ public STaskPeople getPeopleTaskByAbbreviationOrException(String abbreviation) { return castCheck(getTaskByAbbreviationOrException(abbreviation), STaskPeople.class, abbreviation); } private <T extends STask> T castCheck(STask<?> target, Class<T> expectedClass, String abbreviation) { if (target == null) { return null; } else if (expectedClass.isInstance(target)) { return expectedClass.cast(target); } throw new SingularFlowException("Task with abbreviation '" + abbreviation + "' found, but it is of type " + target.getClass().getName() + " and was expected to be " + expectedClass.getName(), this); } /** * <p>Encontra a definio da tarefa informada ou dispara uma exceo caso no a encontre.</p> * * @param taskDefinition a definio informada. * @return a definio da tarefa informada. * @throws SingularException caso no encontre a tarefa. */ public STask<?> getTask(ITaskDefinition taskDefinition) { STask<?> task = getTaskWithName(taskDefinition.getName()); if (task == null) { throw new SingularFlowException( "Task " + taskDefinition.getKey() + " no encontrada em " + getProcessDefinition().getKey(), this); } return task; } /** * <p>Retorna a tarefa deste mapa com o none especificado.</p> * * @param name o nome especificado. * @return a tarefa deste mapa com o nome especificado; ou {@code null} caso no a encontre. */ public STask<?> getTaskWithName(String name) { if (tasksByName.containsKey(name)) { return tasksByName.get(name); } return endTasks.get(name); } public List<STask<?>> getTasksWithMetadata(MetaDataRef ref) { return (List<STask<?>>) getAllTasks().stream().filter(t -> t.getMetaData().get(ref) != null) .collect(Collectors.toList()); } /** * <p>Verifica a consistncia deste mapa.</p> * * <p>Um mapa considerado consistente caso passe nos seguintes testes:</p> * <ul> * <li>Cada tarefa definida neste mapa consistente</li> * <li>A tarefa inicial foi selecionada</li> * <li>Todas as transies levam a uma tarefa vlida</li> * </ul> */ public void verifyConsistency() { verifyTasksConsistency(); if (start == null) { throw new SingularFlowException("There is no initial task set", this); } checkRouteToTheEnd(); } private void verifyTasksConsistency() { tasksByAbbreviation.values().stream().forEach(STask::verifyConsistency); } @SuppressWarnings("StatementWithEmptyBody") private void checkRouteToTheEnd() { final Set<STask<?>> tasks = new HashSet<>(tasksByName.values()); while (removeIfReachesTheEnd(tasks)) { /* CORPO VAZIO */ } if (!tasks.isEmpty()) { throw new SingularFlowException( "The following tasks have no way to reach the end: " + joinTaskNames(tasks), this); } } private static boolean removeIfReachesTheEnd(Set<STask<?>> tasks) { return tasks.removeIf((task) -> task.getTransitions().stream() .anyMatch((transition) -> transition.getDestination().isEnd() || !tasks.contains(transition.getDestination()))); } private static String joinTaskNames(Set<STask<?>> tasks) { return tasks.stream().map(STask::getName).collect(Collectors.joining(", ")); } /** * <p>Retorna o servio de consulta das definies de variveis.</p> * * @return o servio de consulta. */ protected VarService getVarService() { return processDefinition.getVarService(); } @Override public String toString() { return "FlowMap [processDefinition=" + processDefinition.getName() + "]"; } public void addDashboardView(DashboardView dashboardView) { dashboardViews.put(dashboardView.getName(), dashboardView); } public List<DashboardView> getDashboardViews() { return new ArrayList<>(dashboardViews.values()); } public DashboardView getDashboardViewWithName(String name) { return dashboardViews.get(name); } @Nonnull public <T extends Serializable> FlowMap setMetaDataValue(@Nonnull MetaDataRef<T> propRef, T value) { getProcessDefinition().setMetaDataValue(propRef, value); return this; } }