diff --git a/caosdb-proto b/caosdb-proto index 75e826bd318c39e63d324f71e035f08355ffc51f..73d85fb20bb16902c0a89dda697eed17994712bc 160000 --- a/caosdb-proto +++ b/caosdb-proto @@ -1 +1 @@ -Subproject commit 75e826bd318c39e63d324f71e035f08355ffc51f +Subproject commit 73d85fb20bb16902c0a89dda697eed17994712bc diff --git a/pom.xml b/pom.xml index 493e02490eea6cd32f95eb3bd356cf6507de6073..5a2e9f9d60a8083b99ee61f2cefed59bde473da9 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.caosdb</groupId> <artifactId>caosdb-server</artifactId> - <version>0.5.0-GRPC0.0.18</version> + <version>0.5.0-GRPC0.0.19</version> <packaging>jar</packaging> <name>CaosDB Server</name> <scm> diff --git a/src/main/java/org/caosdb/server/entity/wrapper/Property.java b/src/main/java/org/caosdb/server/entity/wrapper/Property.java index b3ce45b56527e40382d3971a61b43f9bc054d21f..064e2bd31fd329477d712c723ecd6462e94d3933 100644 --- a/src/main/java/org/caosdb/server/entity/wrapper/Property.java +++ b/src/main/java/org/caosdb/server/entity/wrapper/Property.java @@ -50,6 +50,7 @@ public class Property extends EntityWrapper { super(new Entity()); } + /** Return the Property Index, the index of a property with respect to a containing Entity. */ public int getPIdx() { return this.pIdx; } @@ -58,6 +59,7 @@ public class Property extends EntityWrapper { private EntityInterface domain = null; private boolean isName; + /** Set the Property Index. */ public void setPIdx(final int i) { this.pIdx = i; } diff --git a/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java b/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java index d0c659679390fa0dbf2ff3f939b7062c38a63f90..7f5434df15bd3653462c702a6a500f699e28f69b 100644 --- a/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java @@ -40,6 +40,7 @@ import org.caosdb.api.entity.v1alpha1.UpdateRequest; import org.caosdb.api.entity.v1alpha1.UpdateResponse; import org.caosdb.api.entity.v1alpha1.Version; import org.caosdb.datetime.DateTimeInterface; +import org.caosdb.server.CaosDBException; import org.caosdb.server.datatype.AbstractCollectionDatatype; import org.caosdb.server.datatype.AbstractDatatype; import org.caosdb.server.datatype.BooleanDatatype; @@ -427,7 +428,7 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa private org.caosdb.api.entity.v1alpha1.ReferenceDataType.Builder convertReferenceDatatype( final ReferenceDatatype datatype) { - return ReferenceDataType.newBuilder().setName(datatype.toString()); + return ReferenceDataType.newBuilder().setName(datatype.getName()); } public Iterable<? extends org.caosdb.api.entity.v1alpha1.Property> convert( @@ -469,6 +470,13 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa return Integer.parseInt(id); } + /** + * Handle read-only transactions. + * + * @param request + * @return + * @throws Exception + */ public MultiTransactionResponse retrieve(final MultiTransactionRequest request) throws Exception { final MultiTransactionResponse.Builder builder = MultiTransactionResponse.newBuilder(); final RetrieveContainer container = @@ -477,6 +485,12 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa FileDownload file_download = null; for (final TransactionRequest sub_request : request.getRequestsList()) { + if (sub_request.getWrappedRequestsCase() != WrappedRequestsCase.RETRIEVE_REQUEST) { + throw new CaosDBException( + "Cannot process a " + + sub_request.getWrappedRequestsCase().name() + + " in a read-only request."); + } final boolean fileDownload = sub_request.getRetrieveRequest().getRegisterFileDownload(); if (sub_request.getRetrieveRequest().hasQuery() && !sub_request.getRetrieveRequest().getQuery().getQuery().isBlank()) { @@ -563,104 +577,205 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa return System.currentTimeMillis(); } + /** + * Handle all entity transactions. + * + * @param request + * @return + * @throws Exception + */ public MultiTransactionResponse transaction(final MultiTransactionRequest request) throws Exception { - // we currently can only process one request type per multi transaction request. - WrappedRequestsCase requestCase = WrappedRequestsCase.WRAPPEDREQUESTS_NOT_SET; - if (request.getRequestsCount() > 0) { - requestCase = request.getRequests(0).getWrappedRequestsCase(); - } - - switch (requestCase) { - case RETRIEVE_REQUEST: - return retrieve(request); - case DELETE_REQUEST: - return delete(request); - default: - return MultiTransactionResponse.newBuilder() - .addResponses(singleTransaction(request.getRequests(0))) - .build(); - } - } - - public TransactionResponse singleTransaction(final TransactionRequest request) throws Exception { - final WrappedRequestsCase requestCase = request.getWrappedRequestsCase(); - switch (requestCase) { - case INSERT_REQUEST: - return TransactionResponse.newBuilder() - .setInsertResponse(insert(request.getInsertRequest())) - .build(); - case UPDATE_REQUEST: - return TransactionResponse.newBuilder() - .setUpdateResponse(update(request.getUpdateRequest())) - .build(); - default: - throw new UnsupportedOperationException("Not implemented"); + // we only test the first request and raise errors when subsequent sub-transactions do not + // fit. + final WrappedRequestsCase requestCase = request.getRequests(0).getWrappedRequestsCase(); + switch (requestCase) { + case RETRIEVE_REQUEST: + // Handle read-only transactions. + return retrieve(request); + default: + // Handle mixed-writed transactions. + return write(request); + } + } else { + // empty request, empty response. + return MultiTransactionResponse.newBuilder().build(); } } - private MultiTransactionResponse delete(final MultiTransactionRequest requests) throws Exception { + /** + * Handle mixed-write transactions. + * + * <p>The current implementation fails fast, without attempts to execute a single request, if + * there are requests with non-integer IDs. This will change in the near future, once string IDs + * are supported by the server. + * + * @param requests + * @return + * @throws Exception + */ + private MultiTransactionResponse write(final MultiTransactionRequest requests) throws Exception { final MultiTransactionResponse.Builder builder = MultiTransactionResponse.newBuilder(); final WritableContainer container = new WritableContainer( SecurityUtils.getSubject(), getTimestamp(), getSRID(), new HashMap<String, String>()); - for (final TransactionRequest request : requests.getRequestsList()) { - final DeleteRequest deleteRequest = request.getDeleteRequest(); - final String id = deleteRequest.getId(); - try { - final DeleteEntity entity = new DeleteEntity(getId(id)); - container.add(entity); - } catch (final NumberFormatException e) { - // ID wasn't an integer - we handle this below + // put entities into the transaction object + for (final TransactionRequest subRequest : requests.getRequestsList()) { + switch (subRequest.getWrappedRequestsCase()) { + case INSERT_REQUEST: + { + final InsertRequest insertRequest = subRequest.getInsertRequest(); + final Entity insertEntity = insertRequest.getEntityRequest().getEntity(); + + final InsertEntity entity = + new InsertEntity( + insertEntity.getName().isEmpty() ? null : insertEntity.getName(), + convert(insertEntity.getRole())); + convert(insertEntity, entity); + addFileUpload(container, entity, insertRequest.getEntityRequest()); + container.add(entity); + } + break; + case UPDATE_REQUEST: + final UpdateRequest updateRequest = subRequest.getUpdateRequest(); + final Entity updateEntity = updateRequest.getEntityRequest().getEntity(); + + try { + final UpdateEntity entity = + new UpdateEntity(getId(updateEntity.getId()), convert(updateEntity.getRole())); + entity.setName(updateEntity.getName().isEmpty() ? null : updateEntity.getName()); + convert(updateEntity, entity); + addFileUpload(container, entity, updateRequest.getEntityRequest()); + container.add(entity); + } catch (final NumberFormatException e) { + // ID wasn't an integer + return failedWriteDueToStringId(requests); + } + break; + case DELETE_REQUEST: + final DeleteRequest deleteRequest = subRequest.getDeleteRequest(); + try { + final DeleteEntity entity = new DeleteEntity(getId(deleteRequest.getId())); + container.add(entity); + + } catch (final NumberFormatException e) { + // ID wasn't an integer + return failedWriteDueToStringId(requests); + } + break; + default: + throw new CaosDBException( + "Cannot process a " + + subRequest.getWrappedRequestsCase().name() + + " in a write request."); } } + // execute the transaction final WriteTransaction transaction = new WriteTransaction(container); + transaction.setNoIdIsError(false); transaction.execute(); + // put inserted/updated/deleted entities back into the response for (final EntityInterface entity : container) { - final IdResponse.Builder idResponse = - IdResponse.newBuilder().setId(entity.getId().toString()); + final IdResponse.Builder idResponse = IdResponse.newBuilder(); + if (entity.getId() != null) { + idResponse.setId(entity.getId().toString()); + } appendMessages(entity, idResponse); - builder - .addResponsesBuilder() - .setDeleteResponse(DeleteResponse.newBuilder().setIdResponse(idResponse)); + if (entity instanceof InsertEntity) { + builder + .addResponsesBuilder() + .setInsertResponse(InsertResponse.newBuilder().setIdResponse(idResponse)); + } else if (entity instanceof UpdateEntity) { + builder + .addResponsesBuilder() + .setUpdateResponse(UpdateResponse.newBuilder().setIdResponse(idResponse)); + } else { + builder + .addResponsesBuilder() + .setDeleteResponse(DeleteResponse.newBuilder().setIdResponse(idResponse)); + } } + return builder.build(); + } - // Add those entities which have not been retrieved because the have a string id - for (final TransactionRequest sub_request : requests.getRequestsList()) { - final String id = sub_request.getRetrieveRequest().getId(); - if (!id.isBlank()) { - try { - getId(id); - } catch (final NumberFormatException e) { - // ID wasn't an integer - the server doesn't support string id's yet, so that entity - // cannot exist. - final IdResponse.Builder idResponse = IdResponse.newBuilder().setId(id); - idResponse.addErrors(convert(ServerMessages.ENTITY_DOES_NOT_EXIST)); - builder.addResponses( - TransactionResponse.newBuilder() - .setDeleteResponse(DeleteResponse.newBuilder().setIdResponse(idResponse))); - } + /** + * Handle a request which contains string id (which cannot be converted to integer ids) and return + * a response which has the "ENTITY_DOES_NOT_EXIST" error for all entities with affected ids. + * + * <p>This does not attempt to execute a single request. + * + * @param request + * @return + */ + private MultiTransactionResponse failedWriteDueToStringId(final MultiTransactionRequest request) { + final org.caosdb.api.entity.v1alpha1.MultiTransactionResponse.Builder builder = + MultiTransactionResponse.newBuilder(); + for (final TransactionRequest subRequest : request.getRequestsList()) { + final IdResponse.Builder idResponse = IdResponse.newBuilder(); + switch (subRequest.getWrappedRequestsCase()) { + case INSERT_REQUEST: + builder + .addResponsesBuilder() + .setInsertResponse(InsertResponse.newBuilder().setIdResponse(idResponse)); + + break; + case UPDATE_REQUEST: + final UpdateRequest updateRequest = subRequest.getUpdateRequest(); + final Entity updateEntity = updateRequest.getEntityRequest().getEntity(); + + idResponse.setId(updateEntity.getId()); + try { + getId(updateEntity.getId()); + } catch (final NumberFormatException e) { + // ID wasn't an integer + idResponse.addErrors(convert(ServerMessages.ENTITY_DOES_NOT_EXIST)); + } + builder + .addResponsesBuilder() + .setUpdateResponse(UpdateResponse.newBuilder().setIdResponse(idResponse)); + break; + case DELETE_REQUEST: + final DeleteRequest deleteRequest = subRequest.getDeleteRequest(); + idResponse.setId(deleteRequest.getId()); + try { + getId(deleteRequest.getId()); + } catch (final NumberFormatException e) { + // ID wasn't an integer + idResponse.addErrors(convert(ServerMessages.ENTITY_DOES_NOT_EXIST)); + } + builder + .addResponsesBuilder() + .setDeleteResponse(DeleteResponse.newBuilder().setIdResponse(idResponse)); + break; + default: + throw new CaosDBException( + "Cannot process a " + + subRequest.getWrappedRequestsCase().name() + + " in a write request."); } } return builder.build(); } - private InsertResponse insert(final InsertRequest insertRequest) throws Exception { - final EntityRequest entityRequest = insertRequest.getEntityRequest(); - final Entity insertEntity = entityRequest.getEntity(); - final InsertEntity entity = - new InsertEntity( - insertEntity.getName().isEmpty() ? null : insertEntity.getName(), - convert(insertEntity.getRole())); - - return InsertResponse.newBuilder() - .setIdResponse(write(convert(insertEntity, entity), entityRequest)) - .build(); + private void addFileUpload( + final WritableContainer container, + final EntityInterface entity, + final EntityRequest entityRequest) { + if (entityRequest.hasUploadId()) { + final FileProperties uploadFile = + fileTransmissionService.getUploadFile(entityRequest.getUploadId()); + if (uploadFile == null) { + entity.addError(ServerMessages.FILE_HAS_NOT_BEEN_UPLOAED); + } else { + container.addFile(uploadFile.getTmpIdentifyer(), uploadFile); + entity.getFileProperties().setTmpIdentifyer(uploadFile.getTmpIdentifyer()); + } + } } private Role convert(final EntityRole role) { @@ -678,57 +793,6 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa } } - private UpdateResponse update(final UpdateRequest updateRequest) throws Exception { - final EntityRequest entityRequest = updateRequest.getEntityRequest(); - final Entity updateEntity = entityRequest.getEntity(); - final String id = updateEntity.getId(); - try { - final UpdateEntity entity = new UpdateEntity(getId(id), convert(updateEntity.getRole())); - entity.setName(updateEntity.getName().isEmpty() ? null : updateEntity.getName()); - - return UpdateResponse.newBuilder() - .setIdResponse(write(convert(updateEntity, entity), entityRequest)) - .build(); - } catch (final NumberFormatException e) { - // ID wasn't an integer - the server doesn't support string id's yet, so that entity cannot - // exist. - return UpdateResponse.newBuilder() - .setIdResponse( - IdResponse.newBuilder() - .setId(id) - .addErrors(convert(ServerMessages.ENTITY_DOES_NOT_EXIST))) - .build(); - } - } - - private IdResponse write(final EntityInterface entity, final EntityRequest entityRequest) - throws Exception { - - final org.caosdb.api.entity.v1alpha1.IdResponse.Builder builder = IdResponse.newBuilder(); - final WritableContainer container = - new WritableContainer( - SecurityUtils.getSubject(), getTimestamp(), getSRID(), new HashMap<>()); - container.add(entity); - if (entityRequest.hasUploadId()) { - final FileProperties uploadFile = - fileTransmissionService.getUploadFile(entityRequest.getUploadId()); - if (uploadFile == null) { - entity.addError(ServerMessages.FILE_HAS_NOT_BEEN_UPLOAED); - } else { - container.addFile(uploadFile.getTmpIdentifyer(), uploadFile); - entity.getFileProperties().setTmpIdentifyer(uploadFile.getTmpIdentifyer()); - } - } - final WriteTransaction transaction = new WriteTransaction(container); - transaction.execute(); - if (entity.hasId()) { - builder.setId(Integer.toString(entity.getId())); - } - appendMessages(entity, builder); - - return builder.build(); - } - private void appendMessages( final EntityInterface from, final org.caosdb.api.entity.v1alpha1.EntityResponse.Builder builder) { @@ -774,6 +838,7 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa } private EntityInterface convert(final Entity from, final EntityInterface entity) { + entity.setName(from.getName().isEmpty() ? null : from.getName()); entity.setDescription(from.getDescription().isBlank() ? null : from.getDescription()); if (!from.getUnit().isBlank()) { entity.addProperty(getUnit(from.getUnit())); diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java index e663874189ee0301a5b30452468391711624b606..fa1c6fd36771e0ddcd08b330724ef1d6aa725956 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java @@ -60,7 +60,7 @@ public class CheckParValid extends EntityJob { // The parent has neither an id nor a name. // Therefore it cannot be identified. - throw ServerMessages.ENTITY_HAS_NO_NAME_AND_NO_ID; + throw ServerMessages.ENTITY_HAS_NO_NAME_OR_ID; } if (parent.hasId()) { @@ -139,8 +139,8 @@ public class CheckParValid extends EntityJob { if (par.getEntityStatus() != EntityStatus.IGNORE) { for (final Parent par2 : getEntity().getParents()) { if (par != par2 && par2.getEntityStatus() != EntityStatus.IGNORE) { - if ((par.hasId() && par2.hasId() && par.getId().equals(par2.getId())) - || (par.hasName() && par2.hasName() && par.getName().equals(par2.getName()))) { + if (par.hasId() && par2.hasId() && par.getId().equals(par2.getId()) + || par.hasName() && par2.hasName() && par.getName().equals(par2.getName())) { if (!Objects.equal(par.getFlag("inheritance"), par2.getFlag("inheritance"))) { getEntity().addError(ServerMessages.PARENT_DUPLICATES_ERROR); getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java index 62a9beea1921f34dd3646bcc78ed9086d05e6098..daaf78ec58de00553b0a15e040e5c5435265ec0c 100644 --- a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java @@ -24,6 +24,7 @@ package org.caosdb.server.transaction; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; import org.apache.shiro.SecurityUtils; @@ -67,10 +68,17 @@ import org.caosdb.server.utils.ServerMessages; public class WriteTransaction extends Transaction<WritableContainer> implements WriteTransactionInterface { + private boolean noIdIsError = true; + public WriteTransaction(final WritableContainer container) { super(container); } + /** Set if it is an error, if an Entity has no ID but just a name upon update. */ + public void setNoIdIsError(final boolean noIdIsError) { + this.noIdIsError = noIdIsError; + } + @Override protected final void preTransaction() throws InterruptedException { // Acquire strong access. No other thread can have access until this strong access is released. @@ -288,7 +296,7 @@ public class WriteTransaction extends Transaction<WritableContainer> entity.setEntityStatus(EntityStatus.UNQUALIFIED); entity.addError(ServerMessages.AUTHORIZATION_ERROR); entity.addInfo(exc.getMessage()); - } catch (ClassCastException exc) { + } catch (final ClassCastException exc) { // not an update entity. ignore. } @@ -308,10 +316,10 @@ public class WriteTransaction extends Transaction<WritableContainer> // split up the TransactionContainer into three containers, one for each // type of writing transaction. - TransactionContainer inserts = new TransactionContainer(); - TransactionContainer updates = new TransactionContainer(); - TransactionContainer deletes = new TransactionContainer(); - for (EntityInterface entity : getContainer()) { + final TransactionContainer inserts = new TransactionContainer(); + final TransactionContainer updates = new TransactionContainer(); + final TransactionContainer deletes = new TransactionContainer(); + for (final EntityInterface entity : getContainer()) { if (entity instanceof InsertEntity) { inserts.add(entity); } else if (entity instanceof UpdateEntity) { @@ -354,7 +362,7 @@ public class WriteTransaction extends Transaction<WritableContainer> * @throws IOException * @throws NoSuchAlgorithmException */ - public static HashSet<Permission> deriveUpdate( + public HashSet<Permission> deriveUpdate( final EntityInterface newEntity, final EntityInterface oldEntity) throws NoSuchAlgorithmException, IOException, CaosDBException { final HashSet<Permission> needPermissions = new HashSet<>(); @@ -461,46 +469,34 @@ public class WriteTransaction extends Transaction<WritableContainer> } // properties - outerLoop: - for (final EntityInterface newProperty : newEntity.getProperties()) { - - // find corresponding oldProperty for this new property and make a - // diff. - if (newProperty.hasId()) { - for (final EntityInterface oldProperty : oldEntity.getProperties()) { - if (newProperty.getId().equals(oldProperty.getId())) { - // do not check again. - oldEntity.getProperties().remove(oldProperty); - - if (((Property) oldProperty).getPIdx() != ((Property) newProperty).getPIdx()) { - // change order of properties - needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY); - needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY); - updatetable = true; - } - - deriveUpdate(newProperty, oldProperty); - if (newProperty.getEntityStatus() == EntityStatus.QUALIFIED) { - needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY); - needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY); - updatetable = true; - } + for (final Property newProperty : newEntity.getProperties()) { + + // find corresponding oldProperty for this new property and make a diff (existing property, + // same property index in this entity, equal content?). + final Property oldProperty = findOldEntity(newProperty, oldEntity.getProperties()); + if (oldProperty != null) { + // do not check again. + oldEntity.getProperties().remove(oldProperty); + + if (oldProperty.getPIdx() != newProperty.getPIdx()) { + // change order of properties + needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY); + needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY); + updatetable = true; + } - continue outerLoop; - } + deriveUpdate(newProperty, oldProperty); + if (newProperty.getEntityStatus() == EntityStatus.QUALIFIED) { + needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY); + needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY); + updatetable = true; } + } else { - newProperty.setEntityStatus(EntityStatus.UNQUALIFIED); - newProperty.addError(ServerMessages.ENTITY_HAS_NO_ID); - newProperty.addInfo("On updates, allways specify the id not just the name."); - newEntity.addError(ServerMessages.ENTITY_HAS_UNQUALIFIED_PROPERTIES); - newEntity.setEntityStatus(EntityStatus.UNQUALIFIED); - return needPermissions; + // no corresponding property found -> this property is new. + needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY); + updatetable = true; } - - // no corresponding property found -> this property is new. - needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY); - updatetable = true; } // some old properties left (and not matched with new ones) -> there are @@ -511,30 +507,20 @@ public class WriteTransaction extends Transaction<WritableContainer> } // update parents - outerLoop: for (final Parent newParent : newEntity.getParents()) { // find corresponding oldParent - if (newParent.hasId()) { - for (final Parent oldParent : oldEntity.getParents()) { - if (oldParent.getId().equals(newParent.getId())) { - // still there! do not check this one again - oldEntity.getParents().remove(oldParent); - continue outerLoop; - } - } + final Parent oldProperty = findOldEntity(newParent, oldEntity.getParents()); + + if (oldProperty != null) { + // do not check again. + oldEntity.getParents().remove(oldProperty); + } else { - newParent.setEntityStatus(EntityStatus.UNQUALIFIED); - newParent.addError(ServerMessages.ENTITY_HAS_NO_ID); - newParent.addInfo("On updates, allways specify the id not just the name."); - newEntity.addError(ServerMessages.ENTITY_HAS_UNQUALIFIED_PROPERTIES); - newEntity.setEntityStatus(EntityStatus.UNQUALIFIED); - return needPermissions; + // no corresponding parent found -> this parent is new. + needPermissions.add(EntityPermission.UPDATE_ADD_PARENT); + updatetable = true; } - - // no corresponding parent found -> this parent is new. - needPermissions.add(EntityPermission.UPDATE_ADD_PARENT); - updatetable = true; } // some old parents left (and not matched with new ones) -> there are @@ -553,6 +539,35 @@ public class WriteTransaction extends Transaction<WritableContainer> return needPermissions; } + /** + * Attempt to find a (sparse) entity among a list of entities. + * + * <p>If no match by ID can be found, matching by name is attempted next, but only if noIdIsError + * is false. + */ + private <T extends EntityInterface> T findOldEntity( + final EntityInterface newEntity, final List<T> oldEntities) { + if (newEntity.hasId()) { + for (final T oldEntity : oldEntities) { + if (Objects.equals(oldEntity.getId(), newEntity.getId())) { + return oldEntity; + } + } + } else if (noIdIsError) { + newEntity.addError(ServerMessages.ENTITY_HAS_NO_ID); + newEntity.addInfo("On updates, always specify the id not just the name."); + } else if (newEntity.hasName()) { + for (final T oldEntity : oldEntities) { + if (oldEntity.getName().equals(newEntity.getName())) { + return oldEntity; + } + } + } else { + newEntity.addError(ServerMessages.ENTITY_HAS_NO_NAME_OR_ID); + } + return null; + } + public String getSRID() { return getContainer().getRequestId(); } diff --git a/src/main/java/org/caosdb/server/utils/ServerMessages.java b/src/main/java/org/caosdb/server/utils/ServerMessages.java index cbca0b2dca1d967536be596547f57c20a2e7593f..a12b9bc4b7c031f39a4aad99e131439440cde621 100644 --- a/src/main/java/org/caosdb/server/utils/ServerMessages.java +++ b/src/main/java/org/caosdb/server/utils/ServerMessages.java @@ -74,9 +74,6 @@ public class ServerMessages { public static final Message NAME_DUPLICATES = new Message(MessageType.Error, 0, "Entity can not be identified due to name duplicates."); - public static final Message ENTITY_HAS_NO_NAME_AND_NO_ID = - new Message(MessageType.Error, 0, "Entity has no name and no ID."); - public static final Message ENTITY_HAS_NO_PROPERTIES = new Message(MessageType.Error, 0, "Entity has no properties."); diff --git a/src/test/java/org/caosdb/server/transaction/UpdateTest.java b/src/test/java/org/caosdb/server/transaction/UpdateTest.java index d22fbea8982b1f8d88c9ccdf069a0c8a816add25..fb121c260a7706e806ddc73e9005ea4a440f1510 100644 --- a/src/test/java/org/caosdb/server/transaction/UpdateTest.java +++ b/src/test/java/org/caosdb/server/transaction/UpdateTest.java @@ -29,22 +29,29 @@ import static org.junit.Assert.assertEquals; import java.io.IOException; import java.security.NoSuchAlgorithmException; import org.caosdb.server.CaosDBException; +import org.caosdb.server.CaosDBServer; import org.caosdb.server.datatype.GenericValue; import org.caosdb.server.entity.Entity; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.StatementStatus; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.utils.EntityStatus; +import org.junit.BeforeClass; import org.junit.Test; public class UpdateTest { + @BeforeClass + public static void setup() throws IOException { + CaosDBServer.initServerProperties(); + } + @Test public void testDeriveUpdate_SameName() throws NoSuchAlgorithmException, IOException, CaosDBException { final Entity newEntity = new Entity("Name"); final Entity oldEntity = new Entity("Name"); - WriteTransaction.deriveUpdate(newEntity, oldEntity); + new WriteTransaction(null).deriveUpdate(newEntity, oldEntity); assertEquals(newEntity.getEntityStatus(), EntityStatus.VALID); } @@ -53,7 +60,7 @@ public class UpdateTest { throws NoSuchAlgorithmException, IOException, CaosDBException { final Entity newEntity = new Entity("NewName"); final Entity oldEntity = new Entity("OldName"); - WriteTransaction.deriveUpdate(newEntity, oldEntity); + new WriteTransaction(null).deriveUpdate(newEntity, oldEntity); assertEquals(newEntity.getEntityStatus(), EntityStatus.QUALIFIED); } @@ -67,7 +74,7 @@ public class UpdateTest { final Entity oldEntity = new Entity(); oldEntity.addProperty(oldProperty); - WriteTransaction.deriveUpdate(newEntity, oldEntity); + new WriteTransaction(null).deriveUpdate(newEntity, oldEntity); assertEquals(newEntity.getEntityStatus(), VALID); } @@ -83,7 +90,7 @@ public class UpdateTest { final Entity oldEntity = new Entity(); oldEntity.addProperty(oldProperty); - WriteTransaction.deriveUpdate(newEntity, oldEntity); + new WriteTransaction(null).deriveUpdate(newEntity, oldEntity); assertEquals(newEntity.getEntityStatus(), QUALIFIED); assertEquals(newProperty.getEntityStatus(), VALID); assertEquals(newProperty2.getEntityStatus(), QUALIFIED); @@ -124,7 +131,7 @@ public class UpdateTest { oldEntity.addProperty(oldProperty); - WriteTransaction.deriveUpdate(newEntity, oldEntity); + new WriteTransaction(null).deriveUpdate(newEntity, oldEntity); assertEquals(newUnit.getEntityStatus(), VALID); assertEquals(newProperty.getEntityStatus(), VALID); assertEquals(newEntity.getEntityStatus(), VALID); @@ -165,7 +172,7 @@ public class UpdateTest { oldEntity.addProperty(oldProperty); - WriteTransaction.deriveUpdate(newEntity, oldEntity); + new WriteTransaction(null).deriveUpdate(newEntity, oldEntity); assertEquals(newEntity.getEntityStatus(), QUALIFIED); assertEquals(newProperty.getEntityStatus(), QUALIFIED); }