diff --git a/src/main/java/org/caosdb/server/entity/ClientMessage.java b/src/main/java/org/caosdb/server/entity/ClientMessage.java index 22f257da013541584ad592d717fe7f5663f3baea..19fe9675a4dd0190304cd3164eb9fa4a40a6312f 100644 --- a/src/main/java/org/caosdb/server/entity/ClientMessage.java +++ b/src/main/java/org/caosdb/server/entity/ClientMessage.java @@ -41,4 +41,14 @@ public class ClientMessage extends Message { public String getProperty(String key) { return properties.get(key); } + + @Override + public String toString() { + return this.type + " - " + this.properties.toString(); + } + + @Override + public int hashCode() { + return type.hashCode() + this.properties.hashCode(); + } } diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java index 76e6ba5d4e3c2b02299715cae681c48083ff97c4..e88bff24c28fb5151e35f2593450065dd637e10c 100644 --- a/src/main/java/org/caosdb/server/jobs/Job.java +++ b/src/main/java/org/caosdb/server/jobs/Job.java @@ -225,14 +225,6 @@ public abstract class Job { } } - // @Override - // public boolean notifyObserver(final String e, final Observable o) { - // if (getEntity().getEntityStatus() != EntityStatus.UNQUALIFIED) { - // getTransaction().getSchedule().runJob(this); - // } - // return true; - // } - static HashMap<String, Class<? extends Job>> allClasses = null; private static List<Class<? extends Job>> loadAlways; diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java b/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java index 65df0fefa425617de6fcaf568782a8922a186b31..642d63bc4a091538bf6e8d4747256e833e0f51c0 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java @@ -16,28 +16,41 @@ public class CheckStateTransition extends EntityStateJob { private static final Message TRANSITION_NOT_ALLOWED = new Message(MessageType.Error, "Transition not allowed."); + private static final Message INITIAL_STATE_NOT_ALLOWED = + new Message(MessageType.Error, "Initial state not allowed."); + private static final Message FINAL_STATE_NOT_ALLOWED = + new Message(MessageType.Error, "Final state not allowed."); + private static final String FORCE_FINAL_STATE = "forceFinalState"; @Override protected void run() { try { + State nextState = getState(); + if (nextState != null) { + checkStateValid(nextState); + } if (getEntity() instanceof UpdateEntity) { - StateMessage toState = getStateMessage(); - StateMessage fromState = getStateMessage(((UpdateEntity) getEntity()).getOriginal()); - checkStateTransition(fromState, toState); + State fromState = getState(((UpdateEntity) getEntity()).getOriginal()); + checkStateTransition(fromState, nextState); } else if (getEntity() instanceof DeleteEntity) { - StateMessage finalState = getStateMessage(); - checkFinalState(finalState); + if (nextState != null) checkFinalState(nextState); } else { - StateMessage initialState = getStateMessage(); - checkInitialState(initialState); + if (nextState != null) checkInitialState(nextState); } } catch (Message m) { getEntity().addError(m); } } + private void checkStateValid(State state) throws Message { + if (state.isFinal() || state.isInitial() || state.getStateModel().getStates().contains(state)) { + return; + } + throw STATE_NOT_IN_STATE_MODEL; + } + /** Check if state is valid and transition is allowed */ - private void checkStateTransition(StateMessage fromState, StateMessage toState) throws Message { + private void checkStateTransition(State fromState, State toState) throws Message { if (fromState == null && toState == null) { return; } else if (fromState == null && toState != null) { @@ -50,32 +63,44 @@ public class CheckStateTransition extends EntityStateJob { StateModel stateModel = findMatchingStateModel(fromState, toState); if (stateModel == null) { + // change from one stateModel to another checkInitialState(toState); checkFinalState(fromState); return; } for (Transition t : stateModel.getTransitions()) { - if (t.fromStatesInclude(fromState.getState()) && t.toStatesInclude(toState.getState())) { + if (t.fromStatesInclude(fromState) && t.toStatesInclude(toState)) { + // TODO permissions return; } } throw TRANSITION_NOT_ALLOWED; } - private StateModel findMatchingStateModel(StateMessage fromState, StateMessage toState) - throws Message { + private StateModel findMatchingStateModel(State fromState, State toState) throws Message { if (fromState.getStateModel().equals(toState.getStateModel())) { return fromState.getStateModel(); } return null; } - private void checkFinalState(StateMessage fromState) { - // TODO Auto-generated method stub + private void checkFinalState(State fromState) throws Message { + if (!fromState.isFinal()) { + if ("true" + .equalsIgnoreCase(getTransaction().getContainer().getFlags().get(FORCE_FINAL_STATE))) { + // TODO permissions + return; + } + throw FINAL_STATE_NOT_ALLOWED; + } + // TODO permissions } - private void checkInitialState(StateMessage toState) { - // TODO Auto-generated method stub + private void checkInitialState(State toState) throws Message { + if (!toState.isInitial()) { + throw INITIAL_STATE_NOT_ALLOWED; + } + // TODO permissions } } diff --git a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java index 32f1e0193102430e4923c96c9ceb7fa2099b52da..6137f72fb199aa45a354449199962fa0cea8f8a2 100644 --- a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java +++ b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java @@ -1,5 +1,6 @@ package org.caosdb.server.jobs.core; +import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -24,24 +25,81 @@ import org.caosdb.server.jobs.EntityJob; import org.caosdb.server.query.Query; import org.jdom2.Element; +/** + * The EntityStateJob is the abstract base class for four EntityJobs. + * + * <p>1. The InitEntityState job reads ClientMessages or StateProperties with tag state and converts + * them into instances of State. This job runs during WriteTransactions. This job runs during the + * INIT Phase and does not perform any checks other than those necessary for the conversion. + * + * <p>2. The CheckStateTransition job checks if the attempted state transition is in compliance with + * the state model. This job runs during the CHECK phase and should do all necessary consistency and + * permission checks. + * + * <p>3. The MakeStateProperty job constructs an ordinary Property from the State right before the + * entity is being written to the back-end and after any checks run. + * + * <p>4. The MakeStateMessage job converts a state property (back) into State messages and appends + * them to the entity. + * + * <p>Only the 4th job runs during Retrieve transitions. During WriteTransactions all four jobs do + * run. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ public abstract class EntityStateJob extends EntityJob { + public static final String TO_STATE_PROPERTY_NAME = "to"; + public static final String FROM_STATE_PROPERTY_NAME = "from"; + public static final String FINAL_STATE_PROPERTY_NAME = "final"; + public static final String INITIAL_STATE_PROPERTY_NAME = "final"; + public static final String STATE_RECORD_TYPE_NAME = "State"; + public static final String STATE_MODEL_RECORD_TYPE_NAME = "StateModel"; + public static final String TRANSITION_RECORD_TYPE_NAME = "Transition"; + public static final String STATE_XML_TAG = "State"; + public static final String MODEL_ATTRIBUTE_NAME = "model"; + public static final String STATE_ATTRIBUTE_NAME = "name"; + + public static final Message STATE_MODEL_NOT_FOUND = + new Message(MessageType.Error, "StateModel not found."); + public static final Message STATE_NOT_IN_STATE_MODEL = + new Message(MessageType.Error, "State does not exist in this StateModel."); + public static final Message COULD_NOT_GENERATE_STATE_MESSAGE = + new Message(MessageType.Error, "Could not generate the state message."); + public static final Message COULD_NOT_GENERATE_TRANSITIONS = + new Message(MessageType.Error, "Could not generate transitions."); + public static final Message COULD_NOT_GENERATE_STATES = + new Message(MessageType.Error, "Could not generate states."); + public static final Message STATE_MODEL_NOT_SPECIFIED = + new Message(MessageType.Error, "State model not specified."); + public static final Message STATE_NOT_SPECIFIED = + new Message(MessageType.Error, "State not specified."); + + /** + * Represents a Transition which is identified by a name and the two States from and to which an + * entity is being transitioned. + * + * <p>Currently, only exactly one toState and one fromState can be defined. However, it might be + * allowed in the future to have multiple states here. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ public class Transition { - private String transitionIdVersion; private State fromState; private State toState; + private String name; public Transition(EntityInterface transition) throws Message { - this.transitionIdVersion = transition.getIdVersion(); + this.name = transition.getName(); this.fromState = getFromState(transition); this.toState = getToState(transition); } private State getToState(EntityInterface transition) throws Message { for (Property p : transition.getProperties()) { - if (p.getName().equals("to")) { - return createStateMessage(p).getState(); + if (p.getName().equals(TO_STATE_PROPERTY_NAME)) { + return createState(p); } } return null; @@ -49,8 +107,8 @@ public abstract class EntityStateJob extends EntityJob { private State getFromState(EntityInterface transition) throws Message { for (Property p : transition.getProperties()) { - if (p.getName().equals("from")) { - return createStateMessage(p).getState(); + if (p.getName().equals(FROM_STATE_PROPERTY_NAME)) { + return createState(p); } } return null; @@ -75,48 +133,167 @@ public abstract class EntityStateJob extends EntityJob { @Override public boolean equals(Object obj) { if (obj instanceof Transition) { - return ((Transition) obj).transitionIdVersion.equals(this.transitionIdVersion); + Transition that = (Transition) obj; + return Objects.equals(this.getName(), that.getName()) + && Objects.equals(this.getFromState(), that.getFromState()) + && Objects.equals(this.getToState(), that.getToState()); } return false; } + public String getName() { + return this.name; + } + @Override - public int hashCode() { - return 1239141238 + this.transitionIdVersion.hashCode(); + public String toString() { + return "Transition (name=" + + getName() + + ", from=" + + getFromState().getStateName() + + ", to=" + + getToState().getStateName() + + ")"; } } + /** + * The State instance represents a single entity state. This class is used for concrete state (the + * state of a stateful entity, say a Record) and abstract states (states which belong to a + * StateModel entity). + * + * <p>States are identified via their name and the name of the model to which they belong. + * + * <p>States are represented by Records with the state's name as the Record name. They belong to a + * StateModel iff the StateModel RecordType references the State Record. Each State should only + * belong to one StateModel. + * + * <p>Furthermore, States are the start or end point of Transitions which belong to the same + * StateModel. Each State can be part of several transitions at the same time. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ public class State implements ToElementable { - private String stateIdVersion; + private String stateModelName = null; + private String stateName = null; + private EntityInterface stateEntity = null; + private EntityInterface stateModelEntity = null; + private StateModel stateModel; - public State(EntityInterface stateEntity) { - this.stateIdVersion = stateEntity.getIdVersion(); + public State(String stateName, String stateModelName) throws Message { + this.stateName = stateName; + this.stateModelName = stateModelName; + } + + public State(EntityInterface stateEntity, EntityInterface stateModelEntity) throws Message { + this.stateEntity = stateEntity; + this.stateName = stateEntity.getName(); + this.stateModelEntity = stateModelEntity; + this.stateModelName = stateModelEntity.getName(); } @Override public boolean equals(Object obj) { if (obj instanceof State) { - return ((State) obj).stateIdVersion.equals(this.stateIdVersion); + State that = (State) obj; + return Objects.equals(that.getStateName(), this.getStateName()) + && Objects.equals(that.getStateModelName(), this.getStateModelName()); } return false; } @Override public int hashCode() { - return 1239141238 + this.stateIdVersion.hashCode(); + return 21364234 + this.getStateName().hashCode() + this.getStateModelName().hashCode(); } @Override public void addToElement(Element ret) { - // TODO Auto-generated method stub + Element e = new Element(STATE_XML_TAG); + if (this.getStateModelName() != null) { + e.setAttribute(MODEL_ATTRIBUTE_NAME, this.getStateModelName()); + } + if (this.getStateName() != null) { + e.setAttribute(STATE_ATTRIBUTE_NAME, this.getStateName()); + } + ret.addContent(e); + } + + private String getStateModelName() { + return this.stateModelName; + } + + private String getStateName() { + return this.stateName; + } + + public StateModel getStateModel() throws Message { + if (this.stateModel == null) { + this.stateModel = new StateModel(getStateModelEntity()); + } + return this.stateModel; + } + + public boolean isInitial() throws Message { + return Objects.equals(this, getStateModel().initialState); + } + + public boolean isFinal() throws Message { + return Objects.equals(this, getStateModel().finalState); + } + + /** + * Create a property which represents the current entity state of a stateful entity. + * + * @return stateProperty + * @throws Message + */ + public Property createStateProperty() throws Message { + EntityInterface stateRecordType = getStateRecordType(); + Property stateProperty = new Property(stateRecordType); + stateProperty.setDatatype(new ReferenceDatatype2(stateRecordType)); + stateProperty.setValue(new ReferenceValue(getStateEntity(), false)); + stateProperty.setStatementStatus(StatementStatus.FIX); + return stateProperty; + } + public EntityInterface getStateEntity() throws Message { + if (this.stateEntity == null) { + this.stateEntity = retrieveStateEntity(getStateModelEntity(), this.getStateName()); + } + return this.stateEntity; + } + + public EntityInterface getStateModelEntity() throws Message { + if (this.stateModelEntity == null) { + this.stateModelEntity = retrieveStateModelEntity(this.getStateModelName()); + } + return this.stateModelEntity; + } + + @Override + public String toString() { + return "State (name=" + getStateName() + ", model=" + getStateModelName() + ")"; } } + /** + * A StateModel is an abstract definition of a Finite State Machine for entities. + * + * <p>It consists of a set of States, a set of transitions, a initial state and a final state. + * + * <p>If the StateModel has no initial state, it cannot be initialized (no entity will ever be in + * any of the StateModel's states) without using the forceInitialState flag. + * + * <p>If the StateModel has not final state, an entity with any of the states from this StateModel + * cannot leave this StateModel (and cannot be deleted either) without using the forceFinalState + * flag. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ public class StateModel { - private String stateModelIdVersion; private String name; private Set<State> states; private Set<Transition> transitions; @@ -124,8 +301,7 @@ public abstract class EntityStateJob extends EntityJob { private State finalState; public StateModel(EntityInterface stateModelEntity) throws Message { - this.stateModelIdVersion = stateModelEntity.getIdVersion(); - this.getName(); + this.name = stateModelEntity.getName(); this.states = getStates(stateModelEntity); this.transitions = getTransitions(stateModelEntity); this.finalState = getFinalState(stateModelEntity); @@ -134,8 +310,8 @@ public abstract class EntityStateJob extends EntityJob { private State getInitialState(EntityInterface stateModelEntity) throws Message { for (Property p : stateModelEntity.getProperties()) { - if (p.getName().equals("initial")) { - return createStateMessage(p).getState(); + if (p.getName().equals(INITIAL_STATE_PROPERTY_NAME)) { + return createState(p); } } return null; @@ -143,8 +319,8 @@ public abstract class EntityStateJob extends EntityJob { private State getFinalState(EntityInterface stateModelEntity) throws Message { for (Property p : stateModelEntity.getProperties()) { - if (p.getName().equals("final")) { - return createStateMessage(p).getState(); + if (p.getName().equals(FINAL_STATE_PROPERTY_NAME)) { + return createState(p); } } return null; @@ -152,7 +328,7 @@ public abstract class EntityStateJob extends EntityJob { private Set<Transition> getTransitions(EntityInterface stateModelEntity) throws Message { for (Property p : stateModelEntity.getProperties()) { - if (p.getName().equals("Transition")) { + if (p.getName().equals(TRANSITION_RECORD_TYPE_NAME)) { return createTransitions(p); } } @@ -175,7 +351,6 @@ public abstract class EntityStateJob extends EntityJob { } } } catch (Exception e) { - e.printStackTrace(); throw COULD_NOT_GENERATE_TRANSITIONS; } return result; @@ -183,33 +358,32 @@ public abstract class EntityStateJob extends EntityJob { private Set<State> getStates(EntityInterface stateModelEntity) throws Message { for (Property p : stateModelEntity.getProperties()) { - if (p.getName().equals("State")) { - return createStates(p); + if (p.getName().equals(STATE_RECORD_TYPE_NAME)) { + return createStates(p, stateModelEntity); } } return null; } - private Set<State> createStates(Property p) throws Message { - Set<State> result = new HashSet<>(); + private Set<State> createStates(Property p, EntityInterface stateModelEntity) throws Message { + if (!(p.getDatatype() instanceof AbstractCollectionDatatype)) { + return null; + } try { - if (!(p.getDatatype() instanceof AbstractCollectionDatatype)) { - return result; - } + Set<State> result = new HashSet<>(); p.parseValue(); CollectionValue vals = (CollectionValue) p.getValue(); for (IndexedSingleValue val : vals) { if (val.getWrapped() instanceof ReferenceValue) { Integer refid = ((ReferenceValue) val.getWrapped()).getId(); - EntityInterface transition = retrieveValidEntity(refid); - result.add(new State(transition)); + EntityInterface stateEntity = retrieveValidEntity(refid); + result.add(new State(stateEntity, stateModelEntity)); } } + return result; } catch (Exception e) { - e.printStackTrace(); throw COULD_NOT_GENERATE_STATES; } - return result; } public String getName() { @@ -235,104 +409,12 @@ public abstract class EntityStateJob extends EntityJob { @Override public boolean equals(Object obj) { if (obj instanceof StateModel) { - return ((StateModel) obj).stateModelIdVersion.equals(this.stateModelIdVersion); + return ((StateModel) obj).getName().equals(this.getName()); } return false; } - - @Override - public int hashCode() { - return 1239141238 + this.stateModelIdVersion.hashCode(); - } } - public class StateMessage implements ToElementable { - - String name; - String model; - private EntityInterface stateModelEntity; - private EntityInterface stateEntity; - - /** - * @param name - * @param model - */ - public StateMessage(String name, String model) { - this.name = name; - this.model = model; - } - - @Override - public void addToElement(Element ret) { - Element e = new Element("State"); - e.setAttribute("model", this.model); - e.setAttribute("name", this.name); - ret.addContent(e); - } - - public Property createStateProperty() { - EntityInterface stateRecordType = getStateRecordType(); - Property stateProperty = new Property(stateRecordType); - stateProperty.setDatatype(new ReferenceDatatype2(stateRecordType)); - stateProperty.setValue(new ReferenceValue(getStateEntity(), false)); - stateProperty.setStatementStatus(StatementStatus.FIX); - return stateProperty; - } - - private EntityInterface getStateRecordType() { - try { - return retrieveValidSparseEntityByName("State"); - } catch (Message e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } - } - - public EntityInterface getStateModelEntity() { - if (this.stateModelEntity == null) { - try { - this.stateModelEntity = retrieveStateModelEntity(model); - } catch (Message e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - return this.stateModelEntity; - } - - public EntityInterface getStateEntity() { - if (this.stateEntity == null) { - try { - this.stateEntity = retrieveStateEntity(getStateModelEntity(), this.name); - } catch (Message e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - return this.stateEntity; - } - - public StateModel getStateModel() throws Message { - return new StateModel(getStateModelEntity()); - } - - public State getState() { - return new State(getStateEntity()); - } - } - - private static final Message STATE_MODEL_NOT_FOUND = - new Message(MessageType.Error, "StateModel does not exist."); - private static final Message STATE_ENTITY_NOT_FOUND = - new Message(MessageType.Error, "State does not exist in this StateModel."); - private static final Message COULD_NOT_GENERATE_STATE_MESSAGE = - new Message(MessageType.Error, "Could not generate the state message"); - private static final Message COULD_NOT_GENERATE_TRANSITIONS = - new Message(MessageType.Error, "Could not generate transitions"); - private static final Message COULD_NOT_GENERATE_STATES = - new Message(MessageType.Error, "Could not generate states"); - protected EntityInterface retrieveStateEntity(EntityInterface stateModelEntity, String stateName) throws Message { @@ -340,10 +422,10 @@ public abstract class EntityStateJob extends EntityJob { try { list = execute(new GetIDByName(stateName)).getList(); } catch (EntityDoesNotExistException e) { - throw STATE_ENTITY_NOT_FOUND; + throw STATE_NOT_IN_STATE_MODEL; } for (Property p : stateModelEntity.getProperties()) { - if (Objects.equals(p.getName(), "State")) { + if (Objects.equals(p.getName(), STATE_RECORD_TYPE_NAME)) { p.parseValue(); for (IndexedSingleValue val : ((CollectionValue) p.getValue())) { ReferenceValue refid = (ReferenceValue) val.getWrapped(); @@ -357,7 +439,7 @@ public abstract class EntityStateJob extends EntityJob { } } } - throw STATE_ENTITY_NOT_FOUND; + throw STATE_NOT_IN_STATE_MODEL; } protected EntityInterface retrieveStateModelEntity(String stateModel) throws Message { @@ -368,99 +450,114 @@ public abstract class EntityStateJob extends EntityJob { } } - protected StateMessage getStateMessage() { - return getStateMessage(false); + protected EntityInterface getStateRecordType() throws Message { + // TODO this should be cached + return retrieveValidSparseEntityByName(STATE_RECORD_TYPE_NAME); } - protected StateMessage getStateMessage(EntityInterface entity) { - return getStateMessage(entity, false); + protected State getState() { + return getState(false); } - protected StateMessage getStateMessage(EntityInterface entity, boolean remove) { + protected State getState(EntityInterface entity) { + return getState(entity, false); + } + + protected State getState(EntityInterface entity, boolean remove) { Iterator<ToElementable> messages = entity.getMessages().iterator(); while (messages.hasNext()) { ToElementable s = messages.next(); - if (s instanceof StateMessage) { + if (s instanceof State) { if (remove) { messages.remove(); } - return (StateMessage) s; + return (State) s; } } return null; } - protected StateMessage getStateMessage(boolean remove) { - return getStateMessage(getEntity(), remove); + protected State getState(boolean remove) { + return getState(getEntity(), remove); } - protected Property getStateProperty(EntityInterface entity, boolean remove) { + protected List<Property> getStateProperties(EntityInterface entity, boolean remove) { Iterator<Property> it = entity.getProperties().iterator(); + List<Property> result = new ArrayList<>(); while (it.hasNext()) { Property p = it.next(); - if (Objects.equals(p.getName(), "State")) { + if (Objects.equals(p.getName(), STATE_RECORD_TYPE_NAME)) { if (!(p.getDatatype() instanceof ReferenceDatatype)) { continue; } if (remove) { it.remove(); } - return p; + result.add(p); } } - return null; + return result; } - protected Property getStateProperty(boolean remove) { - return getStateProperty(getEntity(), remove); + protected List<Property> getStateProperties(boolean remove) { + return getStateProperties(getEntity(), remove); } - protected ClientMessage getStateClientMessage(EntityInterface entity, boolean remove) { - Iterator<Message> stateMessages = entity.getMessages("State").iterator(); + protected List<ClientMessage> getStateClientMessages(EntityInterface entity, boolean remove) { + Iterator<Message> stateMessages = entity.getMessages(STATE_XML_TAG).iterator(); + List<ClientMessage> result = new ArrayList<>(); while (stateMessages.hasNext()) { Message s = stateMessages.next(); if (s instanceof ClientMessage) { if (remove) { stateMessages.remove(); } - return (ClientMessage) s; + result.add((ClientMessage) s); } } - return null; + return result; } - protected ClientMessage getStateClientMessage(boolean remove) { - return getStateClientMessage(getEntity(), remove); + protected List<ClientMessage> getStateClientMessages(boolean remove) { + return getStateClientMessages(getEntity(), remove); } - protected StateMessage createStateMessage(ClientMessage s) throws Message { - String stateModel = s.getProperty("model"); - String stateName = s.getProperty("name"); - return new StateMessage(stateName, stateModel); + protected State createState(ClientMessage s) throws Message { + String stateModel = s.getProperty(MODEL_ATTRIBUTE_NAME); + if (stateModel == null) { + throw STATE_MODEL_NOT_SPECIFIED; + } + String stateName = s.getProperty(STATE_ATTRIBUTE_NAME); + if (stateName == null) { + throw STATE_NOT_SPECIFIED; + } + return new State(stateName, stateModel); } - protected StateMessage createStateMessage(Property p) throws Message { + protected State createState(Property p) throws Message { try { p.parseValue(); ReferenceValue refid = (ReferenceValue) p.getValue(); EntityInterface stateEntity = retrieveValidSparseEntityById(refid.getId(), null); EntityInterface stateModelEntity = findStateModel(stateEntity); - return new StateMessage(stateEntity.getName(), stateModelEntity.getName()); + return new State(stateEntity, stateModelEntity); } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); throw COULD_NOT_GENERATE_STATE_MESSAGE; } } EntityInterface findStateModel(EntityInterface stateEntity) throws Exception { + // TODO this should be cached Query query = new Query( - "FIND Record StateModel WHICH REFERENCES " + Integer.toString(stateEntity.getId()), + "FIND RECORD " + + STATE_MODEL_RECORD_TYPE_NAME + + " WHICH REFERENCES " + + Integer.toString(stateEntity.getId()), getUser(), null); query.execute(getTransaction().getAccess()); - return retrieveValidSparseEntityById(query.getResultSet().get(0), null); + return retrieveValidEntity(query.getResultSet().get(0)); } } diff --git a/src/main/java/org/caosdb/server/jobs/core/InitEntityState.java b/src/main/java/org/caosdb/server/jobs/core/InitEntityState.java index 9a92dd42206ca798a7c6e60ef8ffe77d9dc9f488..4b1d74d9827b88bb286fac0d4a1f9b494eb803ad 100644 --- a/src/main/java/org/caosdb/server/jobs/core/InitEntityState.java +++ b/src/main/java/org/caosdb/server/jobs/core/InitEntityState.java @@ -1,5 +1,7 @@ package org.caosdb.server.jobs.core; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import org.caosdb.server.entity.ClientMessage; import org.caosdb.server.entity.Entity; @@ -24,10 +26,23 @@ public class InitEntityState extends EntityStateJob implements Observer { @Override protected void run() { try { - State newState = initStateMessage(getEntity()); + State newState = null; + { + List<State> states = initStateMessage(getEntity()); + if (states.size() > 1) { + throw new Message( + MessageType.Error, "Currently, each entity can only have one state at a time."); + } else if (states.size() == 1) { + newState = states.get(0); + } + } try { if (getEntity() instanceof UpdateEntity) { - State oldState = initStateMessage(((UpdateEntity) getEntity()).getOriginal()); + List<State> states = initStateMessage(((UpdateEntity) getEntity()).getOriginal()); + State oldState = null; + if (states.size() == 1) { + oldState = states.get(0); + } if (!Objects.equals(newState, oldState)) { getEntity().acceptObserver(this); } @@ -46,26 +61,33 @@ public class InitEntityState extends EntityStateJob implements Observer { MessageType.Warning, "State error in previous entity version\n" + m.getDescription()); } - private State initStateMessage(EntityInterface entity) throws Message { - ClientMessage stateClientMessage = getStateClientMessage(entity, true); - if (stateClientMessage != null) { - StateMessage stateMessage = createStateMessage(stateClientMessage); - entity.addMessage(stateMessage); - return stateMessage.getState(); + private List<State> initStateMessage(EntityInterface entity) throws Message { + List<ClientMessage> stateClientMessages = getStateClientMessages(entity, true); + List<State> result = new ArrayList<>(); + if (stateClientMessages != null) { + for (ClientMessage s : stateClientMessages) { + State stateMessage = createState(s); + entity.addMessage(stateMessage); + result.add(stateMessage); + } } - Property stateProperty = getStateProperty(entity, true); - if (stateProperty != null) { - StateMessage stateMessage = createStateMessage(stateProperty); - entity.addMessage(stateMessage); - return stateMessage.getState(); + List<Property> stateProperties = getStateProperties(entity, true); + if (stateProperties != null) { + for (Property p : stateProperties) { + State stateMessage = createState(p); + entity.addMessage(stateMessage); + result.add(stateMessage); + } } - return null; + return result; } @Override public boolean notifyObserver(String e, Observable o) { if (e == Entity.ENTITY_STATUS_CHANGED_EVENT) { if (o == getEntity() && getEntity().getEntityStatus() == EntityStatus.VALID) { + // The Update.deriveUpdate method didn't recognize that the state is changing and set the + // entity to "VALID" getEntity().setEntityStatus(EntityStatus.QUALIFIED); return false; } diff --git a/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java b/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..3fb97b49bf3f1b5bab105164f9ecfee5d020949a --- /dev/null +++ b/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java @@ -0,0 +1,29 @@ +package org.caosdb.server.jobs.core; + +import java.util.List; +import org.caosdb.server.entity.Message; +import org.caosdb.server.entity.wrapper.Property; +import org.caosdb.server.jobs.JobAnnotation; +import org.caosdb.server.jobs.JobExecutionTime; +import org.caosdb.server.transaction.Retrieve; + +@JobAnnotation(loadAlways = true, time = JobExecutionTime.POST_TRANSACTION) +public class MakeStateMessage extends EntityStateJob { + + @Override + protected void run() { + try { + List<Property> stateProperties = getStateProperties(true); + + // only add the State during Retrieve. In all other cases, the State is already present. + if (stateProperties != null && getTransaction() instanceof Retrieve) { + for (Property s : stateProperties) { + State stateMessage = createState(s); + getEntity().addMessage(stateMessage); + } + } + } catch (Message e) { + getEntity().addError(e); + } + } +} diff --git a/src/main/java/org/caosdb/server/jobs/core/ParseStateMessage.java b/src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java similarity index 56% rename from src/main/java/org/caosdb/server/jobs/core/ParseStateMessage.java rename to src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java index d7cd71dc844b0b0001a0cab52b32d43a2560d843..2df5cb7bd047ad361e756103e600a0b16f72f86d 100644 --- a/src/main/java/org/caosdb/server/jobs/core/ParseStateMessage.java +++ b/src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java @@ -1,5 +1,6 @@ package org.caosdb.server.jobs.core; +import org.caosdb.server.entity.Message; import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.JobExecutionTime; import org.caosdb.server.transaction.WriteTransaction; @@ -8,15 +9,21 @@ import org.caosdb.server.transaction.WriteTransaction; loadAlways = true, transaction = WriteTransaction.class, time = JobExecutionTime.PRE_TRANSACTION) -public class ParseStateMessage extends EntityStateJob { +public class MakeStateProperty extends EntityStateJob { @Override protected void run() { - StateMessage s = getStateMessage(true); - if (s != null) addStateProperty(s); + State s = getState(); + if (s != null) { + try { + addStateProperty(s); + } catch (Message e) { + getEntity().addError(e); + } + } } - private void addStateProperty(StateMessage stateEntity) { + private void addStateProperty(State stateEntity) throws Message { getEntity().addProperty(stateEntity.createStateProperty()); } } diff --git a/src/main/java/org/caosdb/server/jobs/core/WriteStateMessage.java b/src/main/java/org/caosdb/server/jobs/core/WriteStateMessage.java deleted file mode 100644 index 9fa21b7c0ee2248afa5606515fba858cd0e24361..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/jobs/core/WriteStateMessage.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.caosdb.server.jobs.core; - -import org.caosdb.server.entity.Message; -import org.caosdb.server.entity.wrapper.Property; -import org.caosdb.server.jobs.JobAnnotation; -import org.caosdb.server.jobs.JobExecutionTime; - -@JobAnnotation(loadAlways = true, time = JobExecutionTime.POST_TRANSACTION) -public class WriteStateMessage extends EntityStateJob { - - @Override - protected void run() { - try { - Property stateProperty = getStateProperty(true); - if (stateProperty != null) { - StateMessage stateMessage = createStateMessage(stateProperty); - getEntity().addMessage(stateMessage); - } - } catch (Message e) { - getEntity().addError(e); - } - } -}