Skip to content
Snippets Groups Projects
Verified Commit af328e68 authored by Timm Fitschen's avatar Timm Fitschen
Browse files

WIP: FSM

parent e8a64dd5
No related branches found
No related tags found
3 merge requests!21Release v0.4.0,!7F fsm,!6Draft: F acm permissions2
...@@ -41,4 +41,14 @@ public class ClientMessage extends Message { ...@@ -41,4 +41,14 @@ public class ClientMessage extends Message {
public String getProperty(String key) { public String getProperty(String key) {
return properties.get(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();
}
} }
...@@ -225,14 +225,6 @@ public abstract class Job { ...@@ -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; static HashMap<String, Class<? extends Job>> allClasses = null;
private static List<Class<? extends Job>> loadAlways; private static List<Class<? extends Job>> loadAlways;
......
...@@ -16,28 +16,41 @@ public class CheckStateTransition extends EntityStateJob { ...@@ -16,28 +16,41 @@ public class CheckStateTransition extends EntityStateJob {
private static final Message TRANSITION_NOT_ALLOWED = private static final Message TRANSITION_NOT_ALLOWED =
new Message(MessageType.Error, "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 @Override
protected void run() { protected void run() {
try { try {
State nextState = getState();
if (nextState != null) {
checkStateValid(nextState);
}
if (getEntity() instanceof UpdateEntity) { if (getEntity() instanceof UpdateEntity) {
StateMessage toState = getStateMessage(); State fromState = getState(((UpdateEntity) getEntity()).getOriginal());
StateMessage fromState = getStateMessage(((UpdateEntity) getEntity()).getOriginal()); checkStateTransition(fromState, nextState);
checkStateTransition(fromState, toState);
} else if (getEntity() instanceof DeleteEntity) { } else if (getEntity() instanceof DeleteEntity) {
StateMessage finalState = getStateMessage(); if (nextState != null) checkFinalState(nextState);
checkFinalState(finalState);
} else { } else {
StateMessage initialState = getStateMessage(); if (nextState != null) checkInitialState(nextState);
checkInitialState(initialState);
} }
} catch (Message m) { } catch (Message m) {
getEntity().addError(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 */ /** 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) { if (fromState == null && toState == null) {
return; return;
} else if (fromState == null && toState != null) { } else if (fromState == null && toState != null) {
...@@ -50,32 +63,44 @@ public class CheckStateTransition extends EntityStateJob { ...@@ -50,32 +63,44 @@ public class CheckStateTransition extends EntityStateJob {
StateModel stateModel = findMatchingStateModel(fromState, toState); StateModel stateModel = findMatchingStateModel(fromState, toState);
if (stateModel == null) { if (stateModel == null) {
// change from one stateModel to another
checkInitialState(toState); checkInitialState(toState);
checkFinalState(fromState); checkFinalState(fromState);
return; return;
} }
for (Transition t : stateModel.getTransitions()) { for (Transition t : stateModel.getTransitions()) {
if (t.fromStatesInclude(fromState.getState()) && t.toStatesInclude(toState.getState())) { if (t.fromStatesInclude(fromState) && t.toStatesInclude(toState)) {
// TODO permissions
return; return;
} }
} }
throw TRANSITION_NOT_ALLOWED; throw TRANSITION_NOT_ALLOWED;
} }
private StateModel findMatchingStateModel(StateMessage fromState, StateMessage toState) private StateModel findMatchingStateModel(State fromState, State toState) throws Message {
throws Message {
if (fromState.getStateModel().equals(toState.getStateModel())) { if (fromState.getStateModel().equals(toState.getStateModel())) {
return fromState.getStateModel(); return fromState.getStateModel();
} }
return null; return null;
} }
private void checkFinalState(StateMessage fromState) { private void checkFinalState(State fromState) throws Message {
// TODO Auto-generated method stub 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) { private void checkInitialState(State toState) throws Message {
// TODO Auto-generated method stub if (!toState.isInitial()) {
throw INITIAL_STATE_NOT_ALLOWED;
}
// TODO permissions
} }
} }
package org.caosdb.server.jobs.core; package org.caosdb.server.jobs.core;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
...@@ -24,24 +25,81 @@ import org.caosdb.server.jobs.EntityJob; ...@@ -24,24 +25,81 @@ import org.caosdb.server.jobs.EntityJob;
import org.caosdb.server.query.Query; import org.caosdb.server.query.Query;
import org.jdom2.Element; 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 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 { public class Transition {
private String transitionIdVersion;
private State fromState; private State fromState;
private State toState; private State toState;
private String name;
public Transition(EntityInterface transition) throws Message { public Transition(EntityInterface transition) throws Message {
this.transitionIdVersion = transition.getIdVersion(); this.name = transition.getName();
this.fromState = getFromState(transition); this.fromState = getFromState(transition);
this.toState = getToState(transition); this.toState = getToState(transition);
} }
private State getToState(EntityInterface transition) throws Message { private State getToState(EntityInterface transition) throws Message {
for (Property p : transition.getProperties()) { for (Property p : transition.getProperties()) {
if (p.getName().equals("to")) { if (p.getName().equals(TO_STATE_PROPERTY_NAME)) {
return createStateMessage(p).getState(); return createState(p);
} }
} }
return null; return null;
...@@ -49,8 +107,8 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -49,8 +107,8 @@ public abstract class EntityStateJob extends EntityJob {
private State getFromState(EntityInterface transition) throws Message { private State getFromState(EntityInterface transition) throws Message {
for (Property p : transition.getProperties()) { for (Property p : transition.getProperties()) {
if (p.getName().equals("from")) { if (p.getName().equals(FROM_STATE_PROPERTY_NAME)) {
return createStateMessage(p).getState(); return createState(p);
} }
} }
return null; return null;
...@@ -75,48 +133,167 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -75,48 +133,167 @@ public abstract class EntityStateJob extends EntityJob {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof Transition) { 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; return false;
} }
public String getName() {
return this.name;
}
@Override @Override
public int hashCode() { public String toString() {
return 1239141238 + this.transitionIdVersion.hashCode(); 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 { 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(String stateName, String stateModelName) throws Message {
this.stateName = stateName;
this.stateModelName = stateModelName;
}
public State(EntityInterface stateEntity) { public State(EntityInterface stateEntity, EntityInterface stateModelEntity) throws Message {
this.stateIdVersion = stateEntity.getIdVersion(); this.stateEntity = stateEntity;
this.stateName = stateEntity.getName();
this.stateModelEntity = stateModelEntity;
this.stateModelName = stateModelEntity.getName();
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof State) { 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; return false;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return 1239141238 + this.stateIdVersion.hashCode(); return 21364234 + this.getStateName().hashCode() + this.getStateModelName().hashCode();
} }
@Override @Override
public void addToElement(Element ret) { 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 { public class StateModel {
private String stateModelIdVersion;
private String name; private String name;
private Set<State> states; private Set<State> states;
private Set<Transition> transitions; private Set<Transition> transitions;
...@@ -124,8 +301,7 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -124,8 +301,7 @@ public abstract class EntityStateJob extends EntityJob {
private State finalState; private State finalState;
public StateModel(EntityInterface stateModelEntity) throws Message { public StateModel(EntityInterface stateModelEntity) throws Message {
this.stateModelIdVersion = stateModelEntity.getIdVersion(); this.name = stateModelEntity.getName();
this.getName();
this.states = getStates(stateModelEntity); this.states = getStates(stateModelEntity);
this.transitions = getTransitions(stateModelEntity); this.transitions = getTransitions(stateModelEntity);
this.finalState = getFinalState(stateModelEntity); this.finalState = getFinalState(stateModelEntity);
...@@ -134,8 +310,8 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -134,8 +310,8 @@ public abstract class EntityStateJob extends EntityJob {
private State getInitialState(EntityInterface stateModelEntity) throws Message { private State getInitialState(EntityInterface stateModelEntity) throws Message {
for (Property p : stateModelEntity.getProperties()) { for (Property p : stateModelEntity.getProperties()) {
if (p.getName().equals("initial")) { if (p.getName().equals(INITIAL_STATE_PROPERTY_NAME)) {
return createStateMessage(p).getState(); return createState(p);
} }
} }
return null; return null;
...@@ -143,8 +319,8 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -143,8 +319,8 @@ public abstract class EntityStateJob extends EntityJob {
private State getFinalState(EntityInterface stateModelEntity) throws Message { private State getFinalState(EntityInterface stateModelEntity) throws Message {
for (Property p : stateModelEntity.getProperties()) { for (Property p : stateModelEntity.getProperties()) {
if (p.getName().equals("final")) { if (p.getName().equals(FINAL_STATE_PROPERTY_NAME)) {
return createStateMessage(p).getState(); return createState(p);
} }
} }
return null; return null;
...@@ -152,7 +328,7 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -152,7 +328,7 @@ public abstract class EntityStateJob extends EntityJob {
private Set<Transition> getTransitions(EntityInterface stateModelEntity) throws Message { private Set<Transition> getTransitions(EntityInterface stateModelEntity) throws Message {
for (Property p : stateModelEntity.getProperties()) { for (Property p : stateModelEntity.getProperties()) {
if (p.getName().equals("Transition")) { if (p.getName().equals(TRANSITION_RECORD_TYPE_NAME)) {
return createTransitions(p); return createTransitions(p);
} }
} }
...@@ -175,7 +351,6 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -175,7 +351,6 @@ public abstract class EntityStateJob extends EntityJob {
} }
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
throw COULD_NOT_GENERATE_TRANSITIONS; throw COULD_NOT_GENERATE_TRANSITIONS;
} }
return result; return result;
...@@ -183,33 +358,32 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -183,33 +358,32 @@ public abstract class EntityStateJob extends EntityJob {
private Set<State> getStates(EntityInterface stateModelEntity) throws Message { private Set<State> getStates(EntityInterface stateModelEntity) throws Message {
for (Property p : stateModelEntity.getProperties()) { for (Property p : stateModelEntity.getProperties()) {
if (p.getName().equals("State")) { if (p.getName().equals(STATE_RECORD_TYPE_NAME)) {
return createStates(p); return createStates(p, stateModelEntity);
} }
} }
return null; return null;
} }
private Set<State> createStates(Property p) throws Message { private Set<State> createStates(Property p, EntityInterface stateModelEntity) throws Message {
Set<State> result = new HashSet<>();
try {
if (!(p.getDatatype() instanceof AbstractCollectionDatatype)) { if (!(p.getDatatype() instanceof AbstractCollectionDatatype)) {
return result; return null;
} }
try {
Set<State> result = new HashSet<>();
p.parseValue(); p.parseValue();
CollectionValue vals = (CollectionValue) p.getValue(); CollectionValue vals = (CollectionValue) p.getValue();
for (IndexedSingleValue val : vals) { for (IndexedSingleValue val : vals) {
if (val.getWrapped() instanceof ReferenceValue) { if (val.getWrapped() instanceof ReferenceValue) {
Integer refid = ((ReferenceValue) val.getWrapped()).getId(); Integer refid = ((ReferenceValue) val.getWrapped()).getId();
EntityInterface transition = retrieveValidEntity(refid); EntityInterface stateEntity = retrieveValidEntity(refid);
result.add(new State(transition)); result.add(new State(stateEntity, stateModelEntity));
} }
} }
return result;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
throw COULD_NOT_GENERATE_STATES; throw COULD_NOT_GENERATE_STATES;
} }
return result;
} }
public String getName() { public String getName() {
...@@ -235,104 +409,12 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -235,104 +409,12 @@ public abstract class EntityStateJob extends EntityJob {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof StateModel) { if (obj instanceof StateModel) {
return ((StateModel) obj).stateModelIdVersion.equals(this.stateModelIdVersion); return ((StateModel) obj).getName().equals(this.getName());
} }
return false; 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) protected EntityInterface retrieveStateEntity(EntityInterface stateModelEntity, String stateName)
throws Message { throws Message {
...@@ -340,10 +422,10 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -340,10 +422,10 @@ public abstract class EntityStateJob extends EntityJob {
try { try {
list = execute(new GetIDByName(stateName)).getList(); list = execute(new GetIDByName(stateName)).getList();
} catch (EntityDoesNotExistException e) { } catch (EntityDoesNotExistException e) {
throw STATE_ENTITY_NOT_FOUND; throw STATE_NOT_IN_STATE_MODEL;
} }
for (Property p : stateModelEntity.getProperties()) { for (Property p : stateModelEntity.getProperties()) {
if (Objects.equals(p.getName(), "State")) { if (Objects.equals(p.getName(), STATE_RECORD_TYPE_NAME)) {
p.parseValue(); p.parseValue();
for (IndexedSingleValue val : ((CollectionValue) p.getValue())) { for (IndexedSingleValue val : ((CollectionValue) p.getValue())) {
ReferenceValue refid = (ReferenceValue) val.getWrapped(); ReferenceValue refid = (ReferenceValue) val.getWrapped();
...@@ -357,7 +439,7 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -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 { protected EntityInterface retrieveStateModelEntity(String stateModel) throws Message {
...@@ -368,99 +450,114 @@ public abstract class EntityStateJob extends EntityJob { ...@@ -368,99 +450,114 @@ public abstract class EntityStateJob extends EntityJob {
} }
} }
protected StateMessage getStateMessage() { protected EntityInterface getStateRecordType() throws Message {
return getStateMessage(false); // TODO this should be cached
return retrieveValidSparseEntityByName(STATE_RECORD_TYPE_NAME);
} }
protected StateMessage getStateMessage(EntityInterface entity) { protected State getState() {
return getStateMessage(entity, false); 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(); Iterator<ToElementable> messages = entity.getMessages().iterator();
while (messages.hasNext()) { while (messages.hasNext()) {
ToElementable s = messages.next(); ToElementable s = messages.next();
if (s instanceof StateMessage) { if (s instanceof State) {
if (remove) { if (remove) {
messages.remove(); messages.remove();
} }
return (StateMessage) s; return (State) s;
} }
} }
return null; return null;
} }
protected StateMessage getStateMessage(boolean remove) { protected State getState(boolean remove) {
return getStateMessage(getEntity(), 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(); Iterator<Property> it = entity.getProperties().iterator();
List<Property> result = new ArrayList<>();
while (it.hasNext()) { while (it.hasNext()) {
Property p = it.next(); Property p = it.next();
if (Objects.equals(p.getName(), "State")) { if (Objects.equals(p.getName(), STATE_RECORD_TYPE_NAME)) {
if (!(p.getDatatype() instanceof ReferenceDatatype)) { if (!(p.getDatatype() instanceof ReferenceDatatype)) {
continue; continue;
} }
if (remove) { if (remove) {
it.remove(); it.remove();
} }
return p; result.add(p);
} }
} }
return null; return result;
} }
protected Property getStateProperty(boolean remove) { protected List<Property> getStateProperties(boolean remove) {
return getStateProperty(getEntity(), remove); return getStateProperties(getEntity(), remove);
} }
protected ClientMessage getStateClientMessage(EntityInterface entity, boolean remove) { protected List<ClientMessage> getStateClientMessages(EntityInterface entity, boolean remove) {
Iterator<Message> stateMessages = entity.getMessages("State").iterator(); Iterator<Message> stateMessages = entity.getMessages(STATE_XML_TAG).iterator();
List<ClientMessage> result = new ArrayList<>();
while (stateMessages.hasNext()) { while (stateMessages.hasNext()) {
Message s = stateMessages.next(); Message s = stateMessages.next();
if (s instanceof ClientMessage) { if (s instanceof ClientMessage) {
if (remove) { if (remove) {
stateMessages.remove(); stateMessages.remove();
} }
return (ClientMessage) s; result.add((ClientMessage) s);
} }
} }
return null; return result;
} }
protected ClientMessage getStateClientMessage(boolean remove) { protected List<ClientMessage> getStateClientMessages(boolean remove) {
return getStateClientMessage(getEntity(), remove); return getStateClientMessages(getEntity(), remove);
} }
protected StateMessage createStateMessage(ClientMessage s) throws Message { protected State createState(ClientMessage s) throws Message {
String stateModel = s.getProperty("model"); String stateModel = s.getProperty(MODEL_ATTRIBUTE_NAME);
String stateName = s.getProperty("name"); if (stateModel == null) {
return new StateMessage(stateName, stateModel); 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 { try {
p.parseValue(); p.parseValue();
ReferenceValue refid = (ReferenceValue) p.getValue(); ReferenceValue refid = (ReferenceValue) p.getValue();
EntityInterface stateEntity = retrieveValidSparseEntityById(refid.getId(), null); EntityInterface stateEntity = retrieveValidSparseEntityById(refid.getId(), null);
EntityInterface stateModelEntity = findStateModel(stateEntity); EntityInterface stateModelEntity = findStateModel(stateEntity);
return new StateMessage(stateEntity.getName(), stateModelEntity.getName()); return new State(stateEntity, stateModelEntity);
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw COULD_NOT_GENERATE_STATE_MESSAGE; throw COULD_NOT_GENERATE_STATE_MESSAGE;
} }
} }
EntityInterface findStateModel(EntityInterface stateEntity) throws Exception { EntityInterface findStateModel(EntityInterface stateEntity) throws Exception {
// TODO this should be cached
Query query = Query query =
new 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(), getUser(),
null); null);
query.execute(getTransaction().getAccess()); query.execute(getTransaction().getAccess());
return retrieveValidSparseEntityById(query.getResultSet().get(0), null); return retrieveValidEntity(query.getResultSet().get(0));
} }
} }
package org.caosdb.server.jobs.core; package org.caosdb.server.jobs.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import org.caosdb.server.entity.ClientMessage; import org.caosdb.server.entity.ClientMessage;
import org.caosdb.server.entity.Entity; import org.caosdb.server.entity.Entity;
...@@ -24,10 +26,23 @@ public class InitEntityState extends EntityStateJob implements Observer { ...@@ -24,10 +26,23 @@ public class InitEntityState extends EntityStateJob implements Observer {
@Override @Override
protected void run() { protected void run() {
try { 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 { try {
if (getEntity() instanceof UpdateEntity) { 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)) { if (!Objects.equals(newState, oldState)) {
getEntity().acceptObserver(this); getEntity().acceptObserver(this);
} }
...@@ -46,26 +61,33 @@ public class InitEntityState extends EntityStateJob implements Observer { ...@@ -46,26 +61,33 @@ public class InitEntityState extends EntityStateJob implements Observer {
MessageType.Warning, "State error in previous entity version\n" + m.getDescription()); MessageType.Warning, "State error in previous entity version\n" + m.getDescription());
} }
private State initStateMessage(EntityInterface entity) throws Message { private List<State> initStateMessage(EntityInterface entity) throws Message {
ClientMessage stateClientMessage = getStateClientMessage(entity, true); List<ClientMessage> stateClientMessages = getStateClientMessages(entity, true);
if (stateClientMessage != null) { List<State> result = new ArrayList<>();
StateMessage stateMessage = createStateMessage(stateClientMessage); if (stateClientMessages != null) {
for (ClientMessage s : stateClientMessages) {
State stateMessage = createState(s);
entity.addMessage(stateMessage); entity.addMessage(stateMessage);
return stateMessage.getState(); result.add(stateMessage);
} }
Property stateProperty = getStateProperty(entity, true); }
if (stateProperty != null) { List<Property> stateProperties = getStateProperties(entity, true);
StateMessage stateMessage = createStateMessage(stateProperty); if (stateProperties != null) {
for (Property p : stateProperties) {
State stateMessage = createState(p);
entity.addMessage(stateMessage); entity.addMessage(stateMessage);
return stateMessage.getState(); result.add(stateMessage);
}
} }
return null; return result;
} }
@Override @Override
public boolean notifyObserver(String e, Observable o) { public boolean notifyObserver(String e, Observable o) {
if (e == Entity.ENTITY_STATUS_CHANGED_EVENT) { if (e == Entity.ENTITY_STATUS_CHANGED_EVENT) {
if (o == getEntity() && getEntity().getEntityStatus() == EntityStatus.VALID) { 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); getEntity().setEntityStatus(EntityStatus.QUALIFIED);
return false; return false;
} }
......
package org.caosdb.server.jobs.core; package org.caosdb.server.jobs.core;
import java.util.List;
import org.caosdb.server.entity.Message; import org.caosdb.server.entity.Message;
import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.entity.wrapper.Property;
import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.JobAnnotation;
import org.caosdb.server.jobs.JobExecutionTime; import org.caosdb.server.jobs.JobExecutionTime;
import org.caosdb.server.transaction.Retrieve;
@JobAnnotation(loadAlways = true, time = JobExecutionTime.POST_TRANSACTION) @JobAnnotation(loadAlways = true, time = JobExecutionTime.POST_TRANSACTION)
public class WriteStateMessage extends EntityStateJob { public class MakeStateMessage extends EntityStateJob {
@Override @Override
protected void run() { protected void run() {
try { try {
Property stateProperty = getStateProperty(true); List<Property> stateProperties = getStateProperties(true);
if (stateProperty != null) {
StateMessage stateMessage = createStateMessage(stateProperty); // 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); getEntity().addMessage(stateMessage);
} }
}
} catch (Message e) { } catch (Message e) {
getEntity().addError(e); getEntity().addError(e);
} }
......
package org.caosdb.server.jobs.core; package org.caosdb.server.jobs.core;
import org.caosdb.server.entity.Message;
import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.JobAnnotation;
import org.caosdb.server.jobs.JobExecutionTime; import org.caosdb.server.jobs.JobExecutionTime;
import org.caosdb.server.transaction.WriteTransaction; import org.caosdb.server.transaction.WriteTransaction;
...@@ -8,15 +9,21 @@ import org.caosdb.server.transaction.WriteTransaction; ...@@ -8,15 +9,21 @@ import org.caosdb.server.transaction.WriteTransaction;
loadAlways = true, loadAlways = true,
transaction = WriteTransaction.class, transaction = WriteTransaction.class,
time = JobExecutionTime.PRE_TRANSACTION) time = JobExecutionTime.PRE_TRANSACTION)
public class ParseStateMessage extends EntityStateJob { public class MakeStateProperty extends EntityStateJob {
@Override @Override
protected void run() { protected void run() {
StateMessage s = getStateMessage(true); State s = getState();
if (s != null) addStateProperty(s); 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()); getEntity().addProperty(stateEntity.createStateProperty());
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment