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 org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.opensingular.flow.core.entity.IEntityCategory; import org.opensingular.flow.core.entity.IEntityProcessDefinition; import org.opensingular.flow.core.entity.IEntityProcessInstance; import org.opensingular.flow.core.entity.IEntityProcessVersion; import org.opensingular.flow.core.entity.IEntityRoleDefinition; import org.opensingular.flow.core.entity.IEntityRoleInstance; import org.opensingular.flow.core.entity.IEntityTaskDefinition; import org.opensingular.flow.core.entity.IEntityTaskInstance; import org.opensingular.flow.core.entity.IEntityTaskVersion; import org.opensingular.flow.core.entity.IEntityVariableInstance; import org.opensingular.flow.core.property.MetaData; import org.opensingular.flow.core.property.MetaDataEnabled; import org.opensingular.flow.core.service.IPersistenceService; import org.opensingular.flow.core.service.IProcessDataService; import org.opensingular.flow.core.service.IProcessDefinitionEntityService; import org.opensingular.flow.core.variable.VarDefinitionMap; import org.opensingular.flow.core.variable.VarService; import org.opensingular.lib.commons.base.SingularException; import org.opensingular.lib.commons.net.Lnk; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; /** * <p> * Esta a classe responsvel por manter as definies de um dado processo. * </p> * * @param <I> * o tipo das instncias deste processo. * @author Daniel Bordin */ @SuppressWarnings({ "serial", "unchecked" }) public abstract class ProcessDefinition<I extends ProcessInstance> implements Comparable<ProcessDefinition<?>>, MetaDataEnabled { static final Logger logger = LoggerFactory.getLogger(ProcessDefinition.class); private final Class<I> processInstanceClass; private final String key; private String category; private String name; private FlowMap flowMap; private Integer entityVersionCod; private IProcessCreationPageStrategy creationPage; private VarDefinitionMap<?> variableDefinitions; private VarService variableService; private IProcessDataService<I> processDataService; private MetaData metaData; private final Map<String, ProcessScheduledJob> scheduledJobsByName = new HashMap<>(); private transient RefProcessDefinition serializableReference; /** * <p> * Instancia uma nova definio de processo do tipo informado. * </p> * * @param key * a chave do processo. * @param instanceClass * o tipo da instncia da definio a ser instanciada. */ protected ProcessDefinition(Class<I> instanceClass) { this(instanceClass, VarService.basic()); } /** * <p> * Instancia uma nova definio de processo do tipo informado. * </p> * * @param processInstanceClass * o tipo da instncia da definio a ser instanciada. * @param varService * o servio de consulta das definies de variveis. */ protected ProcessDefinition(Class<I> processInstanceClass, VarService varService) { if (!this.getClass().isAnnotationPresent(DefinitionInfo.class)) { throw new SingularFlowException("A definio de fluxo (classe " + getClass().getName() + ") deve ser anotada com " + DefinitionInfo.class.getName(), this); } String flowKey = this.getClass().getAnnotation(DefinitionInfo.class).value(); Objects.requireNonNull(flowKey, "key"); Objects.requireNonNull(processInstanceClass, "processInstanceClass"); if (getClass().getSimpleName().equalsIgnoreCase(flowKey)) { throw new SingularFlowException("O nome simples da classe do processo(" + getClass().getSimpleName() + ") no pode ser igual a chave definida em @DefinitionInfo.", this); } this.key = flowKey; this.processInstanceClass = processInstanceClass; this.variableService = varService; } /** * <p> * Retorna o tipo das instncias desta definio de processo. * </p> * * @return o tipo das instncias. */ public final Class<I> getProcessInstanceClass() { return processInstanceClass; } /** * Mtodo a ser implementado com a criao do fluxo do processo. */ @Nonnull protected abstract FlowMap createFlowMap(); /** * Retorna o {@link FlowMap} para esta definio de processo. */ @Nonnull public synchronized final FlowMap getFlowMap() { if (flowMap == null) { FlowMap novo = createFlowMap(); Objects.requireNonNull(novo); if (novo.getProcessDefinition() != this) { throw new SingularFlowException("Mapa com definiao trocada", this); } novo.verifyConsistency(); SFlowUtil.calculateTaskOrder(novo); flowMap = novo; } return flowMap; } /** * Retorna o servio de consulta das instncias deste tipo de processo. */ @Nonnull public IProcessDataService<I> getDataService() { if (processDataService == null) { processDataService = new ProcessDataServiceImpl<>(this); } return processDataService; } /** * <p> * Determina o servio de consulta das instncias deste tipo de processo. * </p> * @param processDataService */ protected void setProcessDataService(IProcessDataService<I> processDataService) { this.processDataService = processDataService; } /** * <p> * Retorna o servio de consulta das definies de variveis. * </p> * * @return o servio de consulta. */ protected final VarService getVarService() { variableService = variableService.deserialize(); return variableService; } /** * <p> * Retorna as definies de variveis deste processo. * </p> * * @return the variables */ public final VarDefinitionMap<?> getVariables() { if (variableDefinitions == null) { variableDefinitions = getVarService().newVarDefinitionMap(); } return variableDefinitions; } /** * <p> * Cria e adiciona um novo <i>job</i> ao agendador deste processo. * </p> * * @param impl * a implementao do <i>job</i>. * @param name * o nome do <i>job</i>. * @return o {@link ProcessScheduledJob} que encapsula o <i>job</i> criado. */ protected final ProcessScheduledJob addScheduledJob(Supplier<Object> impl, String name) { return addScheduledJob(name).call(impl); } /** * <p> * Cria e adiciona um novo <i>job</i> ao agendador deste processo. * </p> * * @param impl * a implementao do <i>job</i>. * @param name * o nome do <i>job</i>. * @return o {@link ProcessScheduledJob} que encapsula o <i>job</i> criado. */ protected final ProcessScheduledJob addScheduledJob(Runnable impl, String name) { return addScheduledJob(name).call(impl); } /** * <p> * Cria e adiciona um novo <i>job</i> sem implementao ao agendador deste * processo. * </p> * * @param name * o nome do <i>job</i>. * @return o {@link ProcessScheduledJob} que encapsula o <i>job</i> criado. */ protected final ProcessScheduledJob addScheduledJob(String name) { String jobName = StringUtils.trimToNull(name); ProcessScheduledJob scheduledJob = new ProcessScheduledJob(this, jobName); if (scheduledJobsByName.containsKey(jobName)) { throw new SingularFlowException("A Job with name '" + jobName + "' is already defined.", this); } scheduledJobsByName.put(jobName, scheduledJob); return scheduledJob; } @Nonnull final Collection<ProcessScheduledJob> getScheduledJobs() { return CollectionUtils.unmodifiableCollection(scheduledJobsByName.values()); } /** * <p> * Recupera a entidade persistente correspondente a esta definio de * processo. * </p> * * @return a entidade persistente. */ public synchronized final IEntityProcessVersion getEntityProcessVersion() { if (entityVersionCod == null) { try { IProcessDefinitionEntityService<?, ?, ?, ?, ?, ?, ?, ?> processEntityService = Flow.getConfigBean() .getProcessEntityService(); IEntityProcessVersion newVersion = processEntityService.generateEntityFor(this); IEntityProcessVersion oldVersion = newVersion.getProcessDefinition().getLastVersion(); if (processEntityService.isDifferentVersion(oldVersion, newVersion)) { entityVersionCod = getPersistenceService().saveProcessVersion(newVersion).getCod(); } else { entityVersionCod = oldVersion.getCod(); } } catch (Exception e) { throw new SingularFlowException(createErrorMsg("Erro ao criar entidade para o processo"), e); } } IEntityProcessVersion version = getPersistenceService().retrieveProcessVersionByCod(entityVersionCod); if (version == null) { entityVersionCod = null; throw new SingularFlowException(createErrorMsg(String.format( "Definicao demanda inconsistente com o BD: codigo '%d' no encontrado", entityVersionCod)), this); } return version; } public final IEntityProcessDefinition getEntityProcessDefinition() { return getEntityProcessVersion().getProcessDefinition(); } /** * <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) { return getFlowMap().getTask(taskDefinition); } public final Set<IEntityTaskDefinition> getEntityTasksDefinitionNotJava() { return getEntityProcessDefinition().getTaskDefinitions().stream().filter(t -> !t.getLastVersion().isJava()) .collect(Collectors.toSet()); } final @Nonnull IEntityTaskVersion getEntityTaskVersion(@Nonnull STask<?> task) { Objects.requireNonNull(task); IEntityTaskVersion version = getEntityProcessVersion().getTaskVersion(task.getAbbreviation()); if (version == null) { throw new SingularFlowException(createErrorMsg("Dados inconsistentes com o BD"), this); } return version; } /** * <p> * Retorna as entidades persistentes correspondentes s definies de * tarefas informadas. * </p> * * @param task * as definies informadas. * @return as entidades persistentes. */ public final List<IEntityTaskDefinition> getEntityTaskDefinition(ITaskDefinition... task) { return Arrays.stream(task).map(this::getEntityTaskDefinition).collect(Collectors.toList()); } /** * <p> * Retorna as entidades persistentes correspondentes s definies de * tarefas informadas. * </p> * * @param tasks * as definies informadas. * @return as entidades persistentes. */ public final List<IEntityTaskDefinition> getEntityTaskDefinition(Collection<? extends ITaskDefinition> tasks) { return tasks.stream().map(this::getEntityTaskDefinition).collect(Collectors.toList()); } /** * <p> * Retorna a entidade persistente correspondente tarefa informada. * </p> * * @param task * a tarefa informada. * @return a entidade persistente. */ public final IEntityTaskDefinition getEntityTaskDefinition(STask<?> task) { return getEntityTaskDefinitionOrException(task.getAbbreviation()); } /** * <p> * Retorna a entidade persistente correspondente definio de tarefa * informada. * </p> * * @param task * a definio informada. * @return a entidade persistente. */ public final IEntityTaskDefinition getEntityTaskDefinition(ITaskDefinition task) { return getEntityTaskDefinitionOrException(task.getKey()); } /** * <p> * Retorna a entidade persistente correspondente definio de tarefa com a * sigla informada. * </p> * * @param taskAbbreviation * a sigla da definio informada. * @return a entidade persistente; ou {@code null} caso no a encontre. */ public final IEntityTaskDefinition getEntityTaskDefinition(String taskAbbreviation) { return (taskAbbreviation == null) ? null : getEntityProcessDefinition().getTaskDefinition(taskAbbreviation); } /** * <p> * Retorna a entidade persistente correspondente definio de tarefa com a * sigla informada. * </p> * * @param taskAbbreviation * a sigla da definio informada. * @return a entidade persistente. * @throws SingularFlowException * caso a entidade no seja encontrada. */ public final IEntityTaskDefinition getEntityTaskDefinitionOrException(String taskAbbreviation) { IEntityTaskDefinition taskDefinition = getEntityTaskDefinition(taskAbbreviation); if (taskDefinition == null) { throw new SingularFlowException( createErrorMsg("Dados inconsistentes com o BD para a task sigla=" + taskAbbreviation), this); } return taskDefinition; } /** * <p> * Formata uma mensagem de erro. * </p> * <p> * <p> * A formatao da mensagem segue o seguinte padro: * </p> * <p> * * <pre> * "Processo MBPM '" + getName() + "': " + msg * </pre> * * @param msg * a mensagem a ser formatada. * @return a mensagem formatada. * @see #getName() */ protected final String createErrorMsg(String msg) { return "Processo MBPM '" + getName() + "': " + msg; } /** * <p>Retorna o nome deste processo.</p> * * @return o nome deste processo. */ public final String getName() { if (name == null) { logger.warn("!!! process definition name not set, using class simple name !!!"); name = this.getClass().getSimpleName(); } return name; } /** * <p>Retorna a chave deste processo.</p> * * @return a chave deste processo. */ public final String getKey() { return key; } /** * <p>Retorna a categoria deste processo.</p> * * @return a categoria deste processo. */ public final String getCategory() { if (category == null) { logger.warn("!!! process definition category not set, using class simple name !!!"); category = this.getClass().getSimpleName(); } return category; } /** * <p> * Retorna o <i>link resolver</i> deste processo para o usurio * especificado. * </p> * * @param user * o usurio especificado. * @return o <i>link resolver</i>. */ public final Lnk getCreatePageFor(SUser user) { return getCreationPageStrategy().getCreatePageFor(this, user); } /** * <p>Retorna o {@link IProcessCreationPageStrategy} deste processo.</p> * * @return o {@link IProcessCreationPageStrategy}. */ protected final IProcessCreationPageStrategy getCreationPageStrategy() { return creationPage; } /** * <p>Configura o {@link IProcessCreationPageStrategy} deste processo.</p> * * @param creationPage o {@link IProcessCreationPageStrategy}. */ protected final void setCreationPageStrategy(IProcessCreationPageStrategy creationPage) { this.creationPage = creationPage; } /** * <p> * Verifica se h um {@link IProcessCreationPageStrategy} configurado. * </p> * * @return {@code true} caso exista um {@link IProcessCreationPageStrategy} * configurado; {@code false} caso contrrio. */ public boolean isCreatedByUser() { return creationPage != null; } /** * <p> * Verifica se um {@link IProcessCreationPageStrategy} possa ser configurado * pelo usurio especificado. * </p> * * @param user * o usurio especificado. * @return {@code true} caso um {@link IProcessCreationPageStrategy} possa * ser configurado; {@code false} caso contrrio. */ public boolean canBeCreatedBy(SUser user) { return isCreatedByUser(); } /** * <p> * Gera uma sigla para esta definio de processo. * </p> * * @return a sigla gerada. */ protected String generateAbbreviation() { return getClass().getSimpleName(); } /** * <p> * Configura a categoria e nome desta definio de processo. * </p> * * @param category * a categoria. * @param name * o nome. */ protected final void setName(String category, String name) { this.category = category; this.name = name; } @Override @Nonnull public Optional<MetaData> getMetaDataOpt() { return Optional.ofNullable(metaData); } @Override public MetaData getMetaData() { if (metaData == null) { metaData = new MetaData(); } return metaData; } final <X extends IEntityTaskVersion> Set<X> convertToEntityTaskVersion(Stream<? extends STask<?>> stream) { return (Set<X>) stream.map(t -> getEntityTaskVersion(t)).collect(Collectors.toSet()); } /** * <p> * Retorna uma lista de instncias correspondentes s entidades fornecidas. * </p> * * @param demandas * as entidades fornecidas. * @return a lista de instncias. */ protected final List<I> convertToProcessInstance(List<? extends IEntityProcessInstance> demandas) { return demandas.stream().map(e -> convertToProcessInstance(e)).collect(Collectors.toList()); } /** * Retorna a instncia correspondente entidade fornecida. */ @Nonnull protected final I convertToProcessInstance(@Nonnull IEntityProcessInstance dadosInstancia) { Objects.requireNonNull(dadosInstancia); checkIfKeysAreCompatible(this, dadosInstancia); I novo = newUnbindedInstance(); novo.setInternalEntity(dadosInstancia); return novo; } /** Verifica se a entidade da instancia de processo pertence a definio de processo. Seno dispara Exception. */ private static void checkIfKeysAreCompatible(ProcessDefinition<?> definition, IEntityProcessInstance instance) { if (!instance.getProcessVersion().getProcessDefinition().getKey().equalsIgnoreCase(definition.getKey())) { throw new SingularFlowException("A instancia de processo com id " + instance.getCod() + " no pertence a definio de processo " + definition.getName(), definition); } } /** Verifica se a instancia de processo pertence a definio de processo. Seno dispara Exception. */ final void checkIfCompatible(ProcessInstance instance) { checkIfKeysAreCompatible(this, instance.getEntity()); if (!processInstanceClass.isInstance(instance)) { throw new SingularFlowException("A instancia de processo com id=" + instance.getFullId() + " deveria ser da classe " + processInstanceClass.getName() + " mas na verdade da classe " + instance.getClass().getName(), instance); } } /** * Retorna uma nova instncia vazia deste processo pronta para ser * configurada em um novo fluxo. */ @Nonnull public I newPreStartInstance() { I novo = newUnbindedInstance(); novo.setInternalEntity(createProcessInstance()); return novo; } public StartCall<I> prepareStartCall() { return new StartCall<I>(this, new RefStart(getFlowMap().getStart())); } @Nonnull private I newUnbindedInstance() { I novo; try { for (Constructor<?> c : getProcessInstanceClass().getDeclaredConstructors()) { if (c.getParameters().length == 0) { c.setAccessible(true); novo = (I) c.newInstance(); novo.setProcessDefinition(this); return novo; } } } catch (Exception e) { throw new SingularFlowException(e.getMessage(), e); } throw new SingularFlowException( createErrorMsg( "Construtor sem parametros ausente: " + getProcessInstanceClass().getSimpleName() + "()"), this); } final IEntityProcessInstance createProcessInstance() { IEntityTaskVersion initialState = getEntityTaskVersion(getFlowMap().getStart().getTask()); return getPersistenceService().createProcessInstance(getEntityProcessVersion(), initialState); } final IPersistenceService<IEntityCategory, IEntityProcessDefinition, IEntityProcessVersion, IEntityProcessInstance, IEntityTaskInstance, IEntityTaskDefinition, IEntityTaskVersion, IEntityVariableInstance, IEntityRoleDefinition, IEntityRoleInstance> getPersistenceService() { return (IPersistenceService<IEntityCategory, IEntityProcessDefinition, IEntityProcessVersion, IEntityProcessInstance, IEntityTaskInstance, IEntityTaskDefinition, IEntityTaskVersion, IEntityVariableInstance, IEntityRoleDefinition, IEntityRoleInstance>) Flow .getConfigBean().getPersistenceService(); } /** * Retorna uma referncia a definio atual que pode ser serializada e * deserializada sem implicar na serializao de toda definio do processo. * * @return referncia serializvel. */ protected RefProcessDefinition getSerializableReference() { if (serializableReference == null) { serializableReference = createStaticReference(getClass()); } return serializableReference; } private static RefProcessDefinition createStaticReference( final Class<? extends ProcessDefinition> processDefinitionClass) { // A criao da classe tem que ficar em um mtodo esttico de modo que // classe anmina no tenha uma referncia implicita a // ProcessDefinition, o que atrapalharia a serializao return new RefProcessDefinition() { @Override protected ProcessDefinition<?> reload() { return ProcessDefinitionCache.getDefinition(processDefinitionClass); } }; } @Override public int compareTo(ProcessDefinition<?> dp2) { int v = getCategory().compareTo(dp2.getCategory()); if (v == 0) { v = getName().compareTo(dp2.getName()); } return v; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ProcessDefinition<?> that = (ProcessDefinition<?>) o; return category.equals(that.category) && name.equals(that.name); } @Override public int hashCode() { int result; result = getCategory().hashCode(); result = 31 * result + getName().hashCode(); return result; } }