diff --git a/jargon-core/src/main/java/org/irods/jargon/core/packinstr/ModAvuMetadataInp.java b/jargon-core/src/main/java/org/irods/jargon/core/packinstr/ModAvuMetadataInp.java index 448ff7b89..f78500449 100644 --- a/jargon-core/src/main/java/org/irods/jargon/core/packinstr/ModAvuMetadataInp.java +++ b/jargon-core/src/main/java/org/irods/jargon/core/packinstr/ModAvuMetadataInp.java @@ -21,6 +21,8 @@ public class ModAvuMetadataInp extends AbstractIRODSPackingInstruction { public static final String ARG_PREFIX = "arg"; public static final int MOD_AVU_API_NBR = 706; + + public static final String ADMIN_KW = "irodsAdmin"; /** * Type of metadata to be modified @@ -42,6 +44,27 @@ public enum ActionType { private final AvuData avuData; private final AvuData newAvuData; private final ActionType actionType; + private boolean adminFlag; + + /** + * Create an instance of the packing instruction. + * + * Supports all use-cases. + * + * @param targetIdentifier The absolute logical path to the entity. + * @param metadataTargetType The entity type. + * @param avuData An AVU. Depends on the operation. + * @param newAvuData An AVU. Depends on the operation. Set to null if + * unused. + * @param actionType The AVU operation. + * @param adminFlag Execute operation using rodsadmin privileges. + * @return {@link ModAvuMetadataInp} + */ + public static final ModAvuMetadataInp instance(final String targetIdentifier, + final MetadataTargetType metadataTargetType, final AvuData avuData, final AvuData newAvuData, + final ActionType actionType, boolean adminFlag) { + return new ModAvuMetadataInp(targetIdentifier, metadataTargetType, avuData, newAvuData, actionType, adminFlag); + } /** * Create an instance of the packing instruction that will add the AVU to a @@ -335,6 +358,11 @@ public static final ModAvuMetadataInp instanceForDeleteUserMetadata(final String private ModAvuMetadataInp(final String targetIdentifier, final MetadataTargetType metadataTargetType, final AvuData avuData, final AvuData newAvuData, final ActionType actionType) { + this(targetIdentifier, metadataTargetType, avuData, newAvuData, actionType, false); + } + + private ModAvuMetadataInp(final String targetIdentifier, final MetadataTargetType metadataTargetType, + final AvuData avuData, final AvuData newAvuData, final ActionType actionType, final boolean adminFlag) { super(); if (targetIdentifier == null || targetIdentifier.isEmpty()) { @@ -358,6 +386,7 @@ private ModAvuMetadataInp(final String targetIdentifier, final MetadataTargetTyp this.avuData = avuData; this.actionType = actionType; this.newAvuData = newAvuData; + this.adminFlag = adminFlag; setApiNumber(MOD_AVU_API_NBR); @@ -433,6 +462,11 @@ public Tag getTagValue() throws JargonException { } List kvps = new ArrayList(); + + if (adminFlag) { + kvps.add(KeyValuePair.instance(ADMIN_KW, "")); + } + message.addTag(super.createKeyValueTag(kvps)); // take the arg list and compact the params @@ -460,5 +494,9 @@ public ActionType getActionType() { public AvuData getNewAvuData() { return newAvuData; } + + public boolean isAdminFlagEnabled() { + return adminFlag; + } -} +} \ No newline at end of file diff --git a/jargon-core/src/main/java/org/irods/jargon/core/pub/CollectionAO.java b/jargon-core/src/main/java/org/irods/jargon/core/pub/CollectionAO.java index da71f75a1..59012e71c 100644 --- a/jargon-core/src/main/java/org/irods/jargon/core/pub/CollectionAO.java +++ b/jargon-core/src/main/java/org/irods/jargon/core/pub/CollectionAO.java @@ -892,4 +892,48 @@ void setAccessPermissionToNotInheritInAdminMode(String zone, String absolutePath */ void setAccessPermissionInheritAsAdmin(String zone, String absolutePath, boolean recursive) throws JargonException; + /** + * Add an AVU to a collection. + * + * @param absolutePath The absolute path to a collection. + * @param avuData The AVU to add. + * @param adminFlag Execute operation using rodsadmin privileges. + * @throws JargonException If an error occurs. + */ + void addAVUMetadata(String absolutePath, AvuData avuData, boolean adminFlag) throws JargonException; + + /** + * Removes an AVU from a collection. + * + * @param absolutePath The absolute path to a collection. + * @param avuData The AVU to remove. + * @param adminFlag Execute operation using rodsadmin privileges. + * @throws JargonException If an error occurs. + */ + void deleteAVUMetadata(String absolutePath, AvuData avuData, boolean adminFlag) throws JargonException; + + /** + * Sets an AVU on a collection. + * + * All AVUs having an attribute name matching the new AVU will be removed from + * the collection. + * + * @param absolutePath The absolute path to a collection. + * @param avuData The AVU to set. + * @param adminFlag Execute operation using rodsadmin privileges. + * @throws JargonException If an error occurs. + */ + void setAVUMetadata(String absolutePath, AvuData avuData, boolean adminFlag) throws JargonException; + + /** + * Modifies an existing AVU on a collection. + * + * @param absolutePath The absolute path to a collection. + * @param avuData The AVU to modify. + * @param newAvuData The AVU that will replace the target AVU. + * @param adminFlag Execute operation using rodsadmin privileges. + * @throws JargonException If an error occurs. + */ + void modifyAVUMetadata(String absolutePath, AvuData avuData, AvuData newAvuData, boolean adminFlag) + throws JargonException; } diff --git a/jargon-core/src/main/java/org/irods/jargon/core/pub/CollectionAOImpl.java b/jargon-core/src/main/java/org/irods/jargon/core/pub/CollectionAOImpl.java index 2a83c2406..d1c17df7a 100644 --- a/jargon-core/src/main/java/org/irods/jargon/core/pub/CollectionAOImpl.java +++ b/jargon-core/src/main/java/org/irods/jargon/core/pub/CollectionAOImpl.java @@ -13,6 +13,8 @@ import org.irods.jargon.core.exception.OperationNotSupportedByThisServerException; import org.irods.jargon.core.packinstr.ModAccessControlInp; import org.irods.jargon.core.packinstr.ModAvuMetadataInp; +import org.irods.jargon.core.packinstr.ModAvuMetadataInp.ActionType; +import org.irods.jargon.core.packinstr.ModAvuMetadataInp.MetadataTargetType; import org.irods.jargon.core.protovalues.FilePermissionEnum; import org.irods.jargon.core.protovalues.UserTypeEnum; import org.irods.jargon.core.pub.BulkAVUOperationResponse.ResultStatus; @@ -70,6 +72,7 @@ public final class CollectionAOImpl extends FileCatalogObjectAOImpl implements C public static final String SHOW_COLL_ACLS = "ShowCollAcls"; public static final String ERROR_IN_COLECTION_QUERY = "An error occurred in the query for the collection"; + private static final String NULL_OR_EMPTY_ABSOLUTE_PATH = "null or empty absolutePath"; private final IRODSFileFactory irodsFileFactory = new IRODSFileFactoryImpl(getIRODSSession(), getIRODSAccount()); private final IRODSGenQueryExecutor irodsGenQueryExecutor = new IRODSGenQueryExecutorImpl(getIRODSSession(), getIRODSAccount()); @@ -2104,4 +2107,172 @@ public void replicateCollectionAsynchronously(final String irodsCollectionAbsolu } + @Override + public void addAVUMetadata(final String absolutePath, final AvuData avuData, final boolean adminFlag) + throws DataNotFoundException, DuplicateDataException, JargonException { + log.info("addAVUMetadata()"); + + if (null == absolutePath || absolutePath.isEmpty()) { + throw new IllegalArgumentException(NULL_OR_EMPTY_ABSOLUTE_PATH); + } + + if (null == avuData) { + throw new IllegalArgumentException("null AVU data"); + } + + String myPath = MiscIRODSUtils.normalizeIrodsPath(absolutePath); + + log.info("adding avu metadata to collection"); + log.info("absolute path: {}", myPath); + log.info("avu: {}", avuData); + log.info("adminFlag: {}", adminFlag); + + final ModAvuMetadataInp modifyAvuMetadataInp = ModAvuMetadataInp.instance(myPath, + MetadataTargetType.COLLECTION, avuData, null, ActionType.ADD, adminFlag); + + log.debug("sending avu request"); + + try { + getIRODSProtocol().irodsFunction(modifyAvuMetadataInp); + } catch (JargonException je) { + if (je.getMessage().indexOf("-814000") > -1) { + throw new DataNotFoundException("Target collection was not found, could not add AVU"); + } else if (je.getMessage().indexOf("-809000") > -1) { + throw new DuplicateDataException("Duplicate AVU exists, cannot add"); + } + + log.error("jargon exception adding AVU metadata", je); + throw je; + } + + log.debug("metadata added"); + } + + @Override + public void deleteAVUMetadata(final String absolutePath, final AvuData avuData, final boolean adminFlag) + throws DataNotFoundException, JargonException { + log.info("deleteAVUMetadata()"); + + if (null == absolutePath || absolutePath.isEmpty()) { + throw new IllegalArgumentException(NULL_OR_EMPTY_ABSOLUTE_PATH); + } + + if (null == avuData) { + throw new IllegalArgumentException("null AVU data"); + } + + String myPath = MiscIRODSUtils.normalizeIrodsPath(absolutePath); + + log.info("deleting avu metadata on dataObject"); + log.info("absolute path: {}", myPath); + log.info("avu: {}", avuData); + log.info("adminFlag: {}", adminFlag); + + final ModAvuMetadataInp modifyAvuMetadataInp = ModAvuMetadataInp.instance(myPath, + MetadataTargetType.COLLECTION, avuData, null, ActionType.REMOVE, adminFlag); + + log.debug("sending avu request"); + + try { + getIRODSProtocol().irodsFunction(modifyAvuMetadataInp); + } catch (JargonException je) { + if (je.getMessage().indexOf("-814000") > -1) { + throw new DataNotFoundException("Target collection was not found, could not remove AVU"); + } + + log.error("jargon exception removing AVU metadata", je); + throw je; + } + + log.debug("metadata removed"); + } + + @Override + public void setAVUMetadata(final String absolutePath, final AvuData avuData, final boolean adminFlag) + throws DataNotFoundException, JargonException { + log.info("setAVUMetadata()"); + + if (null == absolutePath || absolutePath.isEmpty()) { + throw new IllegalArgumentException(NULL_OR_EMPTY_ABSOLUTE_PATH); + } + + if (null == avuData) { + throw new IllegalArgumentException("null AVU data"); + } + + if (!getIRODSServerProperties().isSupportsMetadataSet()) { + log.error("irods version does not support set avu"); + throw new OperationNotSupportedByThisServerException("set avu not available on this iRODS version"); + } + + String myPath = MiscIRODSUtils.normalizeIrodsPath(absolutePath); + + log.info("setting avu metadata on collection"); + log.info("absolute path: {}", myPath); + log.info("avu: {}", avuData); + log.info("adminFlag: {}", adminFlag); + + final ModAvuMetadataInp modifyAvuMetadataInp = ModAvuMetadataInp.instance(myPath, + MetadataTargetType.COLLECTION, avuData, null, ActionType.SET, adminFlag); + + log.debug("sending avu request"); + + try { + getIRODSProtocol().irodsFunction(modifyAvuMetadataInp); + } catch (JargonException je) { + if (je.getMessage().indexOf("-814000") > -1) { + throw new DataNotFoundException("Target collection was not found, could not add AVU"); + } + + log.error("jargon exception setting AVU metadata", je); + throw je; + } + + log.debug("metadata set"); + } + + @Override + public void modifyAVUMetadata(final String absolutePath, final AvuData avuData, final AvuData newAvuData, + final boolean adminFlag) throws DataNotFoundException, JargonException { + log.info("modifyAVUMetadata()"); + + if (null == absolutePath || absolutePath.isEmpty()) { + throw new IllegalArgumentException(NULL_OR_EMPTY_ABSOLUTE_PATH); + } + + if (null == avuData) { + throw new IllegalArgumentException("target AVU data is null"); + } + + if (null == newAvuData) { + throw new IllegalArgumentException("new AVU data is null"); + } + + String myPath = MiscIRODSUtils.normalizeIrodsPath(absolutePath); + + log.info("modifying avu metadata on collection"); + log.info("absolute path: {}", myPath); + log.info("target avu: {}", avuData); + log.info("new avu: {}", newAvuData); + log.info("adminFlag: {}", adminFlag); + + final ModAvuMetadataInp modifyAvuMetadataInp = ModAvuMetadataInp.instance(myPath, + MetadataTargetType.COLLECTION, avuData, newAvuData, ActionType.MOD, adminFlag); + + log.debug("sending avu request"); + + try { + getIRODSProtocol().irodsFunction(modifyAvuMetadataInp); + } catch (JargonException je) { + if (je.getMessage().indexOf("-814000") > -1) { + throw new DataNotFoundException("Target collection was not found, could not modify AVU"); + } + + log.error("jargon exception modifying AVU metadata", je); + throw je; + } + + log.debug("metadata modified"); + } + } diff --git a/jargon-core/src/main/java/org/irods/jargon/core/pub/DataObjectAO.java b/jargon-core/src/main/java/org/irods/jargon/core/pub/DataObjectAO.java index 5301a0eb9..1b2b485c9 100644 --- a/jargon-core/src/main/java/org/irods/jargon/core/pub/DataObjectAO.java +++ b/jargon-core/src/main/java/org/irods/jargon/core/pub/DataObjectAO.java @@ -1144,4 +1144,49 @@ String truncateReplicaByReplicaNumber(final String logicalPath, final int replic String truncateReplicaByResource(final String logicalPath, final String resource, final long newDataSize) throws JargonException; + /** + * Add an AVU to a data object. + * + * @param absolutePath The absolute path to a data object. + * @param avuData The AVU to add. + * @param adminFlag Execute operation using rodsadmin privileges. + * @throws JargonException If an error occurs. + */ + void addAVUMetadata(String absolutePath, AvuData avuData, boolean adminFlag) throws JargonException; + + /** + * Removes an AVU from a data object. + * + * @param absolutePath The absolute path to a data object. + * @param avuData The AVU to remove. + * @param adminFlag Execute operation using rodsadmin privileges. + * @throws JargonException If an error occurs. + */ + void deleteAVUMetadata(String absolutePath, AvuData avuData, boolean adminFlag) throws JargonException; + + /** + * Sets an AVU on a data object. + * + * All AVUs having an attribute name matching the new AVU will be removed from + * the data object. + * + * @param absolutePath The absolute path to a data object. + * @param avuData The AVU to set. + * @param adminFlag Execute operation using rodsadmin privileges. + * @throws JargonException If an error occurs. + */ + void setAVUMetadata(String absolutePath, AvuData avuData, boolean adminFlag) throws JargonException; + + /** + * Modifies an existing AVU on a data object. + * + * @param absolutePath The absolute path to a data object. + * @param avuData The AVU to modify. + * @param newAvuData The AVU that will replace the target AVU. + * @param adminFlag Execute operation using rodsadmin privileges. + * @throws JargonException If an error occurs. + */ + void modifyAVUMetadata(String absolutePath, AvuData avuData, AvuData newAvuData, boolean adminFlag) + throws JargonException; + } diff --git a/jargon-core/src/main/java/org/irods/jargon/core/pub/DataObjectAOImpl.java b/jargon-core/src/main/java/org/irods/jargon/core/pub/DataObjectAOImpl.java index 1d925dd62..fdfc5c344 100644 --- a/jargon-core/src/main/java/org/irods/jargon/core/pub/DataObjectAOImpl.java +++ b/jargon-core/src/main/java/org/irods/jargon/core/pub/DataObjectAOImpl.java @@ -37,6 +37,8 @@ import org.irods.jargon.core.packinstr.ModDataObjMetaInp; import org.irods.jargon.core.packinstr.Tag; import org.irods.jargon.core.packinstr.TransferOptions; +import org.irods.jargon.core.packinstr.ModAvuMetadataInp.ActionType; +import org.irods.jargon.core.packinstr.ModAvuMetadataInp.MetadataTargetType; import org.irods.jargon.core.packinstr.TransferOptions.ForceOption; import org.irods.jargon.core.protovalues.FilePermissionEnum; import org.irods.jargon.core.protovalues.UserTypeEnum; @@ -4344,4 +4346,246 @@ public String truncateReplicaByResource(final String logicalPath, final String r return tag.getTag(DataObjInp.MY_STR).getStringValue(); } + @Override + public void addAVUMetadata(final String absolutePath, final AvuData avuData, final boolean adminFlag) + throws OperationNotSupportedForCollectionTypeException, DataNotFoundException, DuplicateDataException, + JargonException { + log.info("addAVUMetadata()"); + + if (null == absolutePath || absolutePath.isEmpty()) { + throw new IllegalArgumentException(NULL_OR_EMPTY_ABSOLUTE_PATH); + } + + if (null == avuData) { + throw new IllegalArgumentException("null AVU data"); + } + + String myPath = MiscIRODSUtils.normalizeIrodsPath(absolutePath); + + log.info("adding avu metadata to data object"); + log.info("absolute path: {}", myPath); + log.info("avu: {}", avuData); + log.info("adminFlag: {}", adminFlag); + + /* + * Handle soft links by munging the path + */ + + ObjStat objStat; + try { + objStat = this.retrieveObjStat(myPath); + } catch (Exception e) { + throw new DataNotFoundException(e); + } + + if (objStat.getSpecColType() == SpecColType.MOUNTED_COLL) { + log.info("objStat indicates collection type that does not support this operation:{}", objStat); + throw new OperationNotSupportedForCollectionTypeException( + "The special collection type does not support this operation"); + } + + String absPath = resolveAbsolutePathGivenObjStat(objStat); + + final ModAvuMetadataInp modifyAvuMetadataInp = ModAvuMetadataInp.instance(absPath, + MetadataTargetType.DATA_OBJECT, avuData, null, ActionType.ADD, adminFlag); + + log.debug("sending avu request"); + + try { + getIRODSProtocol().irodsFunction(modifyAvuMetadataInp); + } catch (JargonException je) { + if (je.getMessage().indexOf("-817000") > -1) { + throw new DataNotFoundException("Target data object was not found, could not add AVU"); + } else if (je.getMessage().indexOf("-809000") > -1) { + throw new DuplicateDataException("Duplicate AVU exists, cannot add"); + } + + log.error("jargon exception adding AVU metadata", je); + throw je; + } + + log.debug("metadata added"); + } + + @Override + public void deleteAVUMetadata(final String absolutePath, final AvuData avuData, final boolean adminFlag) + throws OperationNotSupportedForCollectionTypeException, DataNotFoundException, JargonException { + log.info("deleteAVUMetadata()"); + + if (null == absolutePath || absolutePath.isEmpty()) { + throw new IllegalArgumentException(NULL_OR_EMPTY_ABSOLUTE_PATH); + } + + if (null == avuData) { + throw new IllegalArgumentException("null AVU data"); + } + + String myPath = MiscIRODSUtils.normalizeIrodsPath(absolutePath); + + log.info("deleting avu metadata on dataObject"); + log.info("absolute path: {}", myPath); + log.info("avu: {}", avuData); + log.info("adminFlag: {}", adminFlag); + + ObjStat objStat; + try { + objStat = this.retrieveObjStat(myPath); + } catch (FileNotFoundException e) { + throw new DataNotFoundException(e); + } + + if (objStat.getSpecColType() == SpecColType.MOUNTED_COLL) { + log.info("objStat indicates collection type that does not support this operation:{}", objStat); + throw new OperationNotSupportedForCollectionTypeException( + "The special collection type does not support this operation"); + } + + String absPath = resolveAbsolutePathGivenObjStat(objStat); + + final ModAvuMetadataInp modifyAvuMetadataInp = ModAvuMetadataInp.instance(absPath, + MetadataTargetType.DATA_OBJECT, avuData, null, ActionType.REMOVE, adminFlag); + + log.debug("sending avu request"); + + try { + getIRODSProtocol().irodsFunction(modifyAvuMetadataInp); + } catch (JargonException je) { + if (je.getMessage().indexOf("-817000") > -1) { + throw new DataNotFoundException("Target data object was not found, could not remove AVU"); + } + + log.error("jargon exception removing AVU metadata", je); + throw je; + } + + log.debug("metadata removed"); + } + + @Override + public void setAVUMetadata(final String absolutePath, final AvuData avuData, final boolean adminFlag) + throws OperationNotSupportedForCollectionTypeException, DataNotFoundException, JargonException { + log.info("setAVUMetadata()"); + + if (null == absolutePath || absolutePath.isEmpty()) { + throw new IllegalArgumentException(NULL_OR_EMPTY_ABSOLUTE_PATH); + } + + if (null == avuData) { + throw new IllegalArgumentException("null AVU data"); + } + + if (!getIRODSServerProperties().isSupportsMetadataSet()) { + log.error("irods version does not support set avu"); + throw new OperationNotSupportedByThisServerException("set avu not available on this iRODS version"); + } + + String myPath = MiscIRODSUtils.normalizeIrodsPath(absolutePath); + + log.info("setting avu metadata on data object"); + log.info("absolute path: {}", myPath); + log.info("avu: {}", avuData); + log.info("adminFlag: {}", adminFlag); + + /* + * Handle soft links by munging the path + */ + + ObjStat objStat; + try { + objStat = this.retrieveObjStat(absolutePath); + } catch (Exception e) { + throw new DataNotFoundException(e); + } + + if (objStat.getSpecColType() == SpecColType.MOUNTED_COLL) { + log.info("objStat indicates collection type that does not support this operation:{}", objStat); + throw new OperationNotSupportedForCollectionTypeException( + "The special collection type does not support this operation"); + } + + String absPath = resolveAbsolutePathGivenObjStat(objStat); + + final ModAvuMetadataInp modifyAvuMetadataInp = ModAvuMetadataInp.instance(absPath, + MetadataTargetType.DATA_OBJECT, avuData, null, ActionType.SET, adminFlag); + + log.debug("sending avu request"); + + try { + getIRODSProtocol().irodsFunction(modifyAvuMetadataInp); + } catch (JargonException je) { + if (je.getMessage().indexOf("-817000") > -1) { + throw new DataNotFoundException("Target data object was not found, could not add AVU"); + } + + log.error("jargon exception setting AVU metadata", je); + throw je; + } + + log.debug("metadata set"); + } + + @Override + public void modifyAVUMetadata(final String absolutePath, final AvuData avuData, final AvuData newAvuData, + final boolean adminFlag) + throws OperationNotSupportedForCollectionTypeException, DataNotFoundException, JargonException { + log.info("modifyAVUMetadata()"); + + if (null == absolutePath || absolutePath.isEmpty()) { + throw new IllegalArgumentException(NULL_OR_EMPTY_ABSOLUTE_PATH); + } + + if (null == avuData) { + throw new IllegalArgumentException("target AVU data is null"); + } + + if (null == newAvuData) { + throw new IllegalArgumentException("new AVU data is null"); + } + + String myPath = MiscIRODSUtils.normalizeIrodsPath(absolutePath); + + log.info("modifying avu metadata on data object"); + log.info("absolute path: {}", myPath); + log.info("target avu: {}", avuData); + log.info("new avu: {}", newAvuData); + log.info("adminFlag: {}", adminFlag); + + /* + * Handle soft links by munging the path + */ + + ObjStat objStat; + try { + objStat = this.retrieveObjStat(myPath); + } catch (Exception e) { + throw new DataNotFoundException(e); + } + + if (objStat.getSpecColType() == SpecColType.MOUNTED_COLL) { + log.info("objStat indicates collection type that does not support this operation:{}", objStat); + throw new OperationNotSupportedForCollectionTypeException( + "The special collection type does not support this operation"); + } + + String absPath = resolveAbsolutePathGivenObjStat(objStat); + + final ModAvuMetadataInp modifyAvuMetadataInp = ModAvuMetadataInp.instance(absPath, + MetadataTargetType.DATA_OBJECT, avuData, newAvuData, ActionType.MOD, adminFlag); + + log.debug("sending avu request"); + + try { + getIRODSProtocol().irodsFunction(modifyAvuMetadataInp); + } catch (JargonException je) { + if (je.getMessage().indexOf("-817000") > -1) { + throw new DataNotFoundException("Target data object was not found, could not modify AVU"); + } + + log.error("jargon exception modifying AVU metadata", je); + throw je; + } + + log.debug("metadata modified"); + } + } diff --git a/jargon-core/src/test/java/org/irods/jargon/core/pub/CollectionAOImplTest.java b/jargon-core/src/test/java/org/irods/jargon/core/pub/CollectionAOImplTest.java index 469c5e1c9..8b75174c8 100644 --- a/jargon-core/src/test/java/org/irods/jargon/core/pub/CollectionAOImplTest.java +++ b/jargon-core/src/test/java/org/irods/jargon/core/pub/CollectionAOImplTest.java @@ -1,6 +1,7 @@ package org.irods.jargon.core.pub; import java.io.File; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -2313,4 +2314,111 @@ public void testGetPermissionForCollectionDoesThrowExceptionOnTrailingSlashBug37 } + @Test + public void testRodsadminCanManipulateMetadataOnCollectionUsingAdminFlag() throws Exception { + IRODSAccount rodsadminAcct = testingPropertiesHelper + .buildIRODSAccountForIRODSUserFromTestPropertiesForGivenUser(testingProperties, + testingProperties.getProperty(TestingPropertiesHelper.IRODS_ADMIN_USER_KEY), + testingProperties.getProperty(TestingPropertiesHelper.IRODS_ADMIN_PASSWORD_KEY)); + + IRODSAccessObjectFactory aof = irodsFileSystem.getIRODSAccessObjectFactory(); + + Assume.assumeTrue( + "This test requires a minimum version of iRODS 4.2.12 (excluding 4.3.0 since it was released before 4.2.12)", + aof.getIRODSServerProperties(rodsadminAcct).isTheIrodsServerAtLeastAtTheGivenReleaseVersion( + "rods4.2.12") && !aof.getIRODSServerProperties(rodsadminAcct).isVersion("rods4.3.0")); + + IRODSAccount rodsuserAcct = testingPropertiesHelper + .buildIRODSAccountFromSecondaryTestProperties(testingProperties); + + String logicalPath = Paths.get("/", rodsuserAcct.getZone(), "home", rodsuserAcct.getUserName()).toString(); + + // Show the rodsadmin, identified by rodsadminAcct, does NOT have permission on + // the home collection of the rodsuser, identified by rodsuserAcct. + CollectionAO cao = aof.getCollectionAO(rodsadminAcct); + FilePermissionEnum perm = cao.getPermissionForCollection(logicalPath, rodsadminAcct.getUserName(), + rodsadminAcct.getZone()); + Assert.assertEquals(perm, FilePermissionEnum.NONE); + + // Show the rodsadmin cannot add metadata to the collection when the admin flag + // is false. + String attrName = "issue_420_a0"; + String attrValue = "issue_420_v0"; + String attrUnit = ""; + final AvuData avu = AvuData.instance(attrName, attrValue, attrUnit); + boolean adminFlag = false; + Assert.assertThrows(JargonException.class, () -> cao.addAVUMetadata(logicalPath, avu, false /* adminFlag */)); + Assert.assertTrue(cao.findMetadataValuesForCollection(logicalPath).isEmpty()); + + // Show the rodsadmin can add metadata to the collection when the admin flag is + // true. + adminFlag = true; + cao.addAVUMetadata(logicalPath, avu, adminFlag); + List avus = cao.findMetadataValuesForCollection(logicalPath); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + + // Show the rodsadmin cannot modify metadata on the collection when the admin + // flag is false. + AvuData newAvu = AvuData.instance(attrName, attrValue + ".updated", "issue_420_u0"); + Assert.assertThrows(JargonException.class, + () -> cao.modifyAVUMetadata(logicalPath, avu, newAvu, false /* adminFlag */)); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + + // Show the rodsadmin can modify metadata on the collection when the admin flag + // is true. + adminFlag = true; + cao.modifyAVUMetadata(logicalPath, avu, newAvu, adminFlag); + avus = cao.findMetadataValuesForCollection(logicalPath); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(newAvu))); + + // Add the original attribute to the collection. + // This will help prove the correctness of .setAVUMetadata(). + cao.addAVUMetadata(logicalPath, avu, adminFlag); + avus = cao.findMetadataValuesForCollection(logicalPath); + Assert.assertTrue(avus.size() == 2); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(newAvu))); + + // Capture the new attribute value and unit now that they have been modified in + // the catalog. + avu.setValue(newAvu.getValue()); + avu.setUnit(newAvu.getUnit()); + + // At this point, the collection should have the following AVUs. + // - ("issue_420_a0", "issue_420_v0" , "" ) + // - ("issue_420_a0", "issue_420_v0.updated", "issue_420_u0") + + // Show the rodsadmin cannot set metadata on the collection when the admin flag + // is false. + Assert.assertThrows(JargonException.class, () -> cao.setAVUMetadata(logicalPath, avu, false /* adminFlag */)); + avus = cao.findMetadataValuesForCollection(logicalPath); + Assert.assertTrue(avus.size() == 2); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(newAvu))); + + // Show the rodsadmin can set metadata on the collection when the admin flag is + // true. + adminFlag = true; + cao.setAVUMetadata(logicalPath, avu, adminFlag); + avus = cao.findMetadataValuesForCollection(logicalPath); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + + // Show the rodsadmin cannot remove metadata on the collection when the admin + // flag is false. + Assert.assertThrows(JargonException.class, + () -> cao.deleteAVUMetadata(logicalPath, avu, false /* adminFlag */)); + avus = cao.findMetadataValuesForCollection(logicalPath); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + + // Show the rodsadmin can remove metadata on the collection when the admin flag + // is true. + cao.deleteAVUMetadata(logicalPath, avu, adminFlag); + Assert.assertTrue(cao.findMetadataValuesForCollection(logicalPath).isEmpty()); + } + } diff --git a/jargon-core/src/test/java/org/irods/jargon/core/pub/DataObjectAOImplTest.java b/jargon-core/src/test/java/org/irods/jargon/core/pub/DataObjectAOImplTest.java index e56a82bec..501f5b8ca 100644 --- a/jargon-core/src/test/java/org/irods/jargon/core/pub/DataObjectAOImplTest.java +++ b/jargon-core/src/test/java/org/irods/jargon/core/pub/DataObjectAOImplTest.java @@ -8,6 +8,7 @@ import java.util.Date; import java.util.List; import java.util.Properties; +import java.util.Random; import org.irods.jargon.core.connection.IRODSAccount; import org.irods.jargon.core.connection.IRODSProtocolManager; @@ -5251,5 +5252,130 @@ public void testReplicaTruncateInvalidInputs() throws Exception { Assert.assertThrows(IllegalArgumentException.class, () -> dao.truncateReplicaByResource(logicalPath, "demoResc", -1)); Assert.assertEquals(dao.getObjectStatForAbsolutePath(logicalPath).getObjSize(), content.length()); } + + @Test + public void testRodsadminCanManipulateMetadataOnDataObjectUsingAdminFlag() throws Exception { + IRODSAccount rodsadminAcct = testingPropertiesHelper + .buildIRODSAccountForIRODSUserFromTestPropertiesForGivenUser(testingProperties, + testingProperties.getProperty(TestingPropertiesHelper.IRODS_ADMIN_USER_KEY), + testingProperties.getProperty(TestingPropertiesHelper.IRODS_ADMIN_PASSWORD_KEY)); + + IRODSAccessObjectFactory aof = irodsFileSystem.getIRODSAccessObjectFactory(); + + Assume.assumeTrue( + "This test requires a minimum version of iRODS 4.2.12 (excluding 4.3.0 since it was released before 4.2.12)", + aof.getIRODSServerProperties(rodsadminAcct).isTheIrodsServerAtLeastAtTheGivenReleaseVersion( + "rods4.2.12") && !aof.getIRODSServerProperties(rodsadminAcct).isVersion("rods4.3.0")); + + IRODSAccount rodsuserAcct = testingPropertiesHelper + .buildIRODSAccountFromSecondaryTestProperties(testingProperties); + IRODSFileFactory ff = aof.getIRODSFileFactory(rodsuserAcct); + + String logicalPath = Paths.get("/", rodsuserAcct.getZone(), "home", rodsuserAcct.getUserName(), + "testRodsadminCanManipulateMetadataOnDataObjectUsingAdminFlag.txt" + + String.valueOf(new Random().nextInt())) + .toString(); + + try { + // As a rodsuser, create a new data object. + IRODSFile irodsFile = ff.instanceIRODSFile(logicalPath); + IRODSFileOutputStream fos = ff.instanceIRODSFileOutputStream(irodsFile); + String content = "hello, world"; + fos.write(content.getBytes(StandardCharsets.UTF_8)); + fos.close(); + + // Show the rodsadmin, identified by rodsadminAcct, does NOT have permission on + // the new data object. + DataObjectAO dao = aof.getDataObjectAO(rodsadminAcct); + FilePermissionEnum perm = dao.getPermissionForDataObject(logicalPath, rodsadminAcct.getUserName(), + rodsadminAcct.getZone()); + Assert.assertEquals(perm, null); + + // Show the rodsadmin cannot add metadata to the data object when the admin flag + // is false. + String attrName = "issue_420_a0"; + String attrValue = "issue_420_v0"; + String attrUnit = ""; + final AvuData avu = AvuData.instance(attrName, attrValue, attrUnit); + boolean adminFlag = false; + Assert.assertThrows(JargonException.class, + () -> dao.addAVUMetadata(logicalPath, avu, false /* adminFlag */)); + Assert.assertTrue(dao.findMetadataValuesForDataObject(logicalPath).isEmpty()); + + // Show the rodsadmin can add metadata to the data object when the admin flag is + // true. + adminFlag = true; + dao.addAVUMetadata(logicalPath, avu, adminFlag); + List avus = dao.findMetadataValuesForDataObject(logicalPath); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + + // Show the rodsadmin cannot modify metadata on the data object when the admin + // flag is false. + AvuData newAvu = AvuData.instance(attrName, attrValue + ".updated", "issue_420_u0"); + Assert.assertThrows(JargonException.class, + () -> dao.modifyAVUMetadata(logicalPath, avu, newAvu, false /* adminFlag */)); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + + // Show the rodsadmin can modify metadata on the data object when the admin flag + // is true. + adminFlag = true; + dao.modifyAVUMetadata(logicalPath, avu, newAvu, adminFlag); + avus = dao.findMetadataValuesForDataObject(logicalPath); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(newAvu))); + + // Add the original attribute to the data object. + // This will help prove the correctness of .setAVUMetadata(). + dao.addAVUMetadata(logicalPath, avu, adminFlag); + avus = dao.findMetadataValuesForDataObject(logicalPath); + Assert.assertTrue(avus.size() == 2); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(newAvu))); + + // Capture the new attribute value and unit now that they have been modified in + // the catalog. + avu.setValue(newAvu.getValue()); + avu.setUnit(newAvu.getUnit()); + + // At this point, the data object should have the following AVUs. + // - ("issue_420_a0", "issue_420_v0" , "" ) + // - ("issue_420_a0", "issue_420_v0.updated", "issue_420_u0") + + // Show the rodsadmin cannot set metadata on the data object when the admin flag + // is false. + Assert.assertThrows(JargonException.class, + () -> dao.setAVUMetadata(logicalPath, avu, false /* adminFlag */)); + avus = dao.findMetadataValuesForDataObject(logicalPath); + Assert.assertTrue(avus.size() == 2); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(newAvu))); + + // Show the rodsadmin can set metadata on the data object when the admin flag is + // true. + adminFlag = true; + dao.setAVUMetadata(logicalPath, avu, adminFlag); + avus = dao.findMetadataValuesForDataObject(logicalPath); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + + // Show the rodsadmin cannot remove metadata on the data object when the admin + // flag is false. + Assert.assertThrows(JargonException.class, + () -> dao.deleteAVUMetadata(logicalPath, avu, false /* adminFlag */)); + avus = dao.findMetadataValuesForDataObject(logicalPath); + Assert.assertTrue(avus.size() == 1); + Assert.assertTrue(avus.stream().anyMatch(e -> e.asAvu().equals(avu))); + + // Show the rodsadmin can remove metadata on the data object when the admin flag + // is true. + dao.deleteAVUMetadata(logicalPath, avu, adminFlag); + Assert.assertTrue(dao.findMetadataValuesForDataObject(logicalPath).isEmpty()); + } finally { + // Remove the data object. + ff.instanceIRODSFile(logicalPath).deleteWithForceOption(); + } + } }