| /* |
| * 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.jackrabbit.oak.plugins.document; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.Map.Entry; |
| |
| import com.google.common.base.Objects; |
| |
| import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Condition; |
| import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Key; |
| import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation; |
| import org.jetbrains.annotations.NotNull; |
| |
| /** |
| * Provides convenience methods for applying {@link UpdateOp}s to |
| * {@link Document}s. |
| */ |
| public class UpdateUtils { |
| |
| /** |
| * Apply the changes to the in-memory document. |
| * |
| * @param doc |
| * the target document. |
| * @param update |
| * the changes to apply. |
| */ |
| public static void applyChanges(@NotNull Document doc, |
| @NotNull UpdateOp update) { |
| doc.put(Document.ID, update.getId()); |
| for (Entry<Key, Operation> e : checkNotNull(update).getChanges().entrySet()) { |
| Key k = e.getKey(); |
| Operation op = e.getValue(); |
| switch (op.type) { |
| case SET: { |
| doc.put(k.toString(), op.value); |
| break; |
| } |
| case REMOVE: { |
| doc.remove(k.toString()); |
| break; |
| } |
| case MAX: { |
| Comparable newValue = (Comparable) op.value; |
| Object old = doc.get(k.toString()); |
| //noinspection unchecked |
| if (old == null || newValue.compareTo(old) > 0) { |
| doc.put(k.toString(), op.value); |
| } |
| break; |
| } |
| case INCREMENT: { |
| Object old = doc.get(k.toString()); |
| Long x = (Long) op.value; |
| if (old == null) { |
| old = 0L; |
| } |
| doc.put(k.toString(), ((Long) old) + x); |
| break; |
| } |
| case SET_MAP_ENTRY: { |
| Object old = doc.get(k.getName()); |
| @SuppressWarnings("unchecked") |
| Map<Revision, Object> m = (Map<Revision, Object>) old; |
| if (m == null) { |
| m = new TreeMap<Revision, Object>(StableRevisionComparator.REVERSE); |
| doc.put(k.getName(), m); |
| } |
| if (k.getRevision() == null) { |
| throw new IllegalArgumentException("Cannot set map entry " + k.getName() + " with null revision"); |
| } |
| m.put(k.getRevision(), op.value); |
| break; |
| } |
| case REMOVE_MAP_ENTRY: { |
| Object old = doc.get(k.getName()); |
| @SuppressWarnings("unchecked") |
| Map<Revision, Object> m = (Map<Revision, Object>) old; |
| if (m != null) { |
| m.remove(k.getRevision()); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| public static boolean checkConditions(@NotNull Document doc, |
| @NotNull Map<Key, Condition> conditions) { |
| for (Map.Entry<Key, Condition> entry : conditions.entrySet()) { |
| Condition c = entry.getValue(); |
| Key k = entry.getKey(); |
| Object value = doc.get(k.getName()); |
| Revision r = k.getRevision(); |
| if (c.type == Condition.Type.EXISTS) { |
| boolean checkForExistence = Boolean.TRUE.equals(c.value); |
| if (value == null) { |
| if (checkForExistence) { |
| return false; |
| } |
| } else { |
| if (r == null) { |
| if (!checkForExistence) { |
| return false; |
| } |
| } else if (value instanceof Map) { |
| Map<?, ?> map = (Map<?, ?>) value; |
| if (checkForExistence) { |
| if (!map.containsKey(r)) { |
| return false; |
| } |
| } else { |
| if (map.containsKey(r)) { |
| return false; |
| } |
| } |
| } else { |
| return false; |
| } |
| } |
| } else if (c.type == Condition.Type.EQUALS || c.type == Condition.Type.NOTEQUALS) { |
| if (r != null) { |
| if (value instanceof Map) { |
| value = ((Map) value).get(r); |
| } else { |
| value = null; |
| } |
| } |
| boolean equal = Objects.equal(value, c.value); |
| if (c.type == Condition.Type.EQUALS && !equal) { |
| return false; |
| } else if (c.type == Condition.Type.NOTEQUALS && equal) { |
| return false; |
| } |
| } else { |
| throw new IllegalArgumentException("Unknown condition: " + c.type); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Ensures that the given {@link UpdateOp} is unconditional |
| * @param up the update operation |
| * @throws IllegalArgumentException when the operations is conditional |
| */ |
| public static void assertUnconditional(@NotNull UpdateOp up) { |
| Map<Key, Condition> conditions = up.getConditions(); |
| if (!conditions.isEmpty()) { |
| throw new IllegalArgumentException( |
| "This DocumentStore method does not support conditional updates, but the UpdateOp contained: " + conditions); |
| } |
| } |
| } |