/* * Copyright 2012 NGDATA nv * * Licensed 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.lilyproject.repository.impl; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.lilyproject.repository.api.CompareOp; import org.lilyproject.repository.api.MutationCondition; import org.lilyproject.repository.api.QName; import org.lilyproject.repository.api.Record; import org.lilyproject.repository.api.Repository; import org.lilyproject.repository.api.RepositoryException; import org.lilyproject.repository.api.ResponseStatus; import org.lilyproject.repository.api.TypeManager; import org.lilyproject.repository.api.ValueType; import org.lilyproject.util.repo.SystemFields; public class MutationConditionVerifier { private MutationConditionVerifier() { } /** * Checks a set of conditions, if not satisfied it returns false and sets the response * status in the supplied record object to conflict, as well as reduces the fields to those * that were submitted. * * @param record the complete record state as currently stored in the repository * @param conditions the conditions that should be satisfied * * @return null if conditions are satisfied, a Record object if not. */ public static Record checkConditions(Record record, List<MutationCondition> conditions, Repository repository, Record newRecord) throws RepositoryException, InterruptedException { if (conditions == null) { return null; } boolean allSatisfied = true; // TODO should we check with the typemanager that the supplied value types are // of the correct type? this is not necessary for us, but might be helpful to the // user. OTOH, since users most of the time will go through a remote itf, such checks // will likely already have happened as part of serialization/deserialization code. SystemFields systemFields = SystemFields.getInstance(repository.getTypeManager(), repository.getIdGenerator()); for (MutationCondition condition : conditions) { Object value = systemFields.softEval(record, condition.getField(), repository.getTypeManager()); // Compare with null value is special case, handle this first if (condition.getValue() == null) { if (condition.getOp() == CompareOp.EQUAL) { if (value == null) { continue; } else { allSatisfied = false; break; } } else if (condition.getOp() == CompareOp.NOT_EQUAL) { if (value != null) { continue; } else { allSatisfied = false; break; } } else { throw new RepositoryException("When comparing to null, only (not-)equal operator is allowed. " + "Operator: " + condition.getOp() + ", field: " + condition.getField()); } } if (value == null && condition.getAllowMissing()) { continue; } if (value == null) { allSatisfied = false; break; } if (!checkValue(condition, value, repository.getTypeManager())) { allSatisfied = false; break; } } if (!allSatisfied) { Record responseRecord = record.cloneRecord(); if (newRecord != null) { // reduce the fields to return to those that were submitted reduceFields(responseRecord, newRecord.getFields().keySet()); } else { // reduce the fields to those on which conditions were put reduceFields(responseRecord, extractFields(conditions)); } responseRecord.setResponseStatus(ResponseStatus.CONFLICT); return responseRecord; } return null; } private static Set<QName> extractFields(List<MutationCondition> conditions) { Set<QName> fields = new HashSet<QName>(); for (MutationCondition condition : conditions) { fields.add(condition.getField()); } return fields; } private static boolean checkValue(MutationCondition cond, Object currentValue, TypeManager typeManager) throws RepositoryException, InterruptedException { if (cond.getOp() == CompareOp.EQUAL) { return currentValue.equals(cond.getValue()); } else if (cond.getOp() == CompareOp.NOT_EQUAL) { return !currentValue.equals(cond.getValue()); } ValueType valueType = typeManager.getFieldTypeByName(cond.getField()).getValueType(); Comparator comparator = valueType.getComparator(); if (comparator == null) { throw new RepositoryException("Other than (not-)equals operator in mutation condition used for value type " + "that does not support comparison: " + valueType.getName()); } int result = comparator.compare(currentValue, cond.getValue()); if (result == 0) { switch (cond.getOp()) { case EQUAL: case GREATER_OR_EQUAL: case LESS_OR_EQUAL: return true; default: return false; } } else if (result < 0) { switch (cond.getOp()) { case LESS: case LESS_OR_EQUAL: case NOT_EQUAL: return true; default: return false; } } else { // result > 0 switch (cond.getOp()) { case GREATER: case GREATER_OR_EQUAL: case NOT_EQUAL: return true; default: return false; } } } private static void reduceFields(Record record, Set<QName> fieldsToInclude) { Iterator<QName> it = record.getFields().keySet().iterator(); while (it.hasNext()) { QName name = it.next(); if (!fieldsToInclude.contains(name)) { it.remove(); } } } }