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 af3ba15a6152141350b4a2f5109d7ce606ec5259..ef3c8b270ceee19549cd2128480d2a70bfaeb31b 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java @@ -1,5 +1,6 @@ package org.caosdb.server.jobs.core; +import java.util.Map; import org.apache.shiro.authz.AuthorizationException; import org.caosdb.server.entity.DeleteEntity; import org.caosdb.server.entity.Message; @@ -18,6 +19,9 @@ import org.caosdb.server.utils.ServerMessages; @JobAnnotation(time = JobExecutionTime.POST_CHECK, transaction = WriteTransaction.class) public class CheckStateTransition extends EntityStateJob { + private static final String PERMISSION_STATE_FORCE_FINAL = "STATE:FORCE:FINAL"; + private static final String PERMISSION_STATE_UNASSIGN = "STATE:UNASSIGN:"; + private static final String PERMISSION_STATE_ASSIGN = "STATE:ASSIGN:"; private static final Message TRANSITION_NOT_ALLOWED = new Message(MessageType.Error, "Transition not allowed."); private static final Message INITIAL_STATE_NOT_ALLOWED = @@ -29,7 +33,7 @@ public class CheckStateTransition extends EntityStateJob { * The forceFinalState flag is especially useful if you want to delete entities in the middle of * the state machine's usual state cycle. */ - private static final String FORCE_FINAL_STATE = "forceFinalState"; + private static final String FLAG_FORCE_FINAL_STATE = "forceFinalState"; @Override protected void run() { @@ -131,14 +135,13 @@ public class CheckStateTransition extends EntityStateJob { */ private void checkFinalState(State oldState) throws Message { if (!oldState.isFinal()) { - if ("true".equalsIgnoreCase(getTransaction().getContainer().getFlags().get(FORCE_FINAL_STATE)) - || "true".equalsIgnoreCase(getEntity().getFlag(FORCE_FINAL_STATE))) { - // TODO permissions - return; + if (isForceFinal()) { + getUser().checkPermission(PERMISSION_STATE_FORCE_FINAL); + } else { + throw FINAL_STATE_NOT_ALLOWED; } - throw FINAL_STATE_NOT_ALLOWED; } - // TODO permissions + getUser().checkPermission(PERMISSION_STATE_UNASSIGN + oldState.getStateModelName()); } /** @@ -151,6 +154,13 @@ public class CheckStateTransition extends EntityStateJob { if (!newState.isInitial()) { throw INITIAL_STATE_NOT_ALLOWED; } - // TODO permissions + getUser().checkPermission(PERMISSION_STATE_ASSIGN + newState.getStateModelName()); + } + + private boolean isForceFinal() { + Map<String, String> containerFlags = getTransaction().getContainer().getFlags(); + return (containerFlags != null + && "true".equalsIgnoreCase(containerFlags.get(FLAG_FORCE_FINAL_STATE))) + || "true".equalsIgnoreCase(getEntity().getFlag(FLAG_FORCE_FINAL_STATE)); } } 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 a18abd11131fef7e184e318f94860ff1bf72bc5c..dc1eba1d7dceb4768d1c702bc9f96efb8ac9505f 100644 --- a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java +++ b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java @@ -2,8 +2,9 @@ package org.caosdb.server.jobs.core; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -17,6 +18,7 @@ import org.caosdb.server.datatype.IndexedSingleValue; import org.caosdb.server.datatype.ReferenceDatatype; import org.caosdb.server.datatype.ReferenceDatatype2; import org.caosdb.server.datatype.ReferenceValue; +import org.caosdb.server.datatype.TextDatatype; import org.caosdb.server.entity.ClientMessage; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; @@ -74,6 +76,8 @@ public abstract class EntityStateJob extends EntityJob { public static final String STATE_ATTRIBUTE_NAME = "name"; public static final String STATE_ATTRIBUTE_DESCRIPTION = "description"; public static final String STATE_ATTRIBUTE_ID = "id"; + public static final String ENTITY_STATE_ROLE_MARKER = "?STATE?"; + public static final String PERMISSION_STATE_TRANSION = "STATE:TRANSITION:"; public static final Message STATE_MODEL_NOT_FOUND = new Message(MessageType.Error, "StateModel not found."); @@ -103,12 +107,24 @@ public abstract class EntityStateJob extends EntityJob { private String description; private State fromState; private State toState; + private Map<String, String> transitionProperties; public Transition(EntityInterface transition) throws Message { this.name = transition.getName(); this.description = transition.getDescription(); this.fromState = getFromState(transition); this.toState = getToState(transition); + this.transitionProperties = getTransitionProperties(transition); + } + + private Map<String, String> getTransitionProperties(EntityInterface transition) { + Map<String, String> result = new LinkedHashMap<>(); + for (Property p : transition.getProperties()) { + if (p.getDatatype() instanceof TextDatatype) { + result.put(p.getName(), p.getValue().toString()); + } + } + return result; } private State getToState(EntityInterface transition) throws Message { @@ -185,18 +201,30 @@ public abstract class EntityStateJob extends EntityJob { public Element toElement() { Element result = new Element(TRANSITION_XML_TAG); - if (this.name != null) result.setAttribute(TRANSITION_ATTRIBUTE_NAME, this.name); - if (this.description != null) + if (this.transitionProperties != null) { + this.transitionProperties.forEach( + (String key, String value) -> { + result.setAttribute(key, value); + }); + } + if (this.name != null) { + result.setAttribute(TRANSITION_ATTRIBUTE_NAME, this.name); + } + if (this.description != null) { result.setAttribute(TRANSITION_ATTRIBUTE_DESCRIPTION, this.description); + } Element to = new Element(TO_XML_TAG); to.setAttribute(STATE_ATTRIBUTE_NAME, this.toState.stateName); + if (this.toState.stateDescription != null) { + to.setAttribute(STATE_ATTRIBUTE_DESCRIPTION, this.toState.stateDescription); + } Element from = new Element(FROM_XML_TAG); from.setAttribute(STATE_ATTRIBUTE_NAME, this.fromState.stateName); return result.addContent(from).addContent(to); } public boolean isPermitted(Subject user) { - return user.isPermitted("STATE:TRANSITION:" + this.name); + return user.isPermitted(PERMISSION_STATE_TRANSION + this.name); } } @@ -229,6 +257,7 @@ public abstract class EntityStateJob extends EntityJob { private String stateDescription = null; private Integer stateId = null; private EntityACL stateACL = null; + private Map<String, String> stateProperties; public State(String stateName, String stateModelName) throws Message { this.stateName = stateName; @@ -243,12 +272,23 @@ public abstract class EntityStateJob extends EntityJob { this.stateModelEntity = stateModelEntity; this.stateModelName = stateModelEntity.getName(); this.stateACL = createStateACL(stateEntity.getEntityACL()); + this.stateProperties = createStateProperties(stateEntity); + } + + private Map<String, String> createStateProperties(EntityInterface stateEntity) { + Map<String, String> result = new LinkedHashMap<>(); + for (Property p : stateEntity.getProperties()) { + if (p.getDatatype() instanceof TextDatatype) { + result.put(p.getName(), p.getValue().toString()); + } + } + return result; } private EntityACL createStateACL(EntityACL entityACL) { LinkedList<EntityACI> rules = new LinkedList<>(); for (EntityACI aci : entityACL.getRules()) { - if (aci.getResponsibleAgent().toString().startsWith("?STATE?")) { + if (aci.getResponsibleAgent().toString().startsWith(ENTITY_STATE_ROLE_MARKER)) { int end = aci.getResponsibleAgent().toString().length() - 1; String role = aci.getResponsibleAgent().toString().substring(7, end); rules.add( @@ -289,6 +329,15 @@ public abstract class EntityStateJob extends EntityJob { @Override public void addToElement(Element ret) { Element e = new Element(STATE_XML_TAG); + if (this.stateProperties == null && this.stateEntity != null) { + this.stateProperties = createStateProperties(this.stateEntity); + } + if (this.stateProperties != null && this.stateProperties.size() > 0) { + this.stateProperties.forEach( + (String key, String value) -> { + e.setAttribute(key, value); + }); + } if (this.stateModelName != null) { e.setAttribute(STATE_ATTRIBUTE_MODEL, this.stateModelName); } @@ -318,7 +367,7 @@ public abstract class EntityStateJob extends EntityJob { ret.addContent(e); } - private String getStateModelName() { + public String getStateModelName() { return this.stateModelName; } @@ -452,7 +501,7 @@ public abstract class EntityStateJob extends EntityJob { * @throws Message if the transitions could ne be created. */ private Set<Transition> createTransitions(Property p) throws Message { - Set<Transition> result = new HashSet<>(); + Set<Transition> result = new LinkedHashSet<>(); try { if (!(p.getDatatype() instanceof AbstractCollectionDatatype)) { return result; @@ -486,7 +535,7 @@ public abstract class EntityStateJob extends EntityJob { private Set<State> getStates(Set<Transition> transitions, StateModel stateModel) throws Message { Iterator<Transition> it = transitions.iterator(); - Set<State> result = new HashSet<>(); + Set<State> result = new LinkedHashSet<>(); while (it.hasNext()) { Transition t = it.next(); result.add(t.getFromState()); @@ -657,7 +706,7 @@ public abstract class EntityStateJob extends EntityJob { EntityInterface stateEntity = cache.get("state" + Integer.toString(refid.getId())); boolean cached = true; if (stateEntity == null || !cached) { - stateEntity = retrieveValidSparseEntityById(refid.getId(), null); + stateEntity = retrieveValidEntity(refid.getId()); cache.put("state" + Integer.toString(refid.getId()), stateEntity); } diff --git a/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java b/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java index 5dec5d492a10f8e48a1c9877145c94e6db8b1534..2afcd0eeea96636b4a1a3da499b67cdc3a5c591e 100644 --- a/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java +++ b/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java @@ -76,7 +76,9 @@ public class InitEntityStateJobs extends EntityStateJob implements Observer { oldState = null; if (states.size() == 1) { oldState = states.get(0); - ((UpdateEntity) getEntity()).getOriginal().setEntityACL(getEntity().getEntityACL()); + if (newState != null) { + ((UpdateEntity) getEntity()).getOriginal().setEntityACL(getEntity().getEntityACL()); + } } if (!Objects.equals(newState, oldState)) { getEntity().acceptObserver(this);