/* * Copyright (c) 2014 Evolveum * * 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 com.evolveum.midpoint.model.impl.lens.projector; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.EqualFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.caching.AbstractCache; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; /** * @author semancik * */ public class FocusConstraintsChecker<F extends FocusType> { private static ThreadLocal<Cache> cacheThreadLocal = new ThreadLocal<>(); private static final Trace LOGGER = TraceManager.getTrace(FocusConstraintsChecker.class); private static final Trace PERFORMANCE_ADVISOR = TraceManager.getPerformanceAdvisorTrace(); private LensContext<F> context; private PrismContext prismContext; private RepositoryService repositoryService; private boolean satisfiesConstraints; private StringBuilder messageBuilder = new StringBuilder(); private PrismObject<F> conflictingObject; public FocusConstraintsChecker() { } public PrismContext getPrismContext() { return prismContext; } public void setPrismContext(PrismContext prismContext) { this.prismContext = prismContext; } public LensContext<F> getContext() { return context; } public void setContext(LensContext<F> context) { this.context = context; } public RepositoryService getRepositoryService() { return repositoryService; } public void setRepositoryService(RepositoryService repositoryService) { this.repositoryService = repositoryService; } public boolean isSatisfiesConstraints() { return satisfiesConstraints; } public String getMessages() { return messageBuilder.toString(); } public PrismObject<F> getConflictingObject() { return conflictingObject; } public void check(PrismObject<F> objectNew, OperationResult result) throws SchemaException { if (objectNew == null) { // This must be delete LOGGER.trace("No new object. Therefore it satisfies constraints"); satisfiesConstraints = true; return; } // Hardcode to name ... for now PolyStringType name = objectNew.asObjectable().getName(); if (Cache.isOk(name)) { satisfiesConstraints = true; } else { satisfiesConstraints = checkPropertyUniqueness(objectNew, new ItemPath(ObjectType.F_NAME), context, result); if (satisfiesConstraints) { Cache.setOk(name); } } } private <T> boolean checkPropertyUniqueness(PrismObject<F> objectNew, ItemPath propPath, LensContext<F> context, OperationResult result) throws SchemaException { PrismProperty<T> property = objectNew.findProperty(propPath); if (property == null || property.isEmpty()) { throw new SchemaException("No property "+propPath+" in new object "+objectNew+", cannot check uniqueness"); } String oid = objectNew.getOid(); ObjectQuery query = QueryBuilder.queryFor(objectNew.getCompileTimeClass(), prismContext) .itemAs(property) .build(); List<PrismObject<F>> foundObjects = repositoryService.searchObjects(objectNew.getCompileTimeClass(), query, null, result); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Uniqueness check of {}, property {} resulted in {} results, using query:\n{}", new Object[]{objectNew, propPath, foundObjects.size(), query.debugDump()}); } if (foundObjects.isEmpty()) { return true; } if (foundObjects.size() > 1) { LOGGER.trace("Found more than one object with property "+propPath+" = " + property); message("Found more than one object with property "+propPath+" = " + property); return false; } LOGGER.trace("Comparing {} and {}", foundObjects.get(0).getOid(), oid); boolean match = foundObjects.get(0).getOid().equals(oid); if (!match) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Found conflicting existing object with property "+propPath+" = " + property + ":\n" + foundObjects.get(0).debugDump()); } message("Found conflicting existing object with property "+propPath+" = " + property + ": " + foundObjects.get(0)); conflictingObject = foundObjects.get(0); } return match; } private void message(String message) { if (messageBuilder.length() != 0) { messageBuilder.append(", "); } messageBuilder.append(message); } public static void enterCache() { Cache.enter(cacheThreadLocal, Cache.class, LOGGER); } public static void exitCache() { Cache.exit(cacheThreadLocal, LOGGER); } public static <T extends ObjectType> void clearCacheFor(PolyStringType name) { Cache.remove(name); } public static void clearCacheForValues(Collection<? extends PrismValue> values) { if (values == null) { return; } for (PrismValue value : values) { if (value instanceof PrismPropertyValue) { Object real = ((PrismPropertyValue) value).getValue(); if (real instanceof PolyStringType) { clearCacheFor((PolyStringType) real); } } } } public static void clearCacheForDelta(Collection<? extends ItemDelta> modifications) { if (modifications == null) { return; } for (ItemDelta itemDelta : modifications) { if (new ItemPath(ObjectType.F_NAME).equivalent(itemDelta.getPath())) { clearCacheForValues(itemDelta.getValuesToAdd()); // these may present a conflict clearCacheForValues(itemDelta.getValuesToReplace()); // so do these } } } public static class Cache extends AbstractCache { private Set<String> conflictFreeNames = new HashSet<>(); public static boolean isOk(PolyStringType name) { if (name == null) { log("Null name"); return false; // strange case } Cache cache = getCache(); if (cache == null) { log("Cache NULL for {}", name); return false; } if (cache.conflictFreeNames.contains(name.getOrig())) { log("Cache HIT for {}", name); return true; } else { log("Cache MISS for {}", name); return false; } } public static void setOk(PolyStringType name) { Cache cache = getCache(); if (name != null && cache != null) { cache.conflictFreeNames.add(name.getOrig()); } } private static Cache getCache() { return cacheThreadLocal.get(); } public static void remove(PolyStringType name) { Cache cache = getCache(); if (name != null && cache != null) { log("Cache REMOVE for {}", name); cache.conflictFreeNames.remove(name.getOrig()); } } @Override public String description() { return "conflict-free names: " + conflictFreeNames; } private static void log(String message, Object... params) { if (LOGGER.isTraceEnabled()) { LOGGER.trace(message, params); } if (PERFORMANCE_ADVISOR.isTraceEnabled()) { PERFORMANCE_ADVISOR.trace(message, params); } } } }