org.opensingular.flow.core.FlowMap.java Source code

Java tutorial

Introduction

Here is the source code for org.opensingular.flow.core.FlowMap.java

Source

/*
 * 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;
    }
}