/* * Copyright (c) 2010-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.repo.sql; import com.evolveum.midpoint.common.LoggingConfigurationManager; import com.evolveum.midpoint.common.ProfilingConfigurationManager; import com.evolveum.midpoint.common.SystemConfigurationHolder; import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration; import com.evolveum.midpoint.common.crypto.CryptoUtil; import com.evolveum.midpoint.prism.ConsistencyCheckScope; import com.evolveum.midpoint.prism.Containerable; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.query.AllFilter; import com.evolveum.midpoint.prism.query.NoneFilter; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectPaging; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.QueryJaxbConvertor; import com.evolveum.midpoint.repo.api.RepoAddOptions; import com.evolveum.midpoint.repo.api.RepoModifyOptions; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.sql.helpers.*; import com.evolveum.midpoint.repo.sql.query2.matcher.DefaultMatcher; import com.evolveum.midpoint.repo.sql.query2.matcher.PolyStringMatcher; import com.evolveum.midpoint.repo.sql.query2.matcher.StringMatcher; import com.evolveum.midpoint.schema.*; import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.schema.util.ObjectQueryUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; 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.*; import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; import org.apache.commons.configuration.Configuration; import org.apache.commons.lang.Validate; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.jdbc.Work; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.List; import java.util.Properties; import javax.xml.namespace.QName; /** * @author lazyman * * Note: don't autowire this class - because of Spring AOP use it couldn't be found by implementation class; only by its interface. */ @Repository public class SqlRepositoryServiceImpl extends SqlBaseService implements RepositoryService { public static final String PERFORMANCE_LOG_NAME = SqlRepositoryServiceImpl.class.getName() + ".performance"; private static final Trace LOGGER = TraceManager.getTrace(SqlRepositoryServiceImpl.class); private static final Trace LOGGER_PERFORMANCE = TraceManager.getTrace(PERFORMANCE_LOG_NAME); public static final int MAX_CONSTRAINT_NAME_LENGTH = 40; private static final String IMPLEMENTATION_SHORT_NAME = "SQL"; private static final String IMPLEMENTATION_DESCRIPTION = "Implementation that stores data in generic relational" + " (SQL) databases. It is using ORM (hibernate) on top of JDBC to access the database."; private static final String DETAILS_TRANSACTION_ISOLATION = "transactionIsolation"; private static final String DETAILS_CLIENT_INFO = "clientInfo."; private static final String DETAILS_DATA_SOURCE = "dataSource"; private static final String DETAILS_HIBERNATE_DIALECT = "hibernateDialect"; private static final String DETAILS_HIBERNATE_HBM_2_DDL = "hibernateHbm2ddl"; @Autowired private SequenceHelper sequenceHelper; @Autowired private ObjectRetriever objectRetriever; @Autowired private ObjectUpdater objectUpdater; @Autowired private OrgClosureManager closureManager; @Autowired private BaseHelper baseHelper; @Autowired private MatchingRuleRegistry matchingRuleRegistry; @Autowired private MidpointConfiguration midpointConfiguration; private FullTextSearchConfigurationType fullTextSearchConfiguration; public SqlRepositoryServiceImpl(SqlRepositoryFactory repositoryFactory) { super(repositoryFactory); } // public because of testing public OrgClosureManager getClosureManager() { return closureManager; } @Override public <T extends ObjectType> PrismObject<T> getObject(Class<T> type, String oid, Collection<SelectorOptions<GetOperationOptions>> options, OperationResult result) throws ObjectNotFoundException, SchemaException { Validate.notNull(type, "Object type must not be null."); Validate.notEmpty(oid, "Oid must not be null or empty."); Validate.notNull(result, "Operation result must not be null."); LOGGER.debug("Getting object '{}' with oid '{}'.", new Object[]{type.getSimpleName(), oid}); final String operation = "getting"; int attempt = 1; OperationResult subResult = result.createMinorSubresult(GET_OBJECT); subResult.addParam("type", type.getName()); subResult.addParam("oid", oid); SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("getObject"); try { while (true) { try { return objectRetriever.getObjectAttempt(type, oid, options, subResult); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(oid, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @Override public <F extends FocusType> PrismObject<F> searchShadowOwner(String shadowOid, Collection<SelectorOptions<GetOperationOptions>> options, OperationResult result) { Validate.notEmpty(shadowOid, "Oid must not be null or empty."); Validate.notNull(result, "Operation result must not be null."); LOGGER.debug("Searching shadow owner for {}", shadowOid); final String operation = "searching shadow owner"; int attempt = 1; OperationResult subResult = result.createSubresult(SEARCH_SHADOW_OWNER); subResult.addParam("shadowOid", shadowOid); while (true) { try { return objectRetriever.searchShadowOwnerAttempt(shadowOid, options, subResult); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(shadowOid, operation, attempt, ex, subResult); } } } @Override @Deprecated public PrismObject<UserType> listAccountShadowOwner(String accountOid, OperationResult result) throws ObjectNotFoundException { Validate.notEmpty(accountOid, "Oid must not be null or empty."); Validate.notNull(result, "Operation result must not be null."); LOGGER.debug("Selecting account shadow owner for account {}.", new Object[]{accountOid}); final String operation = "listing account shadow owner"; int attempt = 1; OperationResult subResult = result.createSubresult(LIST_ACCOUNT_SHADOW); subResult.addParam("accountOid", accountOid); while (true) { try { return objectRetriever.listAccountShadowOwnerAttempt(accountOid, subResult); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(accountOid, operation, attempt, ex, subResult); } } } @NotNull @Override public <T extends ObjectType> SearchResultList<PrismObject<T>> searchObjects(Class<T> type, ObjectQuery query, Collection<SelectorOptions<GetOperationOptions>> options, OperationResult result) throws SchemaException { Validate.notNull(type, "Object type must not be null."); Validate.notNull(result, "Operation result must not be null."); logSearchInputParameters(type, query, false, null); OperationResult subResult = result.createSubresult(SEARCH_OBJECTS); subResult.addParam("type", type.getName()); subResult.addParam("query", query); // subResult.addParam("paging", paging); if (query != null) { ObjectFilter filter = query.getFilter(); filter = ObjectQueryUtil.simplify(filter); if (filter instanceof NoneFilter) { subResult.recordSuccess(); return new SearchResultList(new ArrayList<PrismObject<T>>(0)); } else { query = replaceSimplifiedFilter(query, filter); } } SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("searchObjects"); final String operation = "searching"; int attempt = 1; try { while (true) { try { return objectRetriever.searchObjectsAttempt(type, query, options, subResult); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(null, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @NotNull private ObjectQuery replaceSimplifiedFilter(ObjectQuery query, ObjectFilter filter) { query = query.cloneEmpty(); query.setFilter(filter instanceof AllFilter ? null : filter); return query; } @Override public <T extends Containerable> SearchResultList<T> searchContainers(Class<T> type, ObjectQuery query, Collection<SelectorOptions<GetOperationOptions>> options, OperationResult parentResult) throws SchemaException { Validate.notNull(type, "Object type must not be null."); Validate.notNull(parentResult, "Operation result must not be null."); logSearchInputParameters(type, query, false, null); OperationResult result = parentResult.createSubresult(SEARCH_CONTAINERS); result.addParam("type", type.getName()); result.addParam("query", query); if (query != null) { ObjectFilter filter = query.getFilter(); filter = ObjectQueryUtil.simplify(filter); if (filter instanceof NoneFilter) { result.recordSuccess(); return new SearchResultList<>(new ArrayList<T>(0)); } else { query = replaceSimplifiedFilter(query, filter); } } SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("searchContainers"); final String operation = "searching"; int attempt = 1; try { while (true) { try { return objectRetriever.searchContainersAttempt(type, query, options, result); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(null, operation, attempt, ex, result); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } private <T> void logSearchInputParameters(Class<T> type, ObjectQuery query, boolean iterative, Boolean strictlySequential) { ObjectPaging paging = query != null ? query.getPaging() : null; LOGGER.debug("Searching objects of type '{}', query (on trace level), offset {}, count {}, iterative {}, strictlySequential {}.", type.getSimpleName(), (paging != null ? paging.getOffset() : "undefined"), (paging != null ? paging.getMaxSize() : "undefined"), iterative, strictlySequential); if (!LOGGER.isTraceEnabled()) { return; } LOGGER.trace("Full query\n{}\nFull paging\n{}", new Object[]{ (query == null ? "undefined" : query.debugDump()), (paging != null ? paging.debugDump() : "undefined")}); if (iterative) { LOGGER.trace("Iterative search by paging: {}, batch size {}", getConfiguration().isIterativeSearchByPaging(), getConfiguration().getIterativeSearchByPagingBatchSize()); } } @Override public <T extends ObjectType> String addObject(PrismObject<T> object, RepoAddOptions options, OperationResult result) throws ObjectAlreadyExistsException, SchemaException { Validate.notNull(object, "Object must not be null."); validateName(object); Validate.notNull(result, "Operation result must not be null."); if (options == null) { options = new RepoAddOptions(); } LOGGER.debug("Adding object type '{}', overwrite={}, allowUnencryptedValues={}", object.getCompileTimeClass().getSimpleName(), options.isOverwrite(), options.isAllowUnencryptedValues()); if (InternalsConfig.encryptionChecks && !RepoAddOptions.isAllowUnencryptedValues(options)) { CryptoUtil.checkEncrypted(object); } if (InternalsConfig.consistencyChecks) { object.checkConsistence(ConsistencyCheckScope.THOROUGH); } else { object.checkConsistence(ConsistencyCheckScope.MANDATORY_CHECKS_ONLY); } if (LOGGER.isTraceEnabled()) { // Explicitly log name PolyStringType namePolyType = object.asObjectable().getName(); LOGGER.trace("NAME: {} - {}", namePolyType.getOrig(), namePolyType.getNorm()); } OperationResult subResult = result.createSubresult(ADD_OBJECT); subResult.addParam("object", object); subResult.addParam("options", options); final String operation = "adding"; int attempt = 1; String oid = object.getOid(); while (true) { try { return objectUpdater.addObjectAttempt(object, options, subResult); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(oid, operation, attempt, ex, subResult); } } } private void validateName(PrismObject object) throws SchemaException { PrismProperty name = object.findProperty(ObjectType.F_NAME); if (name == null || ((PolyString) name.getRealValue()).isEmpty()) { throw new SchemaException("Attempt to add object without name."); } } @Override public <T extends ObjectType> void deleteObject(Class<T> type, String oid, OperationResult result) throws ObjectNotFoundException { Validate.notNull(type, "Object type must not be null."); Validate.notEmpty(oid, "Oid must not be null or empty."); Validate.notNull(result, "Operation result must not be null."); LOGGER.debug("Deleting object type '{}' with oid '{}'", new Object[]{type.getSimpleName(), oid}); final String operation = "deleting"; int attempt = 1; OperationResult subResult = result.createSubresult(DELETE_OBJECT); subResult.addParam("type", type.getName()); subResult.addParam("oid", oid); SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("deleteObject"); try { while (true) { try { objectUpdater.deleteObjectAttempt(type, oid, subResult); return; } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(oid, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @Override public <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query, OperationResult result) { return countObjects(type, query, null, result); } public <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query, Collection<SelectorOptions<GetOperationOptions>> options, OperationResult result) { Validate.notNull(type, "Object type must not be null."); Validate.notNull(result, "Operation result must not be null."); LOGGER.debug("Counting objects of type '{}', query (on trace level).", new Object[]{type.getSimpleName()}); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Full query\n{}", new Object[]{(query == null ? "undefined" : query.debugDump())}); } OperationResult subResult = result.createMinorSubresult(COUNT_OBJECTS); subResult.addParam("type", type.getName()); subResult.addParam("query", query); if (query != null) { ObjectFilter filter = query.getFilter(); filter = ObjectQueryUtil.simplify(filter); if (filter instanceof NoneFilter) { subResult.recordSuccess(); return 0; } query = query.cloneEmpty(); query.setFilter(filter); } final String operation = "counting"; int attempt = 1; while (true) { try { return objectRetriever.countObjectsAttempt(type, query, options, subResult); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(null, operation, attempt, ex, subResult); } } } @Override public <T extends ObjectType> void modifyObject(Class<T> type, String oid, Collection<? extends ItemDelta> modifications, OperationResult result) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { modifyObject(type, oid, modifications, null, result); } @Override public <T extends ObjectType> void modifyObject(Class<T> type, String oid, Collection<? extends ItemDelta> modifications, RepoModifyOptions options, OperationResult result) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { Validate.notNull(modifications, "Modifications must not be null."); Validate.notNull(type, "Object class in delta must not be null."); Validate.notEmpty(oid, "Oid must not null or empty."); Validate.notNull(result, "Operation result must not be null."); OperationResult subResult = result.createSubresult(MODIFY_OBJECT); subResult.addParam("type", type.getName()); subResult.addParam("oid", oid); subResult.addCollectionOfSerializablesAsParam("modifications", modifications); if (modifications.isEmpty() && !RepoModifyOptions.isExecuteIfNoChanges(options)) { LOGGER.debug("Modification list is empty, nothing was modified."); subResult.recordStatus(OperationResultStatus.SUCCESS, "Modification list is empty, nothing was modified."); return; } if (InternalsConfig.encryptionChecks) { CryptoUtil.checkEncrypted(modifications); } if (InternalsConfig.consistencyChecks) { ItemDelta.checkConsistence(modifications, ConsistencyCheckScope.THOROUGH); } else { ItemDelta.checkConsistence(modifications, ConsistencyCheckScope.MANDATORY_CHECKS_ONLY); } if (LOGGER.isTraceEnabled()) { for (ItemDelta modification : modifications) { if (modification instanceof PropertyDelta<?>) { PropertyDelta<?> propDelta = (PropertyDelta<?>) modification; if (propDelta.getPath().equivalent(new ItemPath(ObjectType.F_NAME))) { Collection<PrismPropertyValue<PolyString>> values = propDelta.getValues(PolyString.class); for (PrismPropertyValue<PolyString> pval : values) { PolyString value = pval.getValue(); LOGGER.trace("NAME delta: {} - {}", value.getOrig(), value.getNorm()); } } } } } final String operation = "modifying"; int attempt = 1; SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("modifyObject"); try { while (true) { try { objectUpdater.modifyObjectAttempt(type, oid, modifications, options, subResult); return; } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(oid, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @Override public <T extends ShadowType> List<PrismObject<T>> listResourceObjectShadows(String resourceOid, Class<T> resourceObjectShadowType, OperationResult result) throws ObjectNotFoundException, SchemaException { Validate.notEmpty(resourceOid, "Resource oid must not be null or empty."); Validate.notNull(resourceObjectShadowType, "Resource object shadow type must not be null."); Validate.notNull(result, "Operation result must not be null."); LOGGER.debug("Listing resource object shadows '{}' for resource '{}'.", new Object[]{resourceObjectShadowType.getSimpleName(), resourceOid}); OperationResult subResult = result.createSubresult(LIST_RESOURCE_OBJECT_SHADOWS); subResult.addParam("oid", resourceOid); subResult.addParam("resourceObjectShadowType", resourceObjectShadowType); final String operation = "listing resource object shadows"; int attempt = 1; SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("listResourceObjectShadow"); try { while (true) { try { return objectRetriever.listResourceObjectShadowsAttempt(resourceOid, resourceObjectShadowType, subResult); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(resourceOid, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @Override public RepositoryDiag getRepositoryDiag() { LOGGER.debug("Getting repository diagnostics."); RepositoryDiag diag = new RepositoryDiag(); diag.setImplementationShortName(IMPLEMENTATION_SHORT_NAME); diag.setImplementationDescription(IMPLEMENTATION_DESCRIPTION); SqlRepositoryConfiguration config = getConfiguration(); //todo improve, find and use real values (which are used by sessionFactory) MID-1219 diag.setDriverShortName(config.getDriverClassName()); diag.setRepositoryUrl(config.getJdbcUrl()); diag.setEmbedded(config.isEmbedded()); Enumeration<Driver> drivers = DriverManager.getDrivers(); while ((drivers != null && drivers.hasMoreElements())) { Driver driver = drivers.nextElement(); if (!driver.getClass().getName().equals(config.getDriverClassName())) { continue; } diag.setDriverVersion(driver.getMajorVersion() + "." + driver.getMinorVersion()); } List<LabeledString> details = new ArrayList<>(); diag.setAdditionalDetails(details); details.add(new LabeledString(DETAILS_DATA_SOURCE, config.getDataSource())); details.add(new LabeledString(DETAILS_HIBERNATE_DIALECT, config.getHibernateDialect())); details.add(new LabeledString(DETAILS_HIBERNATE_HBM_2_DDL, config.getHibernateHbm2ddl())); readDetailsFromConnection(diag, config); Collections.sort(details, new Comparator<LabeledString>() { @Override public int compare(LabeledString o1, LabeledString o2) { return String.CASE_INSENSITIVE_ORDER.compare(o1.getLabel(), o2.getLabel()); } }); return diag; } private void readDetailsFromConnection(RepositoryDiag diag, final SqlRepositoryConfiguration config) { final List<LabeledString> details = diag.getAdditionalDetails(); Session session = baseHelper.getSessionFactory().openSession(); try { session.beginTransaction(); session.doWork(new Work() { @Override public void execute(Connection connection) throws SQLException { details.add(new LabeledString(DETAILS_TRANSACTION_ISOLATION, getTransactionIsolation(connection, config))); Properties info = connection.getClientInfo(); if (info == null) { return; } for (String name : info.stringPropertyNames()) { details.add(new LabeledString(DETAILS_CLIENT_INFO + name, info.getProperty(name))); } } }); session.getTransaction().commit(); SessionFactory sessionFactory = baseHelper.getSessionFactory(); if (!(sessionFactory instanceof SessionFactoryImpl)) { return; } SessionFactoryImpl sessionFactoryImpl = (SessionFactoryImpl) sessionFactory; // we try to override configuration which was read from sql repo configuration with // real configuration from session factory String dialect = sessionFactoryImpl.getDialect() != null ? sessionFactoryImpl.getDialect().getClass().getName() : null; details.add(new LabeledString(DETAILS_HIBERNATE_DIALECT, dialect)); } catch (Throwable th) { //nowhere to report error (no operation result available) session.getTransaction().rollback(); } finally { baseHelper.cleanupSessionAndResult(session, null); } } private String getTransactionIsolation(Connection connection, SqlRepositoryConfiguration config) { String value = config.getTransactionIsolation() != null ? config.getTransactionIsolation().name() + "(read from repo configuration)" : null; try { switch (connection.getTransactionIsolation()) { case Connection.TRANSACTION_NONE: value = "TRANSACTION_NONE (read from connection)"; break; case Connection.TRANSACTION_READ_COMMITTED: value = "TRANSACTION_READ_COMMITTED (read from connection)"; break; case Connection.TRANSACTION_READ_UNCOMMITTED: value = "TRANSACTION_READ_UNCOMMITTED (read from connection)"; break; case Connection.TRANSACTION_REPEATABLE_READ: value = "TRANSACTION_REPEATABLE_READ (read from connection)"; break; case Connection.TRANSACTION_SERIALIZABLE: value = "TRANSACTION_SERIALIZABLE (read from connection)"; break; default: value = "Unknown value in connection."; } } catch (Exception ex) { //nowhere to report error (no operation result available) } return value; } /* (non-Javadoc) * @see com.evolveum.midpoint.repo.api.RepositoryService#repositorySelfTest(com.evolveum.midpoint.schema.result.OperationResult) */ @Override public void repositorySelfTest(OperationResult parentResult) { // TODO add some SQL-specific self-test methods // No self-tests for now } @Override public void testOrgClosureConsistency(boolean repairIfNecessary, OperationResult testResult) { getClosureManager().checkAndOrRebuild(true, repairIfNecessary, false, false, testResult); } @Override public <T extends ObjectType> String getVersion(Class<T> type, String oid, OperationResult parentResult) throws ObjectNotFoundException, SchemaException { Validate.notNull(type, "Object type must not be null."); Validate.notNull(oid, "Object oid must not be null."); Validate.notNull(parentResult, "Operation result must not be null."); LOGGER.debug("Getting version for {} with oid '{}'.", new Object[]{type.getSimpleName(), oid}); OperationResult subResult = parentResult.createMinorSubresult(GET_VERSION); subResult.addParam("type", type.getName()); subResult.addParam("oid", oid); SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart(GET_VERSION); final String operation = "getting version"; int attempt = 1; try { while (true) { try { return objectRetriever.getVersionAttempt(type, oid, subResult); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(null, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @Override public <T extends ObjectType> SearchResultMetadata searchObjectsIterative(Class<T> type, ObjectQuery query, ResultHandler<T> handler, Collection<SelectorOptions<GetOperationOptions>> options, boolean strictlySequential, OperationResult result) throws SchemaException { Validate.notNull(type, "Object type must not be null."); Validate.notNull(handler, "Result handler must not be null."); Validate.notNull(result, "Operation result must not be null."); logSearchInputParameters(type, query, true, strictlySequential); OperationResult subResult = result.createSubresult(SEARCH_OBJECTS_ITERATIVE); subResult.addParam("type", type.getName()); subResult.addParam("query", query); if (query != null) { ObjectFilter filter = query.getFilter(); filter = ObjectQueryUtil.simplify(filter); if (filter instanceof NoneFilter) { subResult.recordSuccess(); return null; } else { query = replaceSimplifiedFilter(query, filter); } } if (getConfiguration().isIterativeSearchByPaging()) { if (strictlySequential) { objectRetriever.searchObjectsIterativeByPagingStrictlySequential(type, query, handler, options, subResult); } else { objectRetriever.searchObjectsIterativeByPaging(type, query, handler, options, subResult); } return null; } // turned off until resolved 'unfinished operation' warning // SqlPerformanceMonitor pm = getPerformanceMonitor(); // long opHandle = pm.registerOperationStart(SEARCH_OBJECTS_ITERATIVE); final String operation = "searching iterative"; int attempt = 1; try { while (true) { try { objectRetriever.searchObjectsIterativeAttempt(type, query, handler, options, subResult); return null; } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(null, operation, attempt, ex, subResult); // pm.registerOperationNewTrial(opHandle, attempt); } } } finally { // pm.registerOperationFinish(opHandle, attempt); } } @Override public boolean isAnySubordinate(String upperOrgOid, Collection<String> lowerObjectOids) throws SchemaException { Validate.notNull(upperOrgOid, "upperOrgOid must not be null."); Validate.notNull(lowerObjectOids, "lowerObjectOids must not be null."); if (LOGGER.isTraceEnabled()) LOGGER.trace("Querying for subordination upper {}, lower {}", new Object[]{upperOrgOid, lowerObjectOids}); if (lowerObjectOids.isEmpty()) { // trivial case return false; } int attempt = 1; SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("matchObject"); try { while (true) { try { return objectRetriever.isAnySubordinateAttempt(upperOrgOid, lowerObjectOids); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(upperOrgOid, "isAnySubordinate", attempt, ex, null); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @Override public long advanceSequence(String oid, OperationResult parentResult) throws ObjectNotFoundException, SchemaException { Validate.notEmpty(oid, "Oid must not null or empty."); Validate.notNull(parentResult, "Operation result must not be null."); OperationResult result = parentResult.createSubresult(ADVANCE_SEQUENCE); result.addParam("oid", oid); if (LOGGER.isTraceEnabled()) LOGGER.trace("Advancing sequence {}", oid); int attempt = 1; SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("advanceSequence"); try { while (true) { try { return sequenceHelper.advanceSequenceAttempt(oid, result); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(oid, "advanceSequence", attempt, ex, null); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @Override public void returnUnusedValuesToSequence(String oid, Collection<Long> unusedValues, OperationResult parentResult) throws ObjectNotFoundException, SchemaException { Validate.notEmpty(oid, "Oid must not null or empty."); Validate.notNull(parentResult, "Operation result must not be null."); OperationResult result = parentResult.createSubresult(RETURN_UNUSED_VALUES_TO_SEQUENCE); result.addParam("oid", oid); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Returning unused values of {} to sequence {}", unusedValues, oid); } if (unusedValues == null || unusedValues.isEmpty()) { result.recordSuccess(); return; } int attempt = 1; SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("returnUnusedValuesToSequence"); try { while (true) { try { sequenceHelper.returnUnusedValuesToSequenceAttempt(oid, unusedValues, result); return; } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(oid, "returnUnusedValuesToSequence", attempt, ex, null); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @Override public RepositoryQueryDiagResponse executeQueryDiagnostics(RepositoryQueryDiagRequest request, OperationResult result) { Validate.notNull(request, "Request must not be null."); Validate.notNull(result, "Operation result must not be null."); LOGGER.debug("Executing arbitrary query '{}'.", request); final String operation = "querying"; int attempt = 1; OperationResult subResult = result.createMinorSubresult(EXECUTE_QUERY_DIAGNOSTICS); subResult.addParam("query", request); SqlPerformanceMonitor pm = getPerformanceMonitor(); long opHandle = pm.registerOperationStart("executeQueryDiagnostics"); try { while (true) { try { return objectRetriever.executeQueryDiagnosticsRequest(request, subResult); } catch (RuntimeException ex) { attempt = baseHelper.logOperationAttempt(null, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); } } } finally { pm.registerOperationFinish(opHandle, attempt); } } @Override public <O extends ObjectType> boolean selectorMatches(ObjectSelectorType objectSelector, PrismObject<O> object, Trace logger, String logMessagePrefix) throws SchemaException { if (objectSelector == null) { logger.trace("{} null object specification", logMessagePrefix); return false; } SearchFilterType specFilterType = objectSelector.getFilter(); ObjectReferenceType specOrgRef = objectSelector.getOrgRef(); QName specTypeQName = objectSelector.getType(); // now it does not matter if it's unqualified PrismObjectDefinition<O> objectDefinition = object.getDefinition(); // Type if (specTypeQName != null && !QNameUtil.match(specTypeQName, objectDefinition.getTypeName())) { logger.trace("{} type mismatch, expected {}, was {}", logMessagePrefix, specTypeQName, objectDefinition.getTypeName()); return false; } // Filter if (specFilterType != null) { ObjectFilter specFilter = QueryJaxbConvertor.createObjectFilter(object.getCompileTimeClass(), specFilterType, object.getPrismContext()); ObjectTypeUtil.normalizeFilter(specFilter); // we assume object is already normalized if (specFilter != null) { ObjectQueryUtil.assertPropertyOnly(specFilter, logMessagePrefix + " filter is not property-only filter"); } try { if (!ObjectQuery.match(object, specFilter, matchingRuleRegistry)) { logger.trace("{} object OID {}", logMessagePrefix, object.getOid() ); return false; } } catch (SchemaException ex) { throw new SchemaException(logMessagePrefix + "could not apply for " + object + ": " + ex.getMessage(), ex); } } // Org if (specOrgRef != null) { if (!isDescendant(object, specOrgRef.getOid())) { LOGGER.trace("{} object OID {} (org={})", logMessagePrefix, object.getOid(), specOrgRef.getOid()); return false; } } return true; } @Override public <O extends ObjectType> boolean isDescendant(PrismObject<O> object, String orgOid) throws SchemaException { List<ObjectReferenceType> objParentOrgRefs = object.asObjectable().getParentOrgRef(); List<String> objParentOrgOids = new ArrayList<>(objParentOrgRefs.size()); for (ObjectReferenceType objParentOrgRef: objParentOrgRefs) { objParentOrgOids.add(objParentOrgRef.getOid()); } return isAnySubordinate(orgOid, objParentOrgOids); } @Override public <O extends ObjectType> boolean isAncestor(PrismObject<O> object, String oid) throws SchemaException { if (object.getOid() == null) { return false; } Collection<String> oidList = new ArrayList<>(1); oidList.add(oid); return isAnySubordinate(object.getOid(), oidList); } @Override public QName getApproximateSupportedMatchingRule(Class<?> dataType, QName originalMatchingRule) { if (String.class.equals(dataType)) { return StringMatcher.getApproximateSupportedMatchingRule(originalMatchingRule); } else if (PolyString.class.equals(dataType) || PolyStringType.class.equals(dataType)) { return PolyStringMatcher.getApproximateSupportedMatchingRule(originalMatchingRule); } else { return DefaultMatcher.getApproximateSupportedMatchingRule(originalMatchingRule); } } @Override public void applyFullTextSearchConfiguration(FullTextSearchConfigurationType fullTextSearch) { LOGGER.info("Applying full text search configuration ({} entries)", fullTextSearch != null ? fullTextSearch.getIndexed().size() : 0); fullTextSearchConfiguration = fullTextSearch; } @Override public FullTextSearchConfigurationType getFullTextSearchConfiguration() { return fullTextSearchConfiguration; } @Override public void postInit(OperationResult result) throws SchemaException { SystemConfigurationType systemConfiguration; try { systemConfiguration = getObject(SystemConfigurationType.class, SystemObjectsType.SYSTEM_CONFIGURATION.value(), null, result).asObjectable(); } catch (ObjectNotFoundException e) { // ok, no problem e.g. for tests or initial startup LOGGER.debug("System configuration not found, exiting postInit method."); return; } SystemConfigurationHolder.setCurrentConfiguration(systemConfiguration); Configuration systemConfigFromFile = midpointConfiguration.getConfiguration(MidpointConfiguration.SYSTEM_CONFIGURATION_SECTION); if (systemConfigFromFile != null && systemConfigFromFile .getBoolean(LoggingConfigurationManager.SYSTEM_CONFIGURATION_SKIP_REPOSITORY_LOGGING_SETTINGS, false)) { LOGGER.warn("Skipping application of repository logging configuration because {}=true", LoggingConfigurationManager.SYSTEM_CONFIGURATION_SKIP_REPOSITORY_LOGGING_SETTINGS); } else { LoggingConfigurationType loggingConfig = ProfilingConfigurationManager.checkSystemProfilingConfiguration( systemConfiguration.asPrismObject()); if (loggingConfig != null) { LoggingConfigurationManager.configure(loggingConfig, systemConfiguration.getVersion(), result); } } applyFullTextSearchConfiguration(systemConfiguration.getFullTextSearch()); } }