in guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java [549:800]
public APIPatchResponse patchObjects(List<APIPatch<ExternalType>> patches)
throws GuacamoleException {
// An outcome for each patch included in the request. This list
// may include both success and failure responses, though the
// presence of any failure would indicated that the entire
// request has failed and no changes have been made.
List<APIPatchOutcome> patchOutcomes = new ArrayList<>();
// Perform all requested operations atomically
directory.tryAtomically(new AtomicDirectoryOperation<InternalType>() {
@Override
public void executeOperation(boolean atomic, Directory<InternalType> directory)
throws GuacamoleException {
// If the underlying directory implentation does not support
// atomic operations, abort the patch operation. This REST
// endpoint requires that operations be performed atomically.
if (!atomic)
throw new GuacamoleUnsupportedException(
"The extension providing this directory does not " +
"support Atomic Operations. The patch cannot be " +
"executed.");
// Keep a list of all objects that have been successfully
// added, replaced, or removed
Collection<InternalType> addedObjects = new ArrayList<>();
Collection<InternalType> replacedObjects = new ArrayList<>();
Collection<String> removedIdentifiers = new ArrayList<>();
// A list of all responses associated with the successful
// creation of new objects
List<APIPatchOutcome> creationSuccesses = new ArrayList<>();
// True if any operation in the patch failed. Any failure will
// fail the request, though won't result in immediate stoppage
// since more errors may yet be uncovered.
boolean failed = false;
// Apply each operation specified within the patch
for (APIPatch<ExternalType> patch : patches) {
// Retrieve and validate path
String path = patch.getPath();
if (!path.startsWith("/"))
throw new GuacamoleClientException("Patch paths must start with \"/\".");
APIPatch.Operation op = patch.getOp();
if (op == APIPatch.Operation.add) {
// Filter/sanitize object contents
InternalType internal = filterAndTranslate(patch.getValue());
try {
// Attempt to add the new object
directory.add(internal);
// Add the object to the list if addition was successful
addedObjects.add(internal);
// Add a success outcome describing the object creation
APIPatchOutcome response = new APIPatchOutcome(
op, internal.getIdentifier(), path);
patchOutcomes.add(response);
creationSuccesses.add(response);
}
catch (GuacamoleException | RuntimeException | Error e) {
failed = true;
fireDirectoryFailureEvent(
DirectoryEvent.Operation.ADD,
internal.getIdentifier(), internal, e);
// Attempt to generate an API Patch error using the
// caught exception
APIPatchError patchError = createPatchFailure(
op, null, path, e);
if (patchError != null)
patchOutcomes.add(patchError);
// If an unexpected failure occurs, fall through to
// the standard API error handling
else
throw e;
}
}
else if (op == APIPatch.Operation.replace) {
// The identifier of the object to be replaced
String identifier = path.substring(1);
InternalType original = null;
try {
// Fetch the object to be updated from the atomic
// directory instance. If no object is found, a
// directory GET failure event will be logged, and
// the update attempt will be aborted.
original = getObjectByIdentifier(identifier, directory);
// Apply the changes to the original object
translator.applyExternalChanges(
original, patch.getValue());
// Update the directory
directory.update(original);
replacedObjects.add(original);
// Add a success outcome describing the replacement
APIPatchOutcome response = new APIPatchOutcome(
op, identifier, path);
patchOutcomes.add(response);
}
catch (GuacamoleException | RuntimeException | Error e) {
failed = true;
fireDirectoryFailureEvent(
DirectoryEvent.Operation.UPDATE,
identifier, original, e);
// Attempt to generate an API Patch error using the
// caught exception
APIPatchError patchError = createPatchFailure(
op, identifier, path, e);
if (patchError != null)
patchOutcomes.add(patchError);
// If an unexpected failure occurs, fall through to
// the standard API error handling
else
throw e;
}
}
else if (op == APIPatch.Operation.remove) {
String identifier = path.substring(1);
try {
// Attempt to remove the object
directory.remove(identifier);
// Add the object to the list if the removal was successful
removedIdentifiers.add(identifier);
// Add a success outcome describing the object removal
APIPatchOutcome response = new APIPatchOutcome(
op, identifier, path);
patchOutcomes.add(response);
creationSuccesses.add(response);
}
catch (GuacamoleException | RuntimeException | Error e) {
failed = true;
fireDirectoryFailureEvent(
DirectoryEvent.Operation.REMOVE,
identifier, null, e);
// Attempt to generate an API Patch error using the
// caught exception
APIPatchError patchError = createPatchFailure(
op, identifier, path, e);
if (patchError != null)
patchOutcomes.add(patchError);
// If an unexpected failure occurs, fall through to
// the standard API error handling
else
throw e;
}
}
else {
throw new GuacamoleUnsupportedException(
"Unsupported patch operation \"" + op + "\". "
+ "Only add, replace, and remove are supported.");
}
}
// If any operation failed
if (failed) {
// Any identifiers for objects created during this request
// will no longer be valid, since the creation of those
// objects will be rolled back.
creationSuccesses.forEach(
response -> response.clearIdentifier());
// Return an error response, including any failures that
// caused the failure of any patch in the request
throw new APIPatchFailureException(
"The provided patches failed to apply.", patchOutcomes);
}
// Fire directory success events for each created object
Iterator<InternalType> addedIterator = addedObjects.iterator();
while (addedIterator.hasNext()) {
InternalType internal = addedIterator.next();
fireDirectorySuccessEvent(
DirectoryEvent.Operation.ADD,
internal.getIdentifier(), internal);
}
// Fire directory success events for each updated object
Iterator<InternalType> updatedIterator = replacedObjects.iterator();
while (updatedIterator.hasNext()) {
InternalType internal = updatedIterator.next();
fireDirectorySuccessEvent(
DirectoryEvent.Operation.UPDATE,
internal.getIdentifier(), internal);
}
// Fire directory success events for each removed object
Iterator<String> removedIterator = removedIdentifiers.iterator();
while (removedIterator.hasNext()) {
String identifier = removedIterator.next();
fireDirectorySuccessEvent(
DirectoryEvent.Operation.REMOVE,
identifier, null);
}
}
});
// Return a list of outcomes, one for each patch in the request
return new APIPatchResponse(patchOutcomes);
}