diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java index ec0c0c1b0a067d529ecb72309d7fe83a8b37f819..7af4022595f64b909231c72c83ae1e151a9d06c1 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java @@ -27,10 +27,14 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import org.apache.shiro.SecurityUtils; +import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.RetrieveAllImpl; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.entity.Role; +import org.caosdb.server.permissions.EntityACL; +import org.caosdb.server.permissions.EntityPermission; public class MySQLRetrieveAll extends MySQLTransaction implements RetrieveAllImpl { @@ -38,17 +42,20 @@ public class MySQLRetrieveAll extends MySQLTransaction implements RetrieveAllImp super(access); } - public static final String STMT_GET_ALL_HEAD = "Select id from entities where id > 99"; + public static final String STMT_GET_ALL_HEAD = + "SELECT e.id AS ID, a.acl AS ACL FROM entities AS e JOIN entity_acl AS a ON (e.acl = a.id) WHERE e.id > 99"; public static final String STMT_ENTITY_WHERE_CLAUSE = - " AND ( role=? OR role='" + " AND ( e.role='" + + Role.Record + + "' OR e.role='" + Role.RecordType - + "' OR role='" + + "' OR e.role='" + Role.Property - + "' OR role='" + + "' OR e.role='" + Role.File + "'" + " )"; - public static final String STMT_OTHER_ROLES = " AND role=?"; + public static final String STMT_OTHER_ROLES = " AND e.role=?"; @Override public List<Integer> execute(final String role) throws TransactionException { @@ -58,10 +65,7 @@ public class MySQLRetrieveAll extends MySQLTransaction implements RetrieveAllImp + (role.equalsIgnoreCase("ENTITY") ? STMT_ENTITY_WHERE_CLAUSE : STMT_OTHER_ROLES); final PreparedStatement stmt = prepareStatement(STMT_GET_ALL); - if (role.equalsIgnoreCase("ENTITY")) { - stmt.setString(1, Role.Record.toString()); - - } else { + if (!role.equalsIgnoreCase("ENTITY")) { stmt.setString(1, role); } @@ -69,7 +73,11 @@ public class MySQLRetrieveAll extends MySQLTransaction implements RetrieveAllImp try { final ArrayList<Integer> ret = new ArrayList<Integer>(); while (rs.next()) { - ret.add(rs.getInt(1)); + String acl = DatabaseUtils.bytes2UTF8(rs.getBytes("ACL")); + if (EntityACL.deserialize(acl) + .isPermitted(SecurityUtils.getSubject(), EntityPermission.RETRIEVE_ENTITY)) { + ret.add(rs.getInt("ID")); + } } return ret; } finally { diff --git a/src/main/java/org/caosdb/server/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java index 27570ec81636c7bb9a20bde04fb68f07b4956fea..5522b9be33f2225412c0ba473d7c5c0eec2e4507 100644 --- a/src/main/java/org/caosdb/server/entity/Entity.java +++ b/src/main/java/org/caosdb/server/entity/Entity.java @@ -36,6 +36,7 @@ import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.CaosDBException; +import org.caosdb.server.accessControl.Principal; import org.caosdb.server.database.proto.SparseEntity; import org.caosdb.server.database.proto.VerySparseEntity; import org.caosdb.server.datatype.AbstractCollectionDatatype; @@ -104,10 +105,12 @@ public class Entity extends AbstractObservable implements EntityInterface { public void checkPermission(final Subject subject, final Permission permission) { try { if (!this.hasPermission(subject, permission)) { + String user = "The current user "; + if (subject.getPrincipal() instanceof Principal) { + user = ((Principal) subject.getPrincipal()).getUsername(); + } throw new AuthorizationException( - subject.getPrincipal().toString() - + " doesn't have permission " - + permission.toString()); + user + " doesn't have permission " + permission.toString()); } } catch (final NullPointerException e) { throw new AuthorizationException("This entity doesn't have an ACL!"); 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 86b65fa8b8e875b700b947b8e63bf175e7a7c511..7a7056eb79ce1b5a322a71492cc483821a3b8de7 100644 --- a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java +++ b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java @@ -25,12 +25,14 @@ 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; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import org.apache.shiro.subject.Subject; @@ -288,12 +290,6 @@ public abstract class EntityStateJob extends EntityJob { private EntityACL stateACL = null; private Map<String, String> stateProperties; - public State(String stateName, String stateModelName) throws Message { - // TODO Make constructors private or protected and outsource to caching factory methods. - this.stateName = stateName; - this.stateModelName = stateModelName; - } - public State(EntityInterface stateEntity, EntityInterface stateModelEntity) throws Message { this.stateEntity = stateEntity; this.stateDescription = stateEntity.getDescription(); @@ -364,10 +360,7 @@ 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) { + if (this.stateProperties != null) { this.stateProperties.forEach( (String key, String value) -> { e.setAttribute(key, value); @@ -385,19 +378,13 @@ public abstract class EntityStateJob extends EntityJob { if (this.stateId != null) { e.setAttribute(STATE_ATTRIBUTE_ID, Integer.toString(this.stateId)); } - if (this.stateModelEntity != null) { - try { - this.getStateModel() - .transitions - .forEach( - (Transition t) -> { - if (t.isFromState(this) && t.isPermitted(getUser())) { - e.addContent(t.toElement()); - } - }); - } catch (Message m) { - getEntity().addError(m); - } + if (this.stateModel != null) { + this.stateModel.transitions.forEach( + (Transition t) -> { + if (t.isFromState(this) && t.isPermitted(getUser())) { + e.addContent(t.toElement()); + } + }); } ret.addContent(e); } @@ -412,7 +399,7 @@ public abstract class EntityStateJob extends EntityJob { public StateModel getStateModel() throws Message { if (this.stateModel == null) { - this.stateModel = new StateModel(getStateModelEntity()); + this.stateModel = new StateModel(this.stateModelEntity); } return this.stateModel; } @@ -448,20 +435,11 @@ public abstract class EntityStateJob extends EntityJob { return stateProperty; } - public EntityInterface getStateEntity() throws Message { - if (this.stateEntity == null) { - this.stateEntity = retrieveStateEntity(this.getStateName()); - this.stateDescription = this.stateEntity.getDescription(); - this.stateId = this.stateEntity.getId(); - this.stateACL = createStateACL(this.stateEntity.getEntityACL()); - } + public EntityInterface getStateEntity() { return this.stateEntity; } - public EntityInterface getStateModelEntity() throws Message { - if (this.stateModelEntity == null) { - this.stateModelEntity = retrieveStateModelEntity(this.getStateModelName()); - } + public EntityInterface getStateModelEntity() { return this.stateModelEntity; } @@ -570,7 +548,13 @@ public abstract class EntityStateJob extends EntityJob { for (IndexedSingleValue val : vals) { if (val.getWrapped() instanceof ReferenceValue) { Integer refid = ((ReferenceValue) val.getWrapped()).getId(); - EntityInterface transition = retrieveValidEntity(refid); + + String key = "transition" + Integer.toString(refid); + EntityInterface transition = getCached(key); + if (transition == null) { + transition = retrieveValidEntity(refid); + putCache(key, transition); + } result.add(new Transition(transition)); } } @@ -653,7 +637,7 @@ public abstract class EntityStateJob extends EntityJob { } } - protected EntityInterface retrieveStateEntity(String stateName) throws Message { + private EntityInterface retrieveStateEntity(String stateName) throws Message { try { return retrieveValidEntity(retrieveValidIDByName(stateName)); } catch (EntityDoesNotExistException e) { @@ -661,7 +645,7 @@ public abstract class EntityStateJob extends EntityJob { } } - protected EntityInterface retrieveStateModelEntity(String stateModel) throws Message { + private EntityInterface retrieveStateModelEntity(String stateModel) throws Message { try { return retrieveValidEntity(retrieveValidIDByName(stateModel)); } catch (EntityDoesNotExistException e) { @@ -670,7 +654,12 @@ public abstract class EntityStateJob extends EntityJob { } protected EntityInterface getStateRecordType() throws Message { - return retrieveValidSparseEntityByName(STATE_RECORD_TYPE_NAME); + EntityInterface stateRecordType = getCached(STATE_RECORD_TYPE_NAME); + if (stateRecordType == null) { + stateRecordType = retrieveValidSparseEntityByName(STATE_RECORD_TYPE_NAME); + putCache(STATE_RECORD_TYPE_NAME, stateRecordType); + } + return stateRecordType; } protected State getState() { @@ -724,11 +713,11 @@ public abstract class EntityStateJob extends EntityJob { /** Get the {@code ClientMessage}s which denote a state. */ protected List<ClientMessage> getStateClientMessages(EntityInterface entity, boolean remove) { - Iterator<Message> stateMessages = entity.getMessages(STATE_XML_TAG).iterator(); + Iterator<ToElementable> stateMessages = entity.getMessages().iterator(); List<ClientMessage> result = new ArrayList<>(); while (stateMessages.hasNext()) { - Message s = stateMessages.next(); - if (s instanceof ClientMessage) { + ToElementable s = stateMessages.next(); + if (s instanceof ClientMessage && STATE_XML_TAG.equals(((ClientMessage) s).getType())) { if (remove) { stateMessages.remove(); } @@ -751,7 +740,22 @@ public abstract class EntityStateJob extends EntityJob { if (stateName == null) { throw STATE_NOT_SPECIFIED; } - return new State(stateName, stateModel); + String stateModelKey = "statemodel:" + stateModel; + + EntityInterface stateModelEntity = getCached(stateModelKey); + if (stateModelEntity == null) { + stateModelEntity = retrieveStateModelEntity(stateModel); + putCache(stateModelKey, stateModelEntity); + } + + String stateKey = "namestate:" + stateName; + + EntityInterface stateEntity = getCached(stateKey); + if (stateEntity == null) { + stateEntity = retrieveStateEntity(stateName); + putCache(stateKey, stateEntity); + } + return new State(stateEntity, stateModelEntity); } /** @@ -765,15 +769,15 @@ public abstract class EntityStateJob extends EntityJob { * @throws Message */ protected State createState(Property p) throws Message { - // TODO this should be cached try { p.parseValue(); ReferenceValue refid = (ReferenceValue) p.getValue(); - EntityInterface stateEntity = cache.get("state" + Integer.toString(refid.getId())); - boolean cached = true; - if (stateEntity == null || !cached) { + String key = "idstate" + Integer.toString(refid.getId()); + + EntityInterface stateEntity = getCached(key); + if (stateEntity == null) { stateEntity = retrieveValidEntity(refid.getId()); - cache.put("state" + Integer.toString(refid.getId()), stateEntity); + putCache(key, stateEntity); } EntityInterface stateModelEntity = findStateModel(stateEntity); @@ -786,12 +790,13 @@ public abstract class EntityStateJob extends EntityJob { } private static final Map<String, EntityInterface> cache = new HashMap<>(); + private static final Set<Integer> id_in_cache = new HashSet<>(); EntityInterface findStateModel(EntityInterface stateEntity) throws Exception { - // TODO this should be cached - boolean cached = true; - EntityInterface result = cache.get("modelof" + Integer.toString(stateEntity.getId())); + String key = "modelof" + Integer.toString(stateEntity.getId()); + + EntityInterface result = getCached(key); if (result != null && cached) { return result; } @@ -809,7 +814,40 @@ public abstract class EntityStateJob extends EntityJob { c); query.execute(getTransaction().getAccess()); result = retrieveValidEntity(c.get(0).getId()); - cache.put("modelof" + Integer.toString(stateEntity.getId()), result); + putCache(key, result); + return result; + } + + private EntityInterface getCached(String key) { + EntityInterface result; + synchronized (cache) { + result = cache.get(key); + } return result; } + + private void putCache(String key, EntityInterface value) { + synchronized (cache) { + id_in_cache.add(value.getId()); + cache.put(key, value); + } + } + + protected void removeCached(EntityInterface entity) { + synchronized (cache) { + if (id_in_cache.contains(entity.getId())) { + id_in_cache.remove(entity.getId()); + + List<String> remove = new LinkedList<>(); + for (Entry<String, EntityInterface> entry : cache.entrySet()) { + if (entry.getValue().getId().equals(entity.getId())) { + remove.add(entry.getKey()); + } + } + for (String key : remove) { + cache.remove(key); + } + } + } + } } diff --git a/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java index e21d76c9e5e5f356026cb94d8d8a5f098caeaa8e..bda4b8ebe885aa49df13ecf36ab14663c7ead270 100644 --- a/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java +++ b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java @@ -16,6 +16,8 @@ public class InheritInitialState extends EntityStateJob { State parentState = getFirstParentState(); if (parentState != null) { getEntity().addMessage(parentState); + parentState.getStateEntity(); + getEntity().setEntityACL(parentState.getStateACL()); } } catch (Message e) { getEntity().addError(e); 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 5093d86a66a9524566fecc127b18fd034f607212..c9f1c7b7b6591ae58375f457ce7be9ae20c66a4b 100644 --- a/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java +++ b/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Objects; import org.caosdb.server.CaosDBServer; import org.caosdb.server.entity.ClientMessage; +import org.caosdb.server.entity.DeleteEntity; import org.caosdb.server.entity.Entity; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.InsertEntity; @@ -35,6 +36,7 @@ import org.caosdb.server.entity.Message; import org.caosdb.server.entity.Message.MessageType; import org.caosdb.server.entity.Role; import org.caosdb.server.entity.UpdateEntity; +import org.caosdb.server.entity.WritableEntity; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.JobExecutionTime; @@ -76,6 +78,9 @@ public class InitEntityStateJobs extends EntityStateJob implements Observer { appendJob(MakeStateProperty.class); appendJob(MakeStateMessage.class); } + if (getEntity() instanceof WritableEntity || getEntity() instanceof DeleteEntity) { + removeCached(getEntity()); + } } } @@ -127,7 +132,9 @@ public class InitEntityStateJobs extends EntityStateJob implements Observer { MessageType.Error, "Currently, each entity can only have one state at a time."); } else if (states.size() == 1) { newState = states.get(0); - transferEntityACL(getEntity(), newState); + if (getEntity().getRole() == Role.Record) { + transferEntityACL(getEntity(), newState); + } } } catch (Message m) { getEntity().addError(m); diff --git a/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java b/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java index b0fc4bad949588d3a34c64f9fc1d5acf6135d0cb..6e98704a5f6d73199993f2ed531e6d6277934e64 100644 --- a/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java +++ b/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java @@ -58,23 +58,15 @@ public class MakeStateMessage extends EntityStateJob { try { // fetch all state properties and remove them from the entity (indicated by "true") List<Property> stateProperties = getStateProperties(true); + State stateMessage = getState(false); - // only add the State during Retrieve. In all other cases, the State is already present. - if (stateProperties != null - && stateProperties.size() > 0 - && getTransaction() instanceof Retrieve) { - if (isSparse()) { - for (Property s : stateProperties) { - getEntity().addMessage(getSparseStateMessage(s)); - } - } else { - for (Property s : stateProperties) { - State stateMessage = createState(s); - stateMessage - .getStateModel(); // trigger retrieval of state model because when the XML Writer - // calls the addToElement method, it is to late. - getEntity().addMessage(stateMessage); - } + if (stateMessage != null) { + // trigger retrieval of state model because when the XML Writer calls the addToElement + // method, it is to late. + stateMessage.getStateModel(); + } else if (stateProperties != null && stateProperties.size() > 0) { + for (Property s : stateProperties) { + getEntity().addMessage(getMessage(s, isSparse())); } } } catch (Message e) { @@ -83,6 +75,18 @@ public class MakeStateMessage extends EntityStateJob { } } + private ToElementable getMessage(Property s, boolean sparse) throws Message { + if (sparse) { + return getSparseStateMessage(s); + } + State stateMessage = createState(s); + + // trigger retrieval of state model because when the XML Writer calls the addToElement method, + // it is to late. + stateMessage.getStateModel(); + return stateMessage; + } + private ToElementable getSparseStateMessage(Property s) { return new ToElementable() { @Override diff --git a/src/main/java/org/caosdb/server/permissions/EntityACL.java b/src/main/java/org/caosdb/server/permissions/EntityACL.java index df1915bd460079eb52dfc6d4f3bbf4fe42795918..5adaca78fc03e760a42f1715d2a6cfff56826fc9 100644 --- a/src/main/java/org/caosdb/server/permissions/EntityACL.java +++ b/src/main/java/org/caosdb/server/permissions/EntityACL.java @@ -379,7 +379,9 @@ public class EntityACL { public Element getPermissionsFor(final Subject subject) { final Element ret = new Element("Permissions"); - final Set<EntityPermission> permissionsFor = getPermissionsFor(subject, this.acl); + final List<EntityACI> localAcl = new ArrayList<>(this.acl); + localAcl.addAll(GLOBAL_PERMISSIONS.acl); + final Set<EntityPermission> permissionsFor = getPermissionsFor(subject, localAcl); for (final EntityPermission p : permissionsFor) { ret.addContent(p.toElement()); }