package com.qcadoo.mes.newstates; import static com.qcadoo.mes.states.constants.StateChangeStatus.IN_PROGRESS; import static com.qcadoo.mes.states.constants.StateChangeStatus.PAUSED; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.google.common.base.Optional; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.qcadoo.mes.basic.ShiftsService; import com.qcadoo.mes.states.StateChangeEntityDescriber; import com.qcadoo.mes.states.StateEnum; import com.qcadoo.mes.states.constants.StateChangeStatus; import com.qcadoo.mes.states.exception.AnotherChangeInProgressException; import com.qcadoo.mes.states.exception.StateTransitionNotAlloweException; import com.qcadoo.model.api.Entity; import com.qcadoo.model.api.exception.EntityRuntimeException; import com.qcadoo.model.api.search.SearchCriteriaBuilder; import com.qcadoo.model.api.search.SearchRestrictions; import com.qcadoo.model.api.validators.ErrorMessage; import com.qcadoo.model.api.validators.GlobalMessage; import com.qcadoo.plugin.api.PluginUtils; import com.qcadoo.plugin.api.RunIfEnabled; import com.qcadoo.security.api.SecurityService; import com.qcadoo.view.api.ComponentMessagesHolder; import com.qcadoo.view.api.ComponentState; import com.qcadoo.view.api.ViewDefinitionState; import com.qcadoo.view.api.components.FormComponent; import com.qcadoo.view.api.components.GridComponent; @Service @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) public class StateExecutorService { private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(StateExecutorService.class); @Autowired private ApplicationContext applicationContext; @Autowired private ShiftsService shiftsService; @Autowired private SecurityService securityService; private ComponentMessagesHolder componentMessagesHolder; private static final Logger LOGGER = Logger.getLogger(StateExecutorService.class); public <M extends StateService> void changeState(Class<M> serviceMarker, final ViewDefinitionState view, String[] args) { componentMessagesHolder = view; Optional<GridComponent> maybeGridComponent = view.tryFindComponentByReference("grid"); if (maybeGridComponent.isPresent()) { maybeGridComponent.get().getSelectedEntities().forEach(entity -> { entity = entity.getDataDefinition().getMasterModelEntity(entity.getId()); entity = changeState(serviceMarker, entity, args[0]); copyMessages(entity); }); } else { Optional<FormComponent> maybeForm = view.tryFindComponentByReference("form"); if (maybeForm.isPresent()) { FormComponent formComponent = maybeForm.get(); Entity entity = formComponent.getEntity().getDataDefinition().get(formComponent.getEntityId()); if (entity.isValid()) { entity = changeState(serviceMarker, entity, args[0]); formComponent.setEntity(entity); } } } } public <M extends StateService> Entity changeState(Class<M> serviceMarker, Entity entity, String targetState) { List<M> services = lookupChangeStateServices(serviceMarker); StateChangeEntityDescriber describer = services.stream().findFirst().get().getChangeEntityDescriber(); String sourceState = entity.getStringField(describer.getOwnerStateFieldName()); Entity stateChangeEntity = buildStateChangeEntity(describer, entity, sourceState, targetState); try { stateChangeEntity = saveStateChangeContext(entity, stateChangeEntity, describer, sourceState, targetState, StateChangeStatus.IN_PROGRESS); entity = performChangeState(services, entity, stateChangeEntity, describer); if (entity.isValid()) { copyMessages(entity); saveStateChangeEntity(stateChangeEntity, StateChangeStatus.SUCCESSFUL); message("states.messages.change.successful", ComponentState.MessageType.SUCCESS); LOG.info(String.format("Change state successful. Entity name : %S id : %d. Target state : %S", entity .getDataDefinition().getName(), entity.getId(), stateChangeEntity.getStringField(describer .getTargetStateFieldName()))); } else { saveStateChangeEntity(stateChangeEntity, StateChangeStatus.FAILURE); entity = rollbackStateChange(entity, sourceState); message("states.messages.change.failure", ComponentState.MessageType.FAILURE); LOG.info(String.format("Change state failure. Entity name : %S id : %d. Target state : %S", entity .getDataDefinition().getName(), entity.getId(), stateChangeEntity.getStringField(describer .getTargetStateFieldName()))); } } catch (EntityRuntimeException entityException) { copyMessages(entityException.getEntity(), entity); entity = rollbackStateChange(entity, sourceState); saveStateChangeEntity(stateChangeEntity, StateChangeStatus.FAILURE); message("states.messages.change.failure", ComponentState.MessageType.FAILURE); return entity; } catch (AnotherChangeInProgressException e) { entity = rollbackStateChange(entity, sourceState); saveStateChangeEntity(stateChangeEntity, StateChangeStatus.FAILURE); message("states.messages.change.failure", ComponentState.MessageType.FAILURE); message("states.messages.change.failure.anotherChangeInProgress", ComponentState.MessageType.FAILURE); LOG.info(String.format("Another state change in progress. Entity name : %S id : %d. Target state : %S", entity .getDataDefinition().getName(), entity.getId(), targetState)); } catch (StateTransitionNotAlloweException e) { entity = rollbackStateChange(entity, sourceState); saveStateChangeEntity(stateChangeEntity, StateChangeStatus.FAILURE); message("states.messages.change.failure", ComponentState.MessageType.FAILURE); message("states.messages.change.failure.transitionNotAllowed", ComponentState.MessageType.FAILURE); LOG.info(String.format("State change - transition not allowed. Entity name : %S id : %d. Target state : %S", entity .getDataDefinition().getName(), entity.getId(), targetState)); } catch (Exception exception) { entity = rollbackStateChange(entity, sourceState); saveStateChangeEntity(stateChangeEntity, StateChangeStatus.FAILURE); message("states.messages.change.failure", ComponentState.MessageType.FAILURE); message("states.messages.change.failure.internalServerError", ComponentState.MessageType.FAILURE); LOG.info(String.format("State change exception. Entity name : %S id : %d. Target state : %S", entity .getDataDefinition().getName(), entity.getId(), targetState)); LOG.warn("Can't perform state change", exception); } return entity; } private Entity saveStateChangeContext(Entity entity, Entity stateChangeEntity, StateChangeEntityDescriber describer, String _sourceState, String _targetState, StateChangeStatus status) { final StateEnum sourceState = describer.parseStateEnum(_sourceState); final StateEnum targetState = describer.parseStateEnum(_targetState); if (sourceState != null && !sourceState.canChangeTo(targetState)) { throw new StateTransitionNotAlloweException(sourceState, targetState); } checkForUnfinishedStateChange(describer, entity); stateChangeEntity = saveStateChangeEntity(stateChangeEntity, status); return stateChangeEntity; } @Transactional private <M extends StateService> Entity performChangeState(List<M> services, Entity entity, Entity stateChangeEntity, StateChangeEntityDescriber describer) { LOG.info(String.format("Change state. Entity name : %S id : %d. Target state : %S", entity.getDataDefinition().getName(), entity.getId(), stateChangeEntity.getStringField(describer.getTargetStateFieldName()))); if (!canChangeState(describer, entity, stateChangeEntity.getStringField(describer.getTargetStateFieldName()))) { entity.setNotValid(); return entity; } entity = hookOnValidate(entity, services, stateChangeEntity.getStringField(describer.getSourceStateFieldName()), stateChangeEntity.getStringField(describer.getTargetStateFieldName()), stateChangeEntity, describer); if (!entity.isValid()) { // copyErrorMessages(entity); return entity; } entity = changeState(entity, stateChangeEntity.getStringField(describer.getTargetStateFieldName())); entity = hookOnBeforeSave(entity, services, stateChangeEntity.getStringField(describer.getSourceStateFieldName()), stateChangeEntity.getStringField(describer.getTargetStateFieldName()), stateChangeEntity, describer); if (!entity.isValid()) { throw new EntityRuntimeException(entity); } entity = entity.getDataDefinition().save(entity); if (!hookOnAfterSave(entity, services, stateChangeEntity.getStringField(describer.getSourceStateFieldName()), stateChangeEntity.getStringField(describer.getTargetStateFieldName()), stateChangeEntity, describer)) { throw new EntityRuntimeException(entity); } return entity; } private Entity saveStateChangeEntity(final Entity stateChangeEntity, StateChangeStatus stateChangeStatus) { stateChangeEntity.setField("status", stateChangeStatus.getStringValue()); Entity savedStateChangeEntity = saveAndValidate(stateChangeEntity); return savedStateChangeEntity; } private Entity buildStateChangeEntity(StateChangeEntityDescriber describer, Entity owner, String sourceState, String targetState) { final Entity stateChangeEntity = describer.getDataDefinition().create(); final Entity shift = shiftsService.getShiftFromDateWithTime(new Date()); stateChangeEntity.setField(describer.getDateTimeFieldName(), new Date()); stateChangeEntity.setField(describer.getSourceStateFieldName(), sourceState); stateChangeEntity.setField(describer.getTargetStateFieldName(), targetState); stateChangeEntity.setField(describer.getShiftFieldName(), shift); stateChangeEntity.setField(describer.getWorkerFieldName(), securityService.getCurrentUserName()); stateChangeEntity.setField(describer.getPhaseFieldName(), 0); stateChangeEntity.setField(describer.getOwnerFieldName(), owner); return stateChangeEntity; } private <M extends StateService> boolean canChangeState(StateChangeEntityDescriber describer, Entity owner, String targetStateString) { final StateEnum sourceState = describer.parseStateEnum(owner.getStringField(describer.getOwnerStateFieldName())); final StateEnum targetState = describer.parseStateEnum(targetStateString); // TODO wrzucamy błąd do encji? if (sourceState != null && !sourceState.canChangeTo(targetState)) { return false; } return true; } private <M extends StateService> Entity hookOnValidate(Entity entity, Collection<M> services, String sourceState, String targetState, Entity stateChangeEntity, StateChangeEntityDescriber describer) { for (StateService service : services) { entity = service.onValidate(entity, sourceState, targetState, stateChangeEntity, describer); } return entity; } private Entity changeState(Entity entity, String targetState) { // TODO zawsze stan będzie w tym polu? entity.setField("state", targetState); return entity; } private Entity rollbackStateChange(Entity entity, String sourceState) { entity.setField("state", sourceState); return entity; } private <M extends StateService> Entity hookOnBeforeSave(Entity entity, Collection<M> services, String sourceState, String targetState, Entity stateChangeEntity, StateChangeEntityDescriber describer) { for (StateService service : services) { entity = service.onBeforeSave(entity, sourceState, targetState, stateChangeEntity, describer); } return entity; } private <M extends StateService> boolean hookOnAfterSave(Entity entity, Collection<M> services, String sourceState, String targetState, Entity stateChangeEntity, StateChangeEntityDescriber describer) { for (StateService service : services) { entity = service.onAfterSave(entity, sourceState, targetState, stateChangeEntity, describer); } return entity.isValid(); } private <M extends StateService> List<M> lookupChangeStateServices(Class<M> serviceMarker) { Map<String, M> stateServices = applicationContext.getBeansOfType(serviceMarker); List<M> services = new ArrayList<>(); for (M service : stateServices.values()) { if (serviceEnabled(service)) { services.add(service); } } AnnotationAwareOrderComparator.sort(services); return services; } public <M extends StateService> void buildInitial(Class<M> serviceMarker, Entity entity, String initialState) { List<M> services = lookupChangeStateServices(serviceMarker); StateChangeEntityDescriber describer = services.get(0).getChangeEntityDescriber(); Entity stateChangeEntity = buildStateChangeEntity(describer, entity, null, initialState); stateChangeEntity = saveStateChangeEntity(stateChangeEntity, StateChangeStatus.SUCCESSFUL); entity.setField(describer.getOwnerStateFieldName(), initialState); entity.setField(describer.getOwnerStateChangesFieldName(), Lists.newArrayList(stateChangeEntity)); } private <M extends Object & StateService> boolean serviceEnabled(M service) { RunIfEnabled runIfEnabled = service.getClass().getAnnotation(RunIfEnabled.class); if (runIfEnabled == null) { return true; } for (String pluginIdentifier : runIfEnabled.value()) { if (!PluginUtils.isEnabled(pluginIdentifier)) { return false; } } return true; } private void copyMessages(Entity entity, Entity mainEntity) { if (mainEntity != null && mainEntity.equals(entity)) { return; } if (componentMessagesHolder == null) { return; } for (ErrorMessage errorMessage : entity.getGlobalErrors()) { componentMessagesHolder.addMessage(errorMessage); } for (ErrorMessage errorMessage : entity.getErrors().values()) { componentMessagesHolder.addMessage(errorMessage); } for (GlobalMessage globalMessage : entity.getGlobalMessages()) { componentMessagesHolder.addMessage(globalMessage); } } private void copyMessages(Entity entity) { copyMessages(entity, null); } private Entity saveAndValidate(final Entity entity) { if (entity == null) { return null; } Entity saved = entity.getDataDefinition().save(entity); if (!saved.isValid()) { throw new RuntimeException(String.format("Error on save state entity: %s", saved.getErrors())); } return saved; } private void message(String msg, ComponentState.MessageType messageType) { if (componentMessagesHolder != null) { componentMessagesHolder.addMessage(msg, messageType); } } private void checkForUnfinishedStateChange(final StateChangeEntityDescriber describer, final Entity owner) { final String ownerFieldName = describer.getOwnerFieldName(); final String statusFieldName = describer.getStatusFieldName(); final Set<String> unfinishedStatuses = Sets.newHashSet(IN_PROGRESS.getStringValue(), PAUSED.getStringValue()); final SearchCriteriaBuilder searchCriteria = describer.getDataDefinition().find(); searchCriteria.createAlias(ownerFieldName, ownerFieldName); searchCriteria.add(SearchRestrictions.eq(ownerFieldName + ".id", owner.getId())); searchCriteria.add(SearchRestrictions.in(statusFieldName, unfinishedStatuses)); if (searchCriteria.list().getTotalNumberOfEntities() > 0) { throw new AnotherChangeInProgressException(); } } }