();
+
+ /**
+ * Original spec-file type: ObjectIdentity
+ *
+ * An object identifier.
+ * Select an object by either:
+ * One, and only one, of the numerical id or name of the workspace.
+ * ws_id wsid - the numerical ID of the workspace.
+ * ws_name workspace - the name of the workspace.
+ * AND
+ * One, and only one, of the numerical id or name of the object.
+ * obj_id objid- the numerical ID of the object.
+ * obj_name name - name of the object.
+ * OPTIONALLY
+ * obj_ver ver - the version of the object.
+ * OR an object reference string:
+ * obj_ref ref - an object reference string.
+ *
+ *
+ */
+ @JsonProperty("oi")
+ public ObjectIdentity getOi() {
+ return oi;
+ }
+
+ /**
+ * Original spec-file type: ObjectIdentity
+ *
+ * An object identifier.
+ * Select an object by either:
+ * One, and only one, of the numerical id or name of the workspace.
+ * ws_id wsid - the numerical ID of the workspace.
+ * ws_name workspace - the name of the workspace.
+ * AND
+ * One, and only one, of the numerical id or name of the object.
+ * obj_id objid- the numerical ID of the object.
+ * obj_name name - name of the object.
+ * OPTIONALLY
+ * obj_ver ver - the version of the object.
+ * OR an object reference string:
+ * obj_ref ref - an object reference string.
+ *
+ *
+ */
+ @JsonProperty("oi")
+ public void setOi(ObjectIdentity oi) {
+ this.oi = oi;
+ }
+
+ public ObjectMetadataUpdate withOi(ObjectIdentity oi) {
+ this.oi = oi;
+ return this;
+ }
+
+ @JsonProperty("new")
+ public Map getNew() {
+ return _new;
+ }
+
+ @JsonProperty("new")
+ public void setNew(Map _new) {
+ this._new = _new;
+ }
+
+ public ObjectMetadataUpdate withNew(Map _new) {
+ this._new = _new;
+ return this;
+ }
+
+ @JsonProperty("remove")
+ public List getRemove() {
+ return remove;
+ }
+
+ @JsonProperty("remove")
+ public void setRemove(List remove) {
+ this.remove = remove;
+ }
+
+ public ObjectMetadataUpdate withRemove(List remove) {
+ this.remove = remove;
+ return this;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperties(java.lang.String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public java.lang.String toString() {
+ return ((((((((("ObjectMetadataUpdate"+" [oi=")+ oi)+", _new=")+ _new)+", remove=")+ remove)+", additionalProperties=")+ additionalProperties)+"]");
+ }
+
+}
diff --git a/src/us/kbase/workspace/kbase/WorkspaceServerMethods.java b/src/us/kbase/workspace/kbase/WorkspaceServerMethods.java
index 12b072b3..581522d4 100644
--- a/src/us/kbase/workspace/kbase/WorkspaceServerMethods.java
+++ b/src/us/kbase/workspace/kbase/WorkspaceServerMethods.java
@@ -1,5 +1,6 @@
package us.kbase.workspace.kbase;
+import static java.util.Objects.requireNonNull;
import static us.kbase.common.utils.ServiceUtils.checkAddlArgs;
import static us.kbase.workspace.kbase.ArgUtils.checkLong;
import static us.kbase.workspace.kbase.ArgUtils.chooseInstant;
@@ -29,6 +30,9 @@
import java.util.Map;
import java.util.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import us.kbase.auth.AuthException;
import us.kbase.auth.AuthToken;
import us.kbase.auth.ConfigurableAuthService;
@@ -42,6 +46,7 @@
import us.kbase.typedobj.idref.IdReferenceHandlerSetFactory;
import us.kbase.typedobj.idref.IdReferenceHandlerSetFactoryBuilder;
import us.kbase.typedobj.idref.IdReferencePermissionHandlerSet;
+import us.kbase.workspace.AlterAdminObjectMetadataParams;
import us.kbase.workspace.CreateWorkspaceParams;
import us.kbase.workspace.GetObjectInfo3Params;
import us.kbase.workspace.GetObjectInfo3Results;
@@ -53,6 +58,7 @@
import us.kbase.workspace.ListWorkspaceInfoParams;
import us.kbase.workspace.ObjectData;
import us.kbase.workspace.ObjectIdentity;
+import us.kbase.workspace.ObjectMetadataUpdate;
import us.kbase.workspace.ObjectSaveData;
import us.kbase.workspace.SaveObjectsParams;
import us.kbase.workspace.SetGlobalPermissionsParams;
@@ -61,11 +67,13 @@
import us.kbase.workspace.WorkspacePermissions;
import us.kbase.workspace.database.DependencyStatus;
import us.kbase.workspace.database.ListObjectsParameters;
+import us.kbase.workspace.database.MetadataUpdate;
import us.kbase.workspace.database.ObjectIDNoWSNoVer;
import us.kbase.workspace.database.ObjectIdentifier;
import us.kbase.workspace.database.ObjectInformation;
import us.kbase.workspace.database.Permission;
import us.kbase.workspace.database.RefLimit;
+import us.kbase.workspace.database.ResolvedObjectID;
import us.kbase.workspace.database.User;
import us.kbase.workspace.database.UserWorkspaceIDs;
import us.kbase.workspace.database.Workspace;
@@ -105,6 +113,10 @@ public WorkspaceServerMethods(
this.auth = auth;
}
+ private static Logger getLogger() {
+ return LoggerFactory.getLogger(WorkspaceServerMethods.class);
+ }
+
/** Get the core workspace instance underlying this server -> core translation layer.
* @return the workspace.
*/
@@ -640,4 +652,56 @@ Long, Map>> getObjectHistory(
final ObjectIdentifier oi = processObjectIdentifier(object);
return objInfoToTuple(ws.getObjectHistory(user, oi, asAdmin), true, false);
}
+
+ /** Set administrative metadata on an object. This method is reserved for full workspace
+ * administrators only and should not be exposed in a public API.
+ * @param params the method parameters.
+ * @throws NoSuchObjectException if one of the objects doesn't exist.
+ * @throws CorruptWorkspaceDBException if the workspace database is corrupt.
+ * @throws WorkspaceCommunicationException if a communication error occurs contacting the
+ * database.
+ * @throws InaccessibleObjectException if one of the objects is inaccessible.
+ */
+ public void setAdminObjectMetadata(final AlterAdminObjectMetadataParams params)
+ // TODO CODE corrupt & comm exceptions should be unchecked, there's no recovery
+ // and it's not the user's fault
+ throws WorkspaceCommunicationException, InaccessibleObjectException,
+ CorruptWorkspaceDBException, NoSuchObjectException {
+ checkAddlArgs(
+ requireNonNull(params, "params").getAdditionalProperties(), params.getClass());
+ if (params.getUpdates() == null || params.getUpdates().isEmpty()) {
+ throw new IllegalArgumentException("updates list cannot be empty");
+ }
+ final Map update = new HashMap<>();
+ final ListIterator iter = params.getUpdates().listIterator();
+ while (iter.hasNext()) {
+ try {
+ final ObjectMetadataUpdate u = requireNonNull(iter.next(),
+ ObjectMetadataUpdate.class.getSimpleName() + " cannot be null");
+ checkAddlArgs(u.getAdditionalProperties(), ObjectMetadataUpdate.class);
+ final MetadataUpdate mu = new MetadataUpdate(
+ new WorkspaceUserMetadata(u.getNew()), u.getRemove());
+ if (!mu.hasUpdate()) {
+ throw new IllegalArgumentException("A metadata update is required");
+ }
+ update.put(processObjectIdentifier(u.getOi()), mu);
+ } catch (NullPointerException | IllegalArgumentException | MetadataException e) {
+ // TODO CODE user caused exceptions should be checked & have custom classes
+ // in preparation for adding error codes. Will need to do this if
+ // methods are converted to a REST-like API so 400s and 500s can be
+ // distinguished
+ throw new IllegalArgumentException(String.format(
+ "Error processing update index %s: %s",
+ iter.previousIndex(), e.getMessage()), e);
+ }
+ }
+ final Map objs = ws.setAdminObjectMetadata(update);
+ for (final ResolvedObjectID r: objs.values()) {
+ getLogger().info("Object {}/{}/{}",
+ r.getWorkspaceIdentifier().getID(),
+ r.getId(),
+ r.getVersion()
+ );
+ }
+ }
}
diff --git a/src/us/kbase/workspace/test/kbase/WorkspaceServerMethodsTest.java b/src/us/kbase/workspace/test/kbase/WorkspaceServerMethodsTest.java
new file mode 100644
index 00000000..7d1ada25
--- /dev/null
+++ b/src/us/kbase/workspace/test/kbase/WorkspaceServerMethodsTest.java
@@ -0,0 +1,224 @@
+package us.kbase.workspace.test.kbase;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import us.kbase.common.test.TestCommon;
+import us.kbase.common.test.TestCommon.LogEvent;
+import us.kbase.workspace.AlterAdminObjectMetadataParams;
+import us.kbase.workspace.ObjectIdentity;
+import us.kbase.workspace.ObjectMetadataUpdate;
+import us.kbase.workspace.database.MetadataUpdate;
+import us.kbase.workspace.database.ObjectIdentifier;
+import us.kbase.workspace.database.ResolvedObjectID;
+import us.kbase.workspace.database.ResolvedWorkspaceID;
+import us.kbase.workspace.database.Workspace;
+import us.kbase.workspace.database.WorkspaceUserMetadata;
+import us.kbase.workspace.kbase.WorkspaceServerMethods;
+
+public class WorkspaceServerMethodsTest {
+
+ private static List logEvents;
+
+ @BeforeClass
+ public static void beforeClass() {
+ logEvents = TestCommon.setUpSLF4JTestLoggerAppender("us.kbase.workspace");
+ }
+
+ @Before
+ public void before() {
+ logEvents.clear();
+ }
+
+ @Test
+ public void setAdminObjectMetadata() throws Exception {
+ final Workspace ws = mock(Workspace.class);
+
+ final WorkspaceServerMethods wsm = new WorkspaceServerMethods(ws, null, null);
+ final MetadataUpdate mu1 = new MetadataUpdate(null, Arrays.asList("foo"));
+ final MetadataUpdate mu2 = new MetadataUpdate(new WorkspaceUserMetadata(
+ ImmutableMap.of("baz", "bar")), null);
+ final MetadataUpdate mu3 = new MetadataUpdate(new WorkspaceUserMetadata(
+ ImmutableMap.of("one", "hump")), Arrays.asList("dromedary"));
+
+ when(ws.setAdminObjectMetadata(ImmutableMap.of(
+ ObjectIdentifier.getBuilderFromRefPath("myws/96").build(), mu1,
+ ObjectIdentifier.getBuilderFromRefPath("8/myobj/1").build(), mu2,
+ ObjectIdentifier.getBuilderFromRefPath("myws/myobj").build(), mu3
+ ))).thenReturn(ImmutableMap.of(
+ ObjectIdentifier.getBuilderFromRefPath("myws/96").build(),
+ new ResolvedObjectID(new ResolvedWorkspaceID(3, "myws", false, false),
+ 96, 2, "myobj2", false),
+ ObjectIdentifier.getBuilderFromRefPath("8/myobj/1").build(),
+ new ResolvedObjectID(new ResolvedWorkspaceID(8, "myws2", false, false),
+ 3, 1, "myobj", false),
+ ObjectIdentifier.getBuilderFromRefPath("myws/myobj").build(),
+ new ResolvedObjectID(new ResolvedWorkspaceID(3, "myws", false, false),
+ 254, 108, "myobj", false)
+ ));
+ wsm.setAdminObjectMetadata(new AlterAdminObjectMetadataParams()
+ .withUpdates(Arrays.asList(
+ new ObjectMetadataUpdate() // test wsname, objid
+ .withOi(new ObjectIdentity().withWorkspace("myws").withObjid(96L))
+ .withRemove(Arrays.asList("foo")),
+ new ObjectMetadataUpdate() // test wsid, objname, ver
+ .withOi(new ObjectIdentity().withWsid(8L).withName("myobj")
+ .withVer(1L))
+ .withNew(ImmutableMap.of("baz", "bar")),
+ new ObjectMetadataUpdate() // test ref
+ .withOi(new ObjectIdentity().withRef("myws/myobj"))
+ .withNew(ImmutableMap.of("one", "hump"))
+ .withRemove(Arrays.asList("dromedary"))
+ )));
+
+ TestCommon.assertLogEventsCorrect(
+ logEvents,
+ new LogEvent(Level.INFO, "Object 3/96/2", WorkspaceServerMethods.class),
+ new LogEvent(Level.INFO, "Object 8/3/1", WorkspaceServerMethods.class),
+ new LogEvent(Level.INFO, "Object 3/254/108", WorkspaceServerMethods.class)
+ );
+ }
+
+ @Test
+ public void setAdminObjectMetadataFailBadTopLevelParams() throws Exception {
+ final WorkspaceServerMethods wsm = new WorkspaceServerMethods(
+ mock(Workspace.class), null, null);
+
+ setAdminObjectMetadataFail(wsm, null, new NullPointerException("params"));
+ final AlterAdminObjectMetadataParams p = new AlterAdminObjectMetadataParams();
+ p.setAdditionalProperties("foo", "bar");
+ setAdminObjectMetadataFail(wsm, p, new IllegalArgumentException(
+ "Unexpected arguments in AlterAdminObjectMetadataParams: foo"));
+ setAdminObjectMetadataFail(wsm, new AlterAdminObjectMetadataParams(),
+ new IllegalArgumentException("updates list cannot be empty"));
+ setAdminObjectMetadataFail(wsm, new AlterAdminObjectMetadataParams().withUpdates(null),
+ new IllegalArgumentException("updates list cannot be empty"));
+ setAdminObjectMetadataFail(wsm, new AlterAdminObjectMetadataParams()
+ .withUpdates(Collections.emptyList()),
+ new IllegalArgumentException("updates list cannot be empty"));
+ }
+
+ @Test
+ public void setAdminObjectMetadataFailBadObjectIDs() throws Exception {
+ // test a selection of bad object identifiers, not meant to be exhaustive.
+ final ObjectIdentity good = new ObjectIdentity().withWsid(1L).withObjid(1L);
+
+ setAdminObjectMetadataFailBadObjectID(new IllegalArgumentException(
+ "Error processing update index 1: ObjectIdentity cannot be null"), good, null);
+ setAdminObjectMetadataFailBadObjectID(new IllegalArgumentException(
+ "Error processing update index 0: Must provide one and only one of workspace name "
+ + "(was: null) or id (was: null)"),
+ new ObjectIdentity(), good);
+ setAdminObjectMetadataFailBadObjectID(new IllegalArgumentException(
+ "Error processing update index 2: Must provide one and only one of object name "
+ + "(was: n) or id (was: 1)"),
+ good, good, new ObjectIdentity().withWsid(1L).withName("n").withObjid(1L));
+ setAdminObjectMetadataFailBadObjectID(new IllegalArgumentException(
+ "Error processing update index 3: Illegal number of separators '/' in "
+ + "object reference '1/2/3/4'"),
+ good, good, good, new ObjectIdentity().withRef("1/2/3/4"));
+ }
+
+ private void setAdminObjectMetadataFailBadObjectID(
+ final Exception expected,
+ final ObjectIdentity... oi) {
+ final WorkspaceServerMethods wsm = new WorkspaceServerMethods(
+ mock(Workspace.class), null, null);
+ setAdminObjectMetadataFail(
+ wsm,
+ new AlterAdminObjectMetadataParams().withUpdates(Stream.of(oi)
+ .map(o -> new ObjectMetadataUpdate()
+ .withOi(o).withRemove(Arrays.asList("foo")))
+ .collect(Collectors.toList())),
+ expected);
+ }
+
+ @Test
+ public void setAdminObjectMetadataFailBadMetadata() throws Exception {
+ // test a selection of problems, again not exhaustive for all possible code paths
+ // through dependencies
+ final ObjectMetadataUpdate good = omu(Arrays.asList("foo"));
+
+ setAdminObjectMetadataFailMetadata(new IllegalArgumentException(
+ "Error processing update index 1: ObjectMetadataUpdate cannot be null"),
+ good, null, good);
+ final ObjectMetadataUpdate addl = new ObjectMetadataUpdate();
+ addl.setAdditionalProperties("yay", "boo");
+ setAdminObjectMetadataFailMetadata(new IllegalArgumentException(
+ "Error processing update index 2: Unexpected arguments in "
+ + "ObjectMetadataUpdate: yay"),
+ good, good, addl);
+ setAdminObjectMetadataFailMetadata(new IllegalArgumentException(
+ "Error processing update index 1: null metadata keys are not allowed in the "
+ + "remove parameter"),
+ good, omu(Arrays.asList("foo", null)));
+ setAdminObjectMetadataFailMetadata(new IllegalArgumentException(
+ "Error processing update index 0: A metadata update is required"),
+ omu(), good);
+ final Map nully = new HashMap<>();
+ nully.put("foo", null);
+ setAdminObjectMetadataFailMetadata(new IllegalArgumentException(
+ "Error processing update index 2: Null value for metadata key foo"),
+ good, good, omu(nully), good);
+
+
+ }
+
+ private ObjectMetadataUpdate omu() {
+ return new ObjectMetadataUpdate().withOi(new ObjectIdentity().withRef("1/1"));
+ }
+
+ private ObjectMetadataUpdate omu(final Map newm) {
+ return new ObjectMetadataUpdate()
+ .withOi(new ObjectIdentity().withRef("1/1"))
+ .withNew(newm);
+ }
+
+ private ObjectMetadataUpdate omu(final List remove) {
+ return new ObjectMetadataUpdate()
+ .withOi(new ObjectIdentity().withRef("1/1"))
+ .withRemove(remove);
+ }
+
+ private void setAdminObjectMetadataFailMetadata(
+ final Exception expected,
+ final ObjectMetadataUpdate ... omu) {
+ final WorkspaceServerMethods wsm = new WorkspaceServerMethods(
+ mock(Workspace.class), null, null);
+ setAdminObjectMetadataFail(
+ wsm,
+ new AlterAdminObjectMetadataParams().withUpdates(Arrays.asList(omu)),
+ expected);
+ }
+
+ private void setAdminObjectMetadataFail(
+ final WorkspaceServerMethods wsm,
+ final AlterAdminObjectMetadataParams params,
+ final Exception expected) {
+ try {
+ wsm.setAdminObjectMetadata(params);
+ fail("expected exception");
+ } catch (Exception got) {
+ TestCommon.assertExceptionCorrect(got, expected);
+ }
+ }
+
+
+}