/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2013-2015 ForgeRock AS. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* http://forgerock.org/license/CDDLv1.0.html
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at http://forgerock.org/license/CDDLv1.0.html
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*/
package org.forgerock.openidm.util;
import static org.forgerock.json.JsonValue.*;
import static org.forgerock.json.resource.ResourceResponse.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.forgerock.http.routing.UriRouterContext;
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.JsonValueException;
import org.forgerock.json.patch.JsonPatch;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.ConflictException;
import org.forgerock.json.resource.NotSupportedException;
import org.forgerock.json.resource.PatchOperation;
import org.forgerock.json.resource.Request;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.services.context.Context;
/**
* Resource utilities.
*/
public class ResourceUtil {
/** The name of the field in the resource content which contains the resource ID as a JsonPointer. */
public static JsonPointer RESOURCE_FIELD_CONTENT_ID_POINTER = new JsonPointer(FIELD_CONTENT_ID);
/**
* {@code ResourceUtil} instances should NOT be constructed in standard
* programming. Instead, the class should be used as
* {@code ResourceUtil.parseResourceName(" /foo/bar/ ");}.
*/
private ResourceUtil() {
super();
}
/**
* Retrieve the {@code UriTemplateVariables} from the context.
* <p/>
*
* @param context
*
* @return an unmodifiableMap or null if the {@code context} does not
* contains {@link UriRouterContext}
*/
public static Map<String, String> getUriTemplateVariables(Context context) {
UriRouterContext routerContext =
context.containsContext(UriRouterContext.class) ? context
.asContext(UriRouterContext.class) : null;
if (null != routerContext) {
return Collections.unmodifiableMap(routerContext.getUriTemplateVariables());
}
return null;
}
public static boolean applyPatchOperation(final PatchOperation operation, final JsonValue newContent) throws ResourceException {
boolean isModified = false;
try {
if (operation.isAdd()) {
newContent.putPermissive(operation.getField(), operation.getValue()
.getObject());
} else if (operation.isRemove()) {
if (operation.getValue().isNull()) {
// Remove entire value.
newContent.remove(operation.getField());
} else {
// Find matching value(s) and remove (assumes
// reference to array).
final JsonValue value = newContent.get(operation.getField());
if (value != null) {
if (value.isList()) {
final Object valueToBeRemoved =
operation.getValue().getObject();
final Iterator<Object> iterator = value.asList().iterator();
while (iterator.hasNext()) {
if (valueToBeRemoved.equals(iterator.next())) {
iterator.remove();
}
}
} else {
// Single valued field.
final Object valueToBeRemoved =
operation.getValue().getObject();
if (valueToBeRemoved.equals(value.getObject())) {
newContent.remove(operation.getField());
}
}
}
}
} else if (operation.isReplace()) {
newContent.remove(operation.getField());
if (!operation.getValue().isNull()) {
newContent.putPermissive(operation.getField(), operation.getValue()
.getObject());
}
} else if (operation.isIncrement()) {
final JsonValue value = newContent.get(operation.getField());
final Number amount = operation.getValue().asNumber();
if (value == null) {
throw new BadRequestException("The field '" + operation.getField()
+ "' does not exist");
} else if (value.isList()) {
final List<Object> elements = value.asList();
for (int i = 0; i < elements.size(); i++) {
elements.set(i, increment(operation, elements.get(i), amount));
}
} else {
newContent.put(operation.getField(), increment(operation, value
.getObject(), amount));
}
}
isModified = true;
} catch (final JsonValueException e) {
throw new ConflictException("The field '" + operation.getField()
+ "' does not exist");
}
return isModified;
}
public static boolean applyPatchOperations(final List<PatchOperation> operations,
final JsonValue newContent) throws ResourceException {
boolean isModified = false;
if (null != operations) {
for (final PatchOperation operation : operations) {
isModified = applyPatchOperation(operation, newContent);
}
}
return isModified;
}
private static Object increment(final PatchOperation operation, final Object object,
final Number amount) throws BadRequestException {
if (object instanceof Long) {
return ((Long) object) + amount.longValue();
} else if (object instanceof Integer) {
return ((Integer) object) + amount.intValue();
} else if (object instanceof Float) {
return ((Float) object) + amount.floatValue();
} else if (object instanceof Double) {
return ((Double) object) + amount.doubleValue();
} else {
throw new BadRequestException("The field '" + operation.getField()
+ "' is not a number");
}
}
public static ResourceException notSupported(final Request request) {
return new NotSupportedException(ResourceMessages.ERR_OPERATION_NOT_SUPPORTED_EXPECTATION
.get(request.getRequestType().name()).toString());
}
public static ResourceException notSupportedOnCollection(final Request request) {
return new NotSupportedException(ResourceMessages.ERR_OPERATION_NOT_SUPPORTED_EXPECTATION
.get(request.getRequestType().name()).toString());
}
public static ResourceException notSupportedOnInstance(final Request request) {
return new NotSupportedException(ResourceMessages.ERR_OPERATION_NOT_SUPPORTED_EXPECTATION
.get(request.getRequestType().name()).toString());
}
/**
* Compares the old vs new json to see if the contents are equal, ignoring _id and _rev.
*
* @param oldValue old json to compare.
* @param newValue new json to compare against oldValue.
* @return true if the two values are equal ignoring the _id and _rev.
* @see JsonPatch#diff(JsonValue, JsonValue)
*/
public static boolean isEqual(JsonValue oldValue, JsonValue newValue) {
JsonValue tmpOldValue = null == oldValue ? json(object()) : oldValue.copy();
JsonValue tmpNewValue = null == newValue ? json(object()) : newValue.copy();
tmpOldValue.remove(FIELD_CONTENT_ID);
tmpOldValue.remove(FIELD_CONTENT_REVISION);
tmpNewValue.remove(FIELD_CONTENT_ID);
tmpNewValue.remove(FIELD_CONTENT_REVISION);
return JsonPatch.diff(tmpOldValue, tmpNewValue).size() == 0;
}
}