blob: e5c51170a06e88232feed54f67fa7491d5ed2b57 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.metron.indexing.dao.update;
import java.util.*;
import static org.apache.metron.indexing.dao.update.PatchOperation.*;
public enum PatchUtils {
INSTANCE;
public static final String OP = "op";
public static final String VALUE = "value";
public static final String PATH = "path";
public static final String FROM = "from";
private static final String PATH_SEPARATOR = "/";
public Map<String, Object> applyPatch(List<Map<String, Object>> patches, Map<String, Object> source) {
Map<String, Object> patchedObject = new HashMap<>(source);
for(Map<String, Object> patch: patches) {
// parse patch request parameters
String operation = (String) patch.get(OP);
PatchOperation patchOperation;
try {
patchOperation = PatchOperation.valueOf(operation.toUpperCase());
} catch(IllegalArgumentException e) {
throw new UnsupportedOperationException(String.format("The %s operation is not supported", operation));
}
Object value = patch.get(VALUE);
String path = (String) patch.get(PATH);
// locate the nested object
List<String> fieldNames = getFieldNames(path);
String nestedFieldName = fieldNames.get(fieldNames.size() - 1);
Map<String, Object> nestedObject = getNestedObject(fieldNames, patchedObject);
// apply the patch operation
if (ADD.equals(patchOperation) || REPLACE.equals(patchOperation)) {
nestedObject.put(nestedFieldName, value);
} else if (REMOVE.equals(patchOperation)) {
nestedObject.remove(nestedFieldName);
} else if (COPY.equals(patchOperation) || MOVE.equals(patchOperation)) {
// locate the nested object to copy/move the value from
String from = (String) patch.get(FROM);
List<String> fromFieldNames = getFieldNames(from);
String fromNestedFieldName = fromFieldNames.get(fromFieldNames.size() - 1);
Map<String, Object> fromNestedObject = getNestedObject(fromFieldNames, patchedObject);
// copy the value
Object copyValue = fromNestedObject.get(fromNestedFieldName);
nestedObject.put(nestedFieldName, copyValue);
if (MOVE.equals(patchOperation)) {
// remove the from value in case of a move
nestedObject.remove(fromNestedFieldName);
}
} else if (TEST.equals(patchOperation)) {
Object testValue = nestedObject.get(nestedFieldName);
if (!Objects.equals(value, testValue)) {
throw new PatchException(String.format("TEST operation failed: supplied value [%s] != target value [%s]", value, testValue));
}
}
}
return patchedObject;
}
private List<String> getFieldNames(String path) {
String[] parts = path.split(PATH_SEPARATOR);
return new ArrayList<>(Arrays.asList(parts).subList(1, parts.length));
}
@SuppressWarnings("unchecked")
private Map<String, Object> getNestedObject(List<String> fieldNames, Map<String, Object> patchedObject) {
Map<String, Object> nestedObject = patchedObject;
for(int i = 0; i < fieldNames.size() - 1; i++) {
Object object = nestedObject.get(fieldNames.get(i));
if (object == null || !(object instanceof Map)) {
throw new IllegalArgumentException(String.format("Invalid path: /%s", String.join(PATH_SEPARATOR, fieldNames)));
} else {
nestedObject = (Map<String, Object>) object;
}
}
return nestedObject;
}
}