public APIPatchResponse patchObjects()

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);

    }