diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismContainerValue.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismContainerValue.java index a013bf8aabf..58727c31873 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismContainerValue.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismContainerValue.java @@ -873,7 +873,15 @@ public void removeContainer(ItemPath itemPath) { removeItem(itemPath, PrismContainer.class); } - // Expects that "self" path is NOT present in propPath + public void removeReference(QName name) { + removeReference(new ItemPath(name)); + } + + public void removeReference(ItemPath path) { + removeItem(path, PrismReference.class); + } + + // Expects that "self" path is NOT present in propPath > void removeItem(ItemPath propPath, Class itemType) { if (items == null){ return; diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd index 002406951bc..207fc4e6e20 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd @@ -12154,7 +12154,7 @@ - + @@ -12435,7 +12435,7 @@ - + diff --git a/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/AddGetObjectTest.java b/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/AddGetObjectTest.java index 3fa37040c29..8f3a8960a73 100644 --- a/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/AddGetObjectTest.java +++ b/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/AddGetObjectTest.java @@ -161,6 +161,9 @@ private void addGetCompare(File file) throws Exception { } else if (LookupTableType.class.equals(clazz)) { o = SelectorOptions.createCollection(LookupTableType.F_ROW, GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE)); + } else if (AccessCertificationCampaignType.class.equals(clazz)) { + o = SelectorOptions.createCollection(AccessCertificationCampaignType.F_CASE, + GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE)); } PrismObject newObject = repositoryService.getObject(clazz, oids.get(i), o, result); LOGGER.info("Old\n{}\nnew\n{}", new Object[]{object.debugDump(3), newObject.debugDump(3)}); diff --git a/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/QueryInterpreter2Test.java b/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/QueryInterpreter2Test.java index 49eff0e5753..a7be1f6bfcf 100644 --- a/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/QueryInterpreter2Test.java +++ b/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/QueryInterpreter2Test.java @@ -1368,7 +1368,7 @@ public void test330InOidTest() throws Exception { } @Test - public void test330OwnerInOidTest() throws Exception { + public void test335OwnerInOidTest() throws Exception { Session session = open(); try { ObjectQuery query = QueryBuilder.queryFor(AccessCertificationCaseType.class, prismContext) @@ -1376,17 +1376,11 @@ public void test330OwnerInOidTest() throws Exception { String real = getInterpretedQuery2(session, AccessCertificationCaseType.class, query, false); String expected = "select\n" + - " o.fullObject,\n" + - " o.stringsCount,\n" + - " o.longsCount,\n" + - " o.datesCount,\n" + - " o.referencesCount,\n" + - " o.polysCount,\n" + - " o.booleansCount\n" + + " a.fullObject\n" + "from\n" + - " RObject o\n" + + " RAccessCertificationCase a\n" + "where\n" + - " o.ownerOid in :oid\n"; + " a.ownerOid in :ownerOid"; assertEqualsIgnoreWhitespace(expected, real); } finally { close(session); @@ -1459,8 +1453,8 @@ public void test345QueryOrgAllLevels() throws Exception { "from\n" + " ROrg o\n" + "where\n" + - " o.oid in (select ref.ownerOid from RObjectReference ref where ref.referenceType = com.evolveum.midpoint.repo.sql.data.common.other.RReferenceOwner.OBJECT_PARENT_ORG and ref.targetOid = :orgOid)\n" + - "order by o.name.orig asc\n"; + " o.oid in (select ref.ownerOid from RObjectReference ref where ref.referenceType = com.evolveum.midpoint.repo.sql.data.common.other.RReferenceOwner.OBJECT_PARENT_ORG and ref.targetOid in (select descendantOid from ROrgClosure where ancestorOid = :orgOid))\n" + + "order by o.name.orig asc"; assertEqualsIgnoreWhitespace(expected, real); } finally { @@ -1494,8 +1488,8 @@ public void test348QueryRoots() throws Exception { "from\n" + " ROrg o\n" + "where\n" + - " o.oid in (select ref.ownerOid from RObjectReference ref where ref.referenceType = com.evolveum.midpoint.repo.sql.data.common.other.RReferenceOwner.OBJECT_PARENT_ORG and ref.targetOid = :orgOid)\n" + - "order by o.name.orig asc\n"; + " o.oid in (select descendantOid from ROrgClosure group by descendantOid having count(descendantOid) = 1)\n" + + "order by o.name.orig asc"; assertEqualsIgnoreWhitespace(expected, real); } finally { @@ -2024,7 +2018,7 @@ public void test530queryUserSubstringName() throws Exception { objectQuery = ObjectQuery.createObjectQuery(substring); objectQuery.setUseNewQueryInterpreter(true); count = repositoryService.countObjects(ObjectType.class, objectQuery, result); - AssertJUnit.assertEquals(18, count); + AssertJUnit.assertEquals(19, count); } finally { close(session); diff --git a/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/closure/OrgClosureConcurrencyTest.java b/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/closure/OrgClosureConcurrencyTest.java index 01f9823ab3c..cc3557ad76c 100644 --- a/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/closure/OrgClosureConcurrencyTest.java +++ b/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/closure/OrgClosureConcurrencyTest.java @@ -29,7 +29,6 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.testng.annotations.Test; @@ -41,7 +40,7 @@ import java.util.List; import java.util.Set; -import static com.evolveum.midpoint.repo.sql.OrgClosureManager.Edge; +import static com.evolveum.midpoint.repo.sql.helpers.OrgClosureManager.Edge; /** * @author mederly diff --git a/repo/repo-sql-impl-test/src/test/resources/basic/objects.xml b/repo/repo-sql-impl-test/src/test/resources/basic/objects.xml index b63b62e4e83..d2759b9d606 100644 --- a/repo/repo-sql-impl-test/src/test/resources/basic/objects.xml +++ b/repo/repo-sql-impl-test/src/test/resources/basic/objects.xml @@ -1345,4 +1345,228 @@ 3 value + + + All user assignments 1 + Certifies all users' assignments. Everything is certified by the administrator. + + 2015-12-18T23:59:59.999+01:00 + http://midpoint.evolveum.com/xml/ns/public/certification/trigger/close-stage/handler-3 + + + 2015-12-16T23:59:59.999+01:00 + http://midpoint.evolveum.com/xml/ns/public/certification/trigger/close-stage-approaching/handler-3 + + + 2015-12-18T11:59:59.999+01:00 + http://midpoint.evolveum.com/xml/ns/public/certification/trigger/close-stage-approaching/handler-3 + + + 2015-12-04T00:37:08.885+01:00 + + http://midpoint.evolveum.com/xml/ns/public/gui/channels-3#user + + + + http://midpoint.evolveum.com/xml/ns/public/certification/handlers-3#direct-assignment + + + + + 1 + Administrator's review + In this stage, the administrator has to review all the assignments of all users. + 14 + 48 + 12 + true + + + + + 2015-12-04T00:38:03.031+01:00 + inReviewStage + 1 + + 1 + Administrator's review + In this stage, the administrator has to review all the assignments of all users. + 2015-12-04T00:38:00.708+01:00 + 2015-12-18T23:59:59.999+01:00 + + + + + + 2015-12-04T00:38:00.708+01:00 + 2015-12-18T23:59:59.999+01:00 + + 1 + + notDecided + 2015-12-04T01:10:20.032+01:00 + + notDecided + 1 + + + + false + + + + + + 2015-12-04T00:38:00.708+01:00 + 2015-12-18T23:59:59.999+01:00 + + 1 + + accept + 2015-12-04T01:04:06.385+01:00 + + accept + 1 + + + + false + + + + + + 2015-12-04T00:38:00.708+01:00 + 2015-12-18T23:59:59.999+01:00 + + 1 + + noResponse + 2015-12-04T01:10:08.670+01:00 + + noResponse + 1 + + + + false + + + + + + 2015-12-04T00:38:00.708+01:00 + 2015-12-18T23:59:59.999+01:00 + + 1 + + revoke + 2015-12-04T01:10:13.814+01:00 + + revoke + 1 + + + + + ri:cn + + strong + + CN + + + + + ri:sn + + strong + + SN + + + + + + false + + + + + + 2015-12-04T00:38:00.708+01:00 + 2015-12-18T23:59:59.999+01:00 + + 1 + + revoke + 2015-12-04T01:10:14.614+01:00 + + revoke + 1 + + + + false + + + + + + 2015-12-04T00:38:00.708+01:00 + 2015-12-18T23:59:59.999+01:00 + + 1 + + reduce + 2015-12-04T01:10:15.375+01:00 + + reduce + 1 + + + + false + + + + + + 2015-12-04T00:38:00.708+01:00 + 2015-12-18T23:59:59.999+01:00 + + 1 + + revoke + 2015-12-04T01:10:16.136+01:00 + + revoke + 1 + + + + false + + + diff --git a/repo/repo-sql-impl-test/src/test/resources/logback-test.xml b/repo/repo-sql-impl-test/src/test/resources/logback-test.xml index 5c9eb7a216f..51f06856ad3 100644 --- a/repo/repo-sql-impl-test/src/test/resources/logback-test.xml +++ b/repo/repo-sql-impl-test/src/test/resources/logback-test.xml @@ -35,7 +35,7 @@ - + diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/ObjectPagingAfterOid.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/ObjectPagingAfterOid.java new file mode 100644 index 00000000000..5fab50e5165 --- /dev/null +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/ObjectPagingAfterOid.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2015 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.prism.query.ObjectPaging; + +/** + * @author Pavol + */ // Temporary hack. Represents special paging object that means +// "give me objects with OID greater than specified one, sorted by OID ascending". +// +// TODO: replace by using cookie that is part of the standard ObjectPaging +// (but think out all consequences, e.g. conflicts with the other use of the cookie) +public class ObjectPagingAfterOid extends ObjectPaging { + private String oidGreaterThan; + + public String getOidGreaterThan() { + return oidGreaterThan; + } + + public void setOidGreaterThan(String oidGreaterThan) { + this.oidGreaterThan = oidGreaterThan; + } + + @Override + public String toString() { + return super.toString() + ", after OID: " + oidGreaterThan; + } + + @Override + public ObjectPagingAfterOid clone() { + ObjectPagingAfterOid clone = new ObjectPagingAfterOid(); + copyTo(clone); + return clone; + } + + protected void copyTo(ObjectPagingAfterOid clone) { + super.copyTo(clone); + clone.oidGreaterThan = this.oidGreaterThan; + } +} diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlBaseService.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlBaseService.java index 9326b7f7463..86fbf42aca8 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlBaseService.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlBaseService.java @@ -75,7 +75,7 @@ public SqlPerformanceMonitor getPerformanceMonitor() { return repositoryFactory.getPerformanceMonitor(); } - protected LocalSessionFactoryBean getSessionFactoryBean() { + public LocalSessionFactoryBean getSessionFactoryBean() { return sessionFactoryBean; } @@ -202,7 +202,7 @@ private boolean isExceptionRelatedToSerialization(Exception ex) { || sqlException.getErrorCode() == 3960; // Snapshot isolation transaction aborted due to update conflict. } - protected SQLException findSqlException(Throwable ex) { + public SQLException findSqlException(Throwable ex) { while (ex != null) { if (ex instanceof SQLException) { return (SQLException) ex; @@ -222,11 +222,11 @@ private boolean exceptionContainsText(Throwable ex, String text) { return false; } - protected Session beginTransaction() { + public Session beginTransaction() { return beginTransaction(false); } - protected Session beginTransaction(boolean readOnly) { + public Session beginTransaction(boolean readOnly) { Session session = getSessionFactory().openSession(); session.beginTransaction(); @@ -256,16 +256,16 @@ public void execute(Connection connection) throws SQLException { return session; } - protected void rollbackTransaction(Session session) { + public void rollbackTransaction(Session session) { rollbackTransaction(session, null, null, false); } - protected void rollbackTransaction(Session session, Exception ex, OperationResult result, boolean fatal) { + public void rollbackTransaction(Session session, Exception ex, OperationResult result, boolean fatal) { String message = ex != null ? ex.getMessage() : "null"; rollbackTransaction(session, ex, message, result, fatal); } - protected void rollbackTransaction(Session session, Exception ex, String message, OperationResult result, + public void rollbackTransaction(Session session, Exception ex, String message, OperationResult result, boolean fatal) { if (StringUtils.isEmpty(message) && ex != null) { message = ex.getMessage(); @@ -283,7 +283,7 @@ protected void rollbackTransaction(Session session, Exception ex, String message session.getTransaction().rollback(); } - protected void cleanupSessionAndResult(Session session, OperationResult result) { + public void cleanupSessionAndResult(Session session, OperationResult result) { if (session != null && session.isOpen()) { session.close(); } @@ -293,7 +293,7 @@ protected void cleanupSessionAndResult(Session session, OperationResult result) } } - protected void handleGeneralException(Exception ex, Session session, OperationResult result) { + public void handleGeneralException(Exception ex, Session session, OperationResult result) { if (ex instanceof RuntimeException) { handleGeneralRuntimeException((RuntimeException) ex, session, result); } else { @@ -301,7 +301,7 @@ protected void handleGeneralException(Exception ex, Session session, OperationRe } } - protected void handleGeneralRuntimeException(RuntimeException ex, Session session, OperationResult result) { + public void handleGeneralRuntimeException(RuntimeException ex, Session session, OperationResult result) { LOGGER.debug("General runtime exception occurred.", ex); if (isExceptionRelatedToSerialization(ex)) { @@ -319,7 +319,7 @@ protected void handleGeneralRuntimeException(RuntimeException ex, Session sessio } } - protected void handleGeneralCheckedException(Exception ex, Session session, OperationResult result) { + public void handleGeneralCheckedException(Exception ex, Session session, OperationResult result) { LOGGER.error("General checked exception occurred.", ex); boolean fatal = !isExceptionRelatedToSerialization(ex); diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlRepositoryConfiguration.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlRepositoryConfiguration.java index fc195a6a195..58e6baf566e 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlRepositoryConfiguration.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlRepositoryConfiguration.java @@ -17,6 +17,7 @@ package com.evolveum.midpoint.repo.sql; import com.evolveum.midpoint.repo.api.RepositoryServiceFactoryException; +import com.evolveum.midpoint.repo.sql.helpers.OrgClosureManager; import com.evolveum.midpoint.repo.sql.util.MidPointConnectionCustomizer; import com.evolveum.midpoint.repo.sql.util.MidPointMySQLDialect; import com.evolveum.midpoint.repo.sql.util.MidPointPostgreSQLDialect; diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlRepositoryServiceImpl.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlRepositoryServiceImpl.java index 0f4c2203502..f9fb1d8da62 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlRepositoryServiceImpl.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlRepositoryServiceImpl.java @@ -20,21 +20,11 @@ import com.evolveum.midpoint.common.crypto.CryptoUtil; import com.evolveum.midpoint.prism.ConsistencyCheckScope; import com.evolveum.midpoint.prism.Containerable; -import com.evolveum.midpoint.prism.Item; -import com.evolveum.midpoint.prism.PrismContainer; -import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.PrismProperty; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.PrismReference; -import com.evolveum.midpoint.prism.PrismReferenceDefinition; import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; -import com.evolveum.midpoint.prism.delta.ReferenceDelta; -import com.evolveum.midpoint.prism.parser.XNodeProcessorEvaluationMode; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.query.AllFilter; @@ -44,83 +34,46 @@ import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.repo.api.RepoAddOptions; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.sql.data.common.RObject; -import com.evolveum.midpoint.repo.sql.data.common.any.RAnyValue; -import com.evolveum.midpoint.repo.sql.data.common.any.RValueType; -import com.evolveum.midpoint.repo.sql.data.common.type.RObjectExtensionType; -import com.evolveum.midpoint.repo.sql.helpers.CertificationCaseHelper; -import com.evolveum.midpoint.repo.sql.helpers.LookupTableHelper; -import com.evolveum.midpoint.repo.sql.helpers.GeneralHelper; -import com.evolveum.midpoint.repo.sql.helpers.NameResolutionHelper; -import com.evolveum.midpoint.repo.sql.query.QueryEngine; -import com.evolveum.midpoint.repo.sql.query.QueryException; -import com.evolveum.midpoint.repo.sql.query.RQuery; -import com.evolveum.midpoint.repo.sql.query2.QueryEngine2; -import com.evolveum.midpoint.repo.sql.util.ClassMapper; -import com.evolveum.midpoint.repo.sql.util.DtoTranslationException; -import com.evolveum.midpoint.repo.sql.util.GetObjectResult; -import com.evolveum.midpoint.repo.sql.util.IdGeneratorResult; -import com.evolveum.midpoint.repo.sql.util.PrismIdentifierGenerator; -import com.evolveum.midpoint.repo.sql.util.RUtil; -import com.evolveum.midpoint.repo.sql.util.ScrollableResultsIterator; +import com.evolveum.midpoint.repo.sql.helpers.ObjectRetriever; +import com.evolveum.midpoint.repo.sql.helpers.ObjectUpdater; +import com.evolveum.midpoint.repo.sql.helpers.OrgClosureManager; +import com.evolveum.midpoint.repo.sql.helpers.SequenceHelper; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.LabeledString; import com.evolveum.midpoint.schema.RepositoryDiag; import com.evolveum.midpoint.schema.ResultHandler; -import com.evolveum.midpoint.schema.RetrieveOption; import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.schema.SearchResultMetadata; import com.evolveum.midpoint.schema.SelectorOptions; 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.util.DebugUtil; 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.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCaseType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SequenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; -import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; -import org.hibernate.Criteria; -import org.hibernate.LockMode; -import org.hibernate.LockOptions; -import org.hibernate.Query; -import org.hibernate.SQLQuery; -import org.hibernate.ScrollMode; -import org.hibernate.ScrollableResults; import org.hibernate.Session; -import org.hibernate.criterion.Restrictions; -import org.hibernate.exception.ConstraintViolationException; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.jdbc.Work; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; -import javax.annotation.PostConstruct; -import javax.xml.namespace.QName; -import java.lang.reflect.Method; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; -import java.util.Iterator; import java.util.List; import java.util.Properties; @@ -136,7 +89,7 @@ public class SqlRepositoryServiceImpl extends SqlBaseService implements Reposito private static final Trace LOGGER = TraceManager.getTrace(SqlRepositoryServiceImpl.class); private static final Trace LOGGER_PERFORMANCE = TraceManager.getTrace(PERFORMANCE_LOG_NAME); - private static final int MAX_CONSTRAINT_NAME_LENGTH = 40; + 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."; @@ -147,17 +100,15 @@ public class SqlRepositoryServiceImpl extends SqlBaseService implements Reposito private static final String DETAILS_HIBERNATE_HBM_2_DDL = "hibernateHbm2ddl"; @Autowired - private GeneralHelper generalHelper; + private SequenceHelper sequenceHelper; @Autowired - private LookupTableHelper lookupTableHelper; + private ObjectRetriever objectRetriever; @Autowired - private CertificationCaseHelper caseHelper; + private ObjectUpdater objectUpdater; @Autowired - private NameResolutionHelper nameResolutionHelper; - private OrgClosureManager closureManager; public SqlRepositoryServiceImpl(SqlRepositoryFactory repositoryFactory) { @@ -166,99 +117,9 @@ public SqlRepositoryServiceImpl(SqlRepositoryFactory repositoryFactory) { // public because of testing public OrgClosureManager getClosureManager() { - if (closureManager == null) { - closureManager = new OrgClosureManager(getConfiguration()); - } return closureManager; } - private PrismObject getObject(Session session, Class type, String oid, - Collection> options, - boolean lockForUpdate) - throws ObjectNotFoundException, SchemaException, DtoTranslationException, QueryException { - - boolean lockedForUpdateViaHibernate = false; - boolean lockedForUpdateViaSql = false; - - LockOptions lockOptions = new LockOptions(); - //todo fix lock for update!!!!! - if (lockForUpdate) { - if (getConfiguration().isLockForUpdateViaHibernate()) { - lockOptions.setLockMode(LockMode.PESSIMISTIC_WRITE); - lockedForUpdateViaHibernate = true; - } else if (getConfiguration().isLockForUpdateViaSql()) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Trying to lock object " + oid + " for update (via SQL)"); - } - long time = System.currentTimeMillis(); - SQLQuery q = session.createSQLQuery("select oid from m_object where oid = ? for update"); - q.setString(0, oid); - Object result = q.uniqueResult(); - if (result == null) { - return throwObjectNotFoundException(type, oid); - } - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Locked via SQL (in " + (System.currentTimeMillis() - time) + " ms)"); - } - lockedForUpdateViaSql = true; - } - } - - if (LOGGER.isTraceEnabled()) { - if (lockedForUpdateViaHibernate) { - LOGGER.trace("Getting object " + oid + " with locking for update (via hibernate)"); - } else if (lockedForUpdateViaSql) { - LOGGER.trace("Getting object " + oid + ", already locked for update (via SQL)"); - } else { - LOGGER.trace("Getting object " + oid + " without locking for update"); - } - } - - GetObjectResult fullObject = null; - if (!lockForUpdate) { - Query query = session.getNamedQuery("get.object"); - query.setString("oid", oid); - query.setResultTransformer(GetObjectResult.RESULT_TRANSFORMER); - query.setLockOptions(lockOptions); - - fullObject = (GetObjectResult) query.uniqueResult(); - } else { - // we're doing update after this get, therefore we load full object right now - // (it would be loaded during merge anyway) - // this just loads object to hibernate session, probably will be removed later. Merge after this get - // will be faster. Read and use object only from fullObject column. - // todo remove this later [lazyman] - Criteria criteria = session.createCriteria(ClassMapper.getHQLTypeClass(type)); - criteria.add(Restrictions.eq("oid", oid)); - - criteria.setLockMode(lockOptions.getLockMode()); - RObject obj = (RObject) criteria.uniqueResult(); - - if (obj != null) { - obj.toJAXB(getPrismContext(), null).asPrismObject(); - fullObject = new GetObjectResult(obj.getFullObject(), obj.getStringsCount(), obj.getLongsCount(), - obj.getDatesCount(), obj.getReferencesCount(), obj.getPolysCount(), obj.getBooleansCount()); - } - } - - LOGGER.trace("Got it."); - if (fullObject == null) { - throwObjectNotFoundException(type, oid); - } - - - LOGGER.trace("Transforming data to JAXB type."); - PrismObject prismObject = updateLoadedObject(fullObject, type, options, session); - validateObjectType(prismObject, type); - - return prismObject; - } - - private PrismObject throwObjectNotFoundException(Class type, String oid) - throws ObjectNotFoundException { - throw new ObjectNotFoundException("Object of type '" + type.getSimpleName() + "' with oid '" + oid - + "' was not found.", null, oid); - } @Override public PrismObject getObject(Class type, String oid, @@ -284,7 +145,7 @@ public PrismObject getObject(Class type, String oid try { while (true) { try { - return getObjectAttempt(type, oid, options, subResult); + return objectRetriever.getObjectAttempt(type, oid, options, subResult); } catch (RuntimeException ex) { attempt = logOperationAttempt(oid, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); @@ -295,45 +156,6 @@ public PrismObject getObject(Class type, String oid } } - private PrismObject getObjectAttempt(Class type, String oid, - Collection> options, - OperationResult result) - throws ObjectNotFoundException, SchemaException { - LOGGER_PERFORMANCE.debug("> get object {}, oid={}", type.getSimpleName(), oid); - PrismObject objectType = null; - - Session session = null; - try { - session = beginReadOnlyTransaction(); - - objectType = getObject(session, type, oid, options, false); - - session.getTransaction().commit(); - } catch (ObjectNotFoundException ex) { - GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); - rollbackTransaction(session, ex, result, !GetOperationOptions.isAllowNotFound(rootOptions)); - throw ex; - } catch (SchemaException ex) { - rollbackTransaction(session, ex, "Schema error while getting object with oid: " - + oid + ". Reason: " + ex.getMessage(), result, true); - throw ex; - } catch (QueryException | DtoTranslationException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Get object:\n{}", new Object[]{(objectType != null ? objectType.debugDump(3) : null)}); - } - - return objectType; - } - - private Session beginReadOnlyTransaction() { - return beginTransaction(getConfiguration().isUseReadOnlyTransactions()); - } - @Override public PrismObject searchShadowOwner(String shadowOid, Collection> options, OperationResult result) { Validate.notEmpty(shadowOid, "Oid must not be null or empty."); @@ -349,52 +171,13 @@ public PrismObject searchShadowOwner(String shadowOid, while (true) { try { - return searchShadowOwnerAttempt(shadowOid, options, subResult); + return objectRetriever.searchShadowOwnerAttempt(shadowOid, options, subResult); } catch (RuntimeException ex) { attempt = logOperationAttempt(shadowOid, operation, attempt, ex, subResult); } } } - private PrismObject searchShadowOwnerAttempt(String shadowOid, Collection> options, OperationResult result) { - LOGGER_PERFORMANCE.debug("> search shadow owner for oid={}", shadowOid); - PrismObject owner = null; - Session session = null; - try { - session = beginReadOnlyTransaction(); - LOGGER.trace("Selecting account shadow owner for account {}.", new Object[]{shadowOid}); - Query query = session.getNamedQuery("searchShadowOwner.getOwner"); - query.setString("oid", shadowOid); - query.setResultTransformer(GetObjectResult.RESULT_TRANSFORMER); - - List focuses = query.list(); - LOGGER.trace("Found {} focuses, transforming data to JAXB types.", - new Object[]{(focuses != null ? focuses.size() : 0)}); - - if (focuses == null || focuses.isEmpty()) { - // account shadow owner was not found - return null; - } - - if (focuses.size() > 1) { - LOGGER.warn("Found {} owners for shadow oid {}, returning first owner.", - new Object[]{focuses.size(), shadowOid}); - } - - GetObjectResult focus = focuses.get(0); - owner = updateLoadedObject(focus, (Class) FocusType.class, options, session); - - session.getTransaction().commit(); - - } catch (SchemaException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); - } - - return owner; - } - @Override @Deprecated public PrismObject listAccountShadowOwner(String accountOid, OperationResult result) @@ -412,444 +195,13 @@ public PrismObject listAccountShadowOwner(String accountOid, Operation while (true) { try { - return listAccountShadowOwnerAttempt(accountOid, subResult); + return objectRetriever.listAccountShadowOwnerAttempt(accountOid, subResult); } catch (RuntimeException ex) { attempt = logOperationAttempt(accountOid, operation, attempt, ex, subResult); } } } - private PrismObject listAccountShadowOwnerAttempt(String accountOid, OperationResult result) - throws ObjectNotFoundException { - LOGGER_PERFORMANCE.debug("> list account shadow owner oid={}", accountOid); - PrismObject userType = null; - Session session = null; - try { - session = beginReadOnlyTransaction(); - Query query = session.getNamedQuery("listAccountShadowOwner.getUser"); - query.setString("oid", accountOid); - query.setResultTransformer(GetObjectResult.RESULT_TRANSFORMER); - - List users = query.list(); - LOGGER.trace("Found {} users, transforming data to JAXB types.", - new Object[]{(users != null ? users.size() : 0)}); - - if (users == null || users.isEmpty()) { - // account shadow owner was not found - return null; - } - - if (users.size() > 1) { - LOGGER.warn("Found {} users for account oid {}, returning first user. [interface change needed]", - new Object[]{users.size(), accountOid}); - } - - GetObjectResult user = users.get(0); - userType = updateLoadedObject(user, UserType.class, null, session); - - session.getTransaction().commit(); - } catch (SchemaException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); - } - - return userType; - } - - 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 String addObject(PrismObject 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={}", - new Object[]{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 addObjectAttempt(object, options, subResult); - } catch (RuntimeException ex) { - attempt = logOperationAttempt(oid, operation, attempt, ex, subResult); - } - } - } - - private String addObjectAttempt(PrismObject object, RepoAddOptions options, - OperationResult result) - throws ObjectAlreadyExistsException, SchemaException { - LOGGER_PERFORMANCE.debug("> add object {}, oid={}, overwrite={}", - new Object[]{object.getCompileTimeClass().getSimpleName(), object.getOid(), options.isOverwrite()}); - String oid = null; - Session session = null; - OrgClosureManager.Context closureContext = null; - // it is needed to keep the original oid for example for import options. if we do not keep it - // and it was null it can bring some error because the oid is set when the object contains orgRef - // or it is org. and by the import we do not know it so it will be trying to delete non-existing object - String originalOid = object.getOid(); - try { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Object\n{}", new Object[]{object.debugDump()}); - } - - LOGGER.trace("Translating JAXB to data type."); - PrismIdentifierGenerator.Operation operation = options.isOverwrite() ? - PrismIdentifierGenerator.Operation.ADD_WITH_OVERWRITE : - PrismIdentifierGenerator.Operation.ADD; - - RObject rObject = createDataObjectFromJAXB(object, operation); - - session = beginTransaction(); - - closureContext = getClosureManager().onBeginTransactionAdd(session, object, options.isOverwrite()); - - if (options.isOverwrite()) { - oid = overwriteAddObjectAttempt(object, rObject, originalOid, session, closureContext); - } else { - oid = nonOverwriteAddObjectAttempt(object, rObject, originalOid, session, closureContext); - } - session.getTransaction().commit(); - - LOGGER.trace("Saved object '{}' with oid '{}'", new Object[]{ - object.getCompileTimeClass().getSimpleName(), oid}); - - object.setOid(oid); - } catch (ConstraintViolationException ex) { - handleConstraintViolationException(session, ex, result); - rollbackTransaction(session, ex, result, true); - - LOGGER.debug("Constraint violation occurred (will be rethrown as ObjectAlreadyExistsException).", ex); - // we don't know if it's only name uniqueness violation, or something else, - // therefore we're throwing it always as ObjectAlreadyExistsException revert - // to the original oid and prevent of unexpected behaviour (e.g. by import with overwrite option) - if (StringUtils.isEmpty(originalOid)) { - object.setOid(null); - } - String constraintName = ex.getConstraintName(); - // Breaker to avoid long unreadable messages - if (constraintName != null && constraintName.length() > MAX_CONSTRAINT_NAME_LENGTH) { - constraintName = null; - } - throw new ObjectAlreadyExistsException("Conflicting object already exists" - + (constraintName == null ? "" : " (violated constraint '" + constraintName + "')"), ex); - } catch (ObjectAlreadyExistsException | SchemaException ex) { - rollbackTransaction(session, ex, result, true); - throw ex; - } catch (DtoTranslationException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupClosureAndSessionAndResult(closureContext, session, result); - } - - return oid; - } - - private String overwriteAddObjectAttempt(PrismObject object, RObject rObject, - String originalOid, Session session, OrgClosureManager.Context closureContext) - throws ObjectAlreadyExistsException, SchemaException, DtoTranslationException { - - PrismObject oldObject = null; - - //check if object already exists, find differences and increment version if necessary - Collection modifications = null; - if (originalOid != null) { - try { - oldObject = getObject(session, object.getCompileTimeClass(), originalOid, null, true); - ObjectDelta delta = object.diff(oldObject); - modifications = delta.getModifications(); - - //we found existing object which will be overwritten, therefore we increment version - Integer version = RUtil.getIntegerFromString(oldObject.getVersion()); - version = (version == null) ? 0 : ++version; - - rObject.setVersion(version); - } catch (QueryException ex) { - handleGeneralCheckedException(ex, session, null); - } catch (ObjectNotFoundException ex) { - //it's ok that object was not found, therefore we won't be overwriting it - } - } - - updateFullObject(rObject, object); - RObject merged = (RObject) session.merge(rObject); - lookupTableHelper.addLookupTableRows(session, rObject, modifications != null); - caseHelper.addCertificationCampaignCases(session, rObject, modifications != null); - - if (getClosureManager().isEnabled()) { - OrgClosureManager.Operation operation; - if (modifications == null) { - operation = OrgClosureManager.Operation.ADD; - modifications = createAddParentRefDelta(object); - } else { - operation = OrgClosureManager.Operation.MODIFY; - } - getClosureManager().updateOrgClosure(oldObject, modifications, session, merged.getOid(), object.getCompileTimeClass(), - operation, closureContext); - } - return merged.getOid(); - } - - private void updateFullObject(RObject object, PrismObject savedObject) - throws DtoTranslationException, SchemaException { - LOGGER.debug("Updating full object xml column start."); - savedObject.setVersion(Integer.toString(object.getVersion())); - - if (FocusType.class.isAssignableFrom(savedObject.getCompileTimeClass())) { - savedObject.removeProperty(FocusType.F_JPEG_PHOTO); - } else if (LookupTableType.class.equals(savedObject.getCompileTimeClass())) { - PrismContainer table = savedObject.findContainer(LookupTableType.F_ROW); - savedObject.remove(table); - } else if (AccessCertificationCampaignType.class.equals(savedObject.getCompileTimeClass())) { - PrismContainer caseContainer = savedObject.findContainer(AccessCertificationCampaignType.F_CASE); - savedObject.remove(caseContainer); - } - - String xml = getPrismContext().serializeObjectToString(savedObject, PrismContext.LANG_XML); - byte[] fullObject = RUtil.getByteArrayFromXml(xml, getConfiguration().isUseZip()); - - if (LOGGER.isTraceEnabled()) LOGGER.trace("Storing full object\n{}", xml); - - object.setFullObject(fullObject); - - LOGGER.debug("Updating full object xml column finish."); - } - - private String nonOverwriteAddObjectAttempt(PrismObject object, RObject rObject, - String originalOid, Session session, OrgClosureManager.Context closureContext) - throws ObjectAlreadyExistsException, SchemaException, DtoTranslationException { - - // check name uniqueness (by type) - if (StringUtils.isNotEmpty(originalOid)) { - LOGGER.trace("Checking oid uniqueness."); - //todo improve this table name bullshit - Class hqlType = ClassMapper.getHQLTypeClass(object.getCompileTimeClass()); - SQLQuery query = session.createSQLQuery("select count(*) from " + RUtil.getTableName(hqlType) - + " where oid=:oid"); - query.setString("oid", object.getOid()); - - Number count = (Number) query.uniqueResult(); - if (count != null && count.longValue() > 0) { - throw new ObjectAlreadyExistsException("Object '" + object.getCompileTimeClass().getSimpleName() - + "' with oid '" + object.getOid() + "' already exists."); - } - } - - updateFullObject(rObject, object); - - LOGGER.trace("Saving object (non overwrite)."); - String oid = (String) session.save(rObject); - lookupTableHelper.addLookupTableRows(session, rObject, false); - caseHelper.addCertificationCampaignCases(session, rObject, false); - - if (getClosureManager().isEnabled()) { - Collection modifications = createAddParentRefDelta(object); - getClosureManager().updateOrgClosure(null, modifications, session, oid, object.getCompileTimeClass(), - OrgClosureManager.Operation.ADD, closureContext); - } - - return oid; - } - - @Override - public void deleteObject(Class 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 { - deleteObjectAttempt(type, oid, subResult); - return; - } catch (RuntimeException ex) { - attempt = logOperationAttempt(oid, operation, attempt, ex, subResult); - pm.registerOperationNewTrial(opHandle, attempt); - } - } - } finally { - pm.registerOperationFinish(opHandle, attempt); - } - } - - private List createAddParentRefDelta(PrismObject object) { - PrismReference parentOrgRef = object.findReference(ObjectType.F_PARENT_ORG_REF); - if (parentOrgRef == null || parentOrgRef.isEmpty()) { - return new ArrayList<>(); - } - - PrismObjectDefinition def = object.getDefinition(); - ReferenceDelta delta = ReferenceDelta.createModificationAdd(new ItemPath(ObjectType.F_PARENT_ORG_REF), - def, parentOrgRef.getClonedValues()); - - return Arrays.asList(delta); - } - - private void deleteObjectAttempt(Class type, String oid, OperationResult result) - throws ObjectNotFoundException { - LOGGER_PERFORMANCE.debug("> delete object {}, oid={}", new Object[]{type.getSimpleName(), oid}); - Session session = null; - OrgClosureManager.Context closureContext = null; - try { - session = beginTransaction(); - - closureContext = getClosureManager().onBeginTransactionDelete(session, type, oid); - - Criteria query = session.createCriteria(ClassMapper.getHQLTypeClass(type)); - query.add(Restrictions.eq("oid", oid)); - RObject object = (RObject) query.uniqueResult(); - if (object == null) { - throw new ObjectNotFoundException("Object of type '" + type.getSimpleName() + "' with oid '" + oid - + "' was not found.", null, oid); - } - - getClosureManager().updateOrgClosure(null, null, session, oid, type, OrgClosureManager.Operation.DELETE, closureContext); - - session.delete(object); - if (LookupTableType.class.equals(type)) { - lookupTableHelper.deleteLookupTableRows(session, oid); - } - if (AccessCertificationCampaignType.class.equals(type)) { - caseHelper.deleteCertificationCampaignCases(session, oid); - } - - session.getTransaction().commit(); - } catch (ObjectNotFoundException ex) { - rollbackTransaction(session, ex, result, true); - throw ex; - } catch (RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupClosureAndSessionAndResult(closureContext, session, result); - } - } - - @Override - public int countObjects(Class type, ObjectQuery query, 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 countObjectsAttempt(type, query, subResult); - } catch (RuntimeException ex) { - attempt = logOperationAttempt(null, operation, attempt, ex, subResult); - } - } - } - - private int countObjectsAttempt(Class type, ObjectQuery query, OperationResult result) { - LOGGER_PERFORMANCE.debug("> count objects {}", new Object[]{type.getSimpleName()}); - - int count = 0; - - Session session = null; - try { - Class hqlType = ClassMapper.getHQLTypeClass(type); - - session = beginReadOnlyTransaction(); - Number longCount; - if (query == null || query.getFilter() == null) { - // this is 5x faster than count with 3 inner joins, it can probably improved also for queries which - // filters uses only properties from concrete entities like RUser, RRole by improving interpreter [lazyman] - SQLQuery sqlQuery = session.createSQLQuery("SELECT COUNT(*) FROM " + RUtil.getTableName(hqlType)); - longCount = (Number) sqlQuery.uniqueResult(); - } else { - RQuery rQuery; - if (isUseNewQueryInterpreter(query)) { - QueryEngine2 engine = new QueryEngine2(getConfiguration(), getPrismContext()); - rQuery = engine.interpret(query, type, null, true, session); - } else { - QueryEngine engine = new QueryEngine(getConfiguration(), getPrismContext()); - rQuery = engine.interpret(query, type, null, true, session); - } - - longCount = (Number) rQuery.uniqueResult(); - } - LOGGER.trace("Found {} objects.", longCount); - count = longCount != null ? longCount.intValue() : 0; - } catch (QueryException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); - } - - return count; - } - @Override public SearchResultList> searchObjects(Class type, ObjectQuery query, Collection> options, @@ -871,13 +223,13 @@ public SearchResultList> searchObjects(Cla subResult.recordSuccess(); return new SearchResultList(new ArrayList>(0)); } else if (filter instanceof AllFilter){ - query = query.cloneEmpty(); - query.setFilter(null); + query = query.cloneEmpty(); + query.setFilter(null); } else { - query = query.cloneEmpty(); - query.setFilter(filter); + query = query.cloneEmpty(); + query.setFilter(filter); } - + } SqlPerformanceMonitor pm = getPerformanceMonitor(); @@ -888,7 +240,7 @@ public SearchResultList> searchObjects(Cla try { while (true) { try { - return searchObjectsAttempt(type, query, options, subResult); + return objectRetriever.searchObjectsAttempt(type, query, options, subResult); } catch (RuntimeException ex) { attempt = logOperationAttempt(null, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); @@ -935,7 +287,7 @@ public SearchResultList searchContainers(Class t try { while (true) { try { - return searchContainersAttempt(type, query, options, result); + return objectRetriever.searchContainersAttempt(type, query, options, result); } catch (RuntimeException ex) { attempt = logOperationAttempt(null, operation, attempt, ex, result); pm.registerOperationNewTrial(opHandle, attempt); @@ -968,171 +320,129 @@ private void logSearchInputParameters(Class type, ObjectQuery query, bool } } - private SearchResultList> searchObjectsAttempt(Class type, ObjectQuery query, - Collection> options, - OperationResult result) throws SchemaException { - LOGGER_PERFORMANCE.debug("> search objects {}", new Object[]{type.getSimpleName()}); - List> list = new ArrayList<>(); - Session session = null; - try { - session = beginReadOnlyTransaction(); - RQuery rQuery; - - if (isUseNewQueryInterpreter(query)) { - QueryEngine2 engine = new QueryEngine2(getConfiguration(), getPrismContext()); - rQuery = engine.interpret(query, type, options, false, session); - } else { - QueryEngine engine = new QueryEngine(getConfiguration(), getPrismContext()); - rQuery = engine.interpret(query, type, options, false, session); - } - - List objects = rQuery.list(); - LOGGER.trace("Found {} objects, translating to JAXB.", new Object[]{(objects != null ? objects.size() : 0)}); - - for (GetObjectResult object : objects) { - PrismObject prismObject = updateLoadedObject(object, type, options, session); - list.add(prismObject); - } + @Override + public String addObject(PrismObject 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."); - session.getTransaction().commit(); - } catch (QueryException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); + if (options == null) { + options = new RepoAddOptions(); } - return new SearchResultList>(list); - } + LOGGER.debug("Adding object type '{}', overwrite={}, allowUnencryptedValues={}", + new Object[]{object.getCompileTimeClass().getSimpleName(), options.isOverwrite(), + options.isAllowUnencryptedValues()} + ); - private SearchResultList searchContainersAttempt(Class type, ObjectQuery query, - Collection> options, - OperationResult result) throws SchemaException { + if (InternalsConfig.encryptionChecks && !RepoAddOptions.isAllowUnencryptedValues(options)) { + CryptoUtil.checkEncrypted(object); + } - if (!(AccessCertificationCaseType.class.equals(type))) { - throw new UnsupportedOperationException("Only AccessCertificationCaseType is supported here now."); + if (InternalsConfig.consistencyChecks) { + object.checkConsistence(ConsistencyCheckScope.THOROUGH); + } else { + object.checkConsistence(ConsistencyCheckScope.MANDATORY_CHECKS_ONLY); } - LOGGER_PERFORMANCE.debug("> search containers {}", new Object[]{type.getSimpleName()}); - List list = new ArrayList<>(); - Session session = null; - try { - session = beginReadOnlyTransaction(); + if (LOGGER.isTraceEnabled()) { + // Explicitly log name + PolyStringType namePolyType = object.asObjectable().getName(); + LOGGER.trace("NAME: {} - {}", namePolyType.getOrig(), namePolyType.getNorm()); + } - QueryEngine2 engine = new QueryEngine2(getConfiguration(), getPrismContext()); - RQuery rQuery = engine.interpret(query, type, options, false, session); + OperationResult subResult = result.createSubresult(ADD_OBJECT); + subResult.addParam("object", object); + subResult.addParam("options", options); - List items = rQuery.list(); - LOGGER.trace("Found {} items, translating to JAXB.", items.size()); + final String operation = "adding"; + int attempt = 1; - for (GetObjectResult item : items) { - C value = (C) caseHelper.updateLoadedCertificationCase(item, options, session); - list.add(value); + String oid = object.getOid(); + while (true) { + try { + return objectUpdater.addObjectAttempt(object, options, subResult); + } catch (RuntimeException ex) { + attempt = logOperationAttempt(oid, operation, attempt, ex, subResult); } - - session.getTransaction().commit(); - } catch (QueryException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); } + } - return new SearchResultList(list); + 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."); + } } - /** - * This method provides object parsing from String and validation. - */ - private PrismObject updateLoadedObject(GetObjectResult result, Class type, - Collection> options, - Session session) throws SchemaException { + @Override + public void deleteObject(Class 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."); - String xml = RUtil.getXmlFromByteArray(result.getFullObject(), getConfiguration().isUseZip()); - PrismObject prismObject; - try { - // "Postel mode": be tolerant what you read. We need this to tolerate (custom) schema changes - prismObject = getPrismContext().parseObject(xml, XNodeProcessorEvaluationMode.COMPAT); - } catch (SchemaException e) { - LOGGER.debug("Couldn't parse object because of schema exception ({}):\nObject: {}", e, xml); - throw e; - } catch (RuntimeException e) { - LOGGER.debug("Couldn't parse object because of unexpected exception ({}):\nObject: {}", e, xml); - throw e; - } + LOGGER.debug("Deleting object type '{}' with oid '{}'", new Object[]{type.getSimpleName(), oid}); - if (FocusType.class.isAssignableFrom(prismObject.getCompileTimeClass())) { - if (SelectorOptions.hasToLoadPath(FocusType.F_JPEG_PHOTO, options)) { - //todo improve, use user.hasPhoto flag and take options into account [lazyman] - //this is called only when options contains INCLUDE user/jpegPhoto - Query query = session.getNamedQuery("get.focusPhoto"); - query.setString("oid", prismObject.getOid()); - byte[] photo = (byte[]) query.uniqueResult(); - if (photo != null) { - PrismProperty property = prismObject.findOrCreateProperty(FocusType.F_JPEG_PHOTO); - property.setRealValue(photo); - } - } - } else if (ShadowType.class.equals(prismObject.getCompileTimeClass())) { - //we store it because provisioning now sends it to repo, but it should be transient - prismObject.removeContainer(ShadowType.F_ASSOCIATION); + final String operation = "deleting"; + int attempt = 1; - LOGGER.debug("Loading definitions for shadow attributes."); + OperationResult subResult = result.createSubresult(DELETE_OBJECT); + subResult.addParam("type", type.getName()); + subResult.addParam("oid", oid); - Short[] counts = result.getCountProjection(); - Class[] classes = GetObjectResult.EXT_COUNT_CLASSES; + SqlPerformanceMonitor pm = getPerformanceMonitor(); + long opHandle = pm.registerOperationStart("deleteObject"); - for (int i = 0; i < classes.length; i++) { - if (counts[i] == null || counts[i] == 0) { - continue; + try { + while (true) { + try { + objectUpdater.deleteObjectAttempt(type, oid, subResult); + return; + } catch (RuntimeException ex) { + attempt = logOperationAttempt(oid, operation, attempt, ex, subResult); + pm.registerOperationNewTrial(opHandle, attempt); } - - applyShadowAttributeDefinitions(classes[i], prismObject, session); } - LOGGER.debug("Definitions for attributes loaded. Counts: {}", Arrays.toString(counts)); - } else if (LookupTableType.class.equals(prismObject.getCompileTimeClass())) { - lookupTableHelper.updateLoadedLookupTable(prismObject, options, session); - } else if (AccessCertificationCampaignType.class.equals(prismObject.getCompileTimeClass())) { - caseHelper.updateLoadedCampaign(prismObject, options, session); + } finally { + pm.registerOperationFinish(opHandle, attempt); } - - nameResolutionHelper.resolveNamesIfRequested(session, prismObject.getValue(), options); - validateObjectType(prismObject, type); - - return prismObject; } + @Override + public int countObjects(Class type, ObjectQuery query, OperationResult result) { + Validate.notNull(type, "Object type must not be null."); + Validate.notNull(result, "Operation result must not be null."); - private void applyShadowAttributeDefinitions(Class anyValueType, - PrismObject object, Session session) throws SchemaException { - - PrismContainer attributes = object.findContainer(ShadowType.F_ATTRIBUTES); + 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())}); + } - Query query = session.getNamedQuery("getDefinition." + anyValueType.getSimpleName()); - query.setParameter("oid", object.getOid()); - query.setParameter("ownerType", RObjectExtensionType.ATTRIBUTES); + OperationResult subResult = result.createMinorSubresult(COUNT_OBJECTS); + subResult.addParam("type", type.getName()); + subResult.addParam("query", query); - List values = query.list(); - if (values == null || values.isEmpty()) { - return; + 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); } - for (Object[] value : values) { - QName name = RUtil.stringToQName((String) value[0]); - QName type = RUtil.stringToQName((String) value[1]); - Item item = attributes.findItem(name); - - // A switch statement used to be here - // but that caused strange trouble with OpenJDK. This if-then-else works. - if (item.getDefinition() == null) { - RValueType rValType = (RValueType) value[2]; - if (rValType == RValueType.PROPERTY) { - PrismPropertyDefinition def = new PrismPropertyDefinition(name, type, object.getPrismContext()); - item.applyDefinition(def, true); - } else if (rValType == RValueType.REFERENCE) { - PrismReferenceDefinition def = new PrismReferenceDefinition(name, type, object.getPrismContext()); - item.applyDefinition(def, true); - } else { - throw new UnsupportedOperationException("Unsupported value type " + rValType); - } + final String operation = "counting"; + int attempt = 1; + + while (true) { + try { + return objectRetriever.countObjectsAttempt(type, query, subResult); + } catch (RuntimeException ex) { + attempt = logOperationAttempt(null, operation, attempt, ex, subResult); } } } @@ -1193,7 +503,7 @@ public void modifyObject(Class type, String oid, try { while (true) { try { - modifyObjectAttempt(type, oid, modifications, subResult); + objectUpdater.modifyObjectAttempt(type, oid, modifications, subResult); return; } catch (RuntimeException ex) { attempt = logOperationAttempt(oid, operation, attempt, ex, subResult); @@ -1206,183 +516,6 @@ public void modifyObject(Class type, String oid, } - private void modifyObjectAttempt(Class type, String oid, - Collection modifications, - OperationResult result) throws ObjectNotFoundException, - SchemaException, ObjectAlreadyExistsException, SerializationRelatedException { - - // shallow clone - because some methods, e.g. filterLookupTableModifications manipulate this collection - modifications = new ArrayList<>(modifications); - - LOGGER.debug("Modifying object '{}' with oid '{}'.", new Object[]{type.getSimpleName(), oid}); - LOGGER_PERFORMANCE.debug("> modify object {}, oid={}, modifications={}", - new Object[]{type.getSimpleName(), oid, modifications}); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Modifications:\n{}", new Object[]{DebugUtil.debugDump(modifications)}); - } - - Session session = null; - OrgClosureManager.Context closureContext = null; - try { - session = beginTransaction(); - - closureContext = getClosureManager().onBeginTransactionModify(session, type, oid, modifications); - - Collection lookupTableModifications = lookupTableHelper.filterLookupTableModifications(type, modifications); - Collection campaignCaseModifications = caseHelper.filterCampaignCaseModifications(type, modifications); - - // JpegPhoto (RFocusPhoto) is a special kind of entity. First of all, it is lazily loaded, because photos are really big. - // Each RFocusPhoto naturally belongs to one RFocus, so it would be appropriate to set orphanRemoval=true for focus-photo - // association. However, this leads to a strange problem when merging in-memory RFocus object with the database state: - // If in-memory RFocus object has no photo associated (because of lazy loading), then the associated RFocusPhoto is deleted. - // - // To prevent this behavior, we've set orphanRemoval to false. Fortunately, the remove operation on RFocus - // seems to be still cascaded to RFocusPhoto. What we have to implement ourselves, however, is removal of RFocusPhoto - // _without_ removing of RFocus. In order to know whether the photo has to be removed, we have to retrieve - // its value, apply the delta (e.g. if the delta is a DELETE VALUE X, we have to know whether X matches current - // value of the photo), and if the resulting value is empty, we have to manually delete the RFocusPhoto instance. - // - // So the first step is to retrieve the current value of photo - we obviously do this only if the modifications - // deal with the jpegPhoto property. - Collection> options; - boolean containsFocusPhotoModification = FocusType.class.isAssignableFrom(type) && containsPhotoModification(modifications); - if (containsFocusPhotoModification) { - options = Arrays.asList(SelectorOptions.create(FocusType.F_JPEG_PHOTO, GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE))); - } else { - options = null; - } - - // TODO skip processing if there are no modifications other than row/case ones - - // get object - PrismObject prismObject = getObject(session, type, oid, options, true); - // apply diff - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("OBJECT before:\n{}", new Object[]{prismObject.debugDump()}); - } - PrismObject originalObject = null; - if (getClosureManager().isEnabled()) { - originalObject = prismObject.clone(); - } - ItemDelta.applyTo(modifications, prismObject); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("OBJECT after:\n{}", prismObject.debugDump()); - } - - // Continuing the photo treatment: should we remove the (now obsolete) focus photo? - // We have to test prismObject at this place, because updateFullObject (below) removes photo property from the prismObject. - boolean shouldPhotoBeRemoved = containsFocusPhotoModification && ((FocusType) prismObject.asObjectable()).getJpegPhoto() == null; - - // merge and update object - LOGGER.trace("Translating JAXB to data type."); - RObject rObject = createDataObjectFromJAXB(prismObject, PrismIdentifierGenerator.Operation.MODIFY); - rObject.setVersion(rObject.getVersion() + 1); - - updateFullObject(rObject, prismObject); - session.merge(rObject); - - lookupTableHelper.updateLookupTableData(session, rObject, lookupTableModifications); - caseHelper.updateCampaignCases(session, rObject, campaignCaseModifications); - - if (getClosureManager().isEnabled()) { - getClosureManager().updateOrgClosure(originalObject, modifications, session, oid, type, OrgClosureManager.Operation.MODIFY, closureContext); - } - - // JpegPhoto cleanup: As said before, if a focus has to have no photo (after modifications are applied), - // we have to remove the photo manually. - if (shouldPhotoBeRemoved) { - Query query = session.createQuery("delete RFocusPhoto where ownerOid = :oid"); - query.setParameter("oid", prismObject.getOid()); - query.executeUpdate(); - LOGGER.trace("Focus photo for {} was deleted", prismObject.getOid()); - } - - LOGGER.trace("Before commit..."); - session.getTransaction().commit(); - LOGGER.trace("Committed!"); - } catch (ObjectNotFoundException ex) { - rollbackTransaction(session, ex, result, true); - throw ex; - } catch (ConstraintViolationException ex) { - handleConstraintViolationException(session, ex, result); - - rollbackTransaction(session, ex, result, true); - - LOGGER.debug("Constraint violation occurred (will be rethrown as ObjectAlreadyExistsException).", ex); - // we don't know if it's only name uniqueness violation, or something else, - // therefore we're throwing it always as ObjectAlreadyExistsException - - //todo improve (we support only 5 DB, so we should probably do some hacking in here) - throw new ObjectAlreadyExistsException(ex); - } catch (SchemaException ex) { - rollbackTransaction(session, ex, result, true); - throw ex; - } catch (QueryException | DtoTranslationException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupClosureAndSessionAndResult(closureContext, session, result); - LOGGER.trace("Session cleaned up."); - } - } - - private boolean containsPhotoModification(Collection modifications) { - ItemPath photoPath = new ItemPath(FocusType.F_JPEG_PHOTO); - for (ItemDelta delta : modifications) { - ItemPath path = delta.getPath(); - if (path.isEmpty()) { - throw new UnsupportedOperationException("Focus cannot be modified via empty-path modification"); - } else if (photoPath.isSubPathOrEquivalent(path)) { // actually, "subpath" variant should not occur - return true; - } - } - - return false; - } - - private void cleanupClosureAndSessionAndResult(final OrgClosureManager.Context closureContext, final Session session, final OperationResult result) { - if (closureContext != null) { - getClosureManager().cleanUpAfterOperation(closureContext, session); - } - cleanupSessionAndResult(session, result); - } - - private void handleConstraintViolationException(Session session, ConstraintViolationException ex, OperationResult result) { - - // BRUTAL HACK - in PostgreSQL, concurrent changes in parentRefOrg sometimes cause the following exception - // "duplicate key value violates unique constraint "XXXX". This is *not* an ObjectAlreadyExistsException, - // more likely it is a serialization-related one. - // - // TODO: somewhat generalize this approach - perhaps by retrying all operations not dealing with OID/name uniqueness - - SQLException sqlException = findSqlException(ex); - if (sqlException != null) { - SQLException nextException = sqlException.getNextException(); - LOGGER.debug("ConstraintViolationException = {}; SQL exception = {}; embedded SQL exception = {}", new Object[]{ex, sqlException, nextException}); - String[] ok = new String[]{ - "duplicate key value violates unique constraint \"m_org_closure_pkey\"", - "duplicate key value violates unique constraint \"m_reference_pkey\"" - }; - String msg1; - if (sqlException.getMessage() != null) { - msg1 = sqlException.getMessage(); - } else { - msg1 = ""; - } - String msg2; - if (nextException != null && nextException.getMessage() != null) { - msg2 = nextException.getMessage(); - } else { - msg2 = ""; - } - for (int i = 0; i < ok.length; i++) { - if (msg1.contains(ok[i]) || msg2.contains(ok[i])) { - rollbackTransaction(session, ex, result, false); - throw new SerializationRelatedException(ex); - } - } - } - } - @Override public List> listResourceObjectShadows(String resourceOid, Class resourceObjectShadowType, @@ -1407,7 +540,7 @@ public List> listResourceObjectShadows(Str try { while (true) { try { - return listResourceObjectShadowsAttempt(resourceOid, resourceObjectShadowType, subResult); + return objectRetriever.listResourceObjectShadowsAttempt(resourceOid, resourceObjectShadowType, subResult); } catch (RuntimeException ex) { attempt = logOperationAttempt(resourceOid, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); @@ -1418,85 +551,6 @@ public List> listResourceObjectShadows(Str } } - private List> listResourceObjectShadowsAttempt( - String resourceOid, Class resourceObjectShadowType, OperationResult result) - throws ObjectNotFoundException, SchemaException { - - LOGGER_PERFORMANCE.debug("> list resource object shadows {}, for resource oid={}", - new Object[]{resourceObjectShadowType.getSimpleName(), resourceOid}); - List> list = new ArrayList<>(); - Session session = null; - try { - session = beginReadOnlyTransaction(); - Query query = session.getNamedQuery("listResourceObjectShadows"); - query.setString("oid", resourceOid); - query.setResultTransformer(GetObjectResult.RESULT_TRANSFORMER); - - List shadows = query.list(); - LOGGER.debug("Query returned {} shadows, transforming to JAXB types.", - new Object[]{(shadows != null ? shadows.size() : 0)}); - - if (shadows != null) { - for (GetObjectResult shadow : shadows) { - PrismObject prismObject = updateLoadedObject(shadow, resourceObjectShadowType, null, session); - list.add(prismObject); - } - } - session.getTransaction().commit(); - } catch (SchemaException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); - } - - return list; - } - - private void validateObjectType(PrismObject prismObject, Class type) - throws SchemaException { - if (prismObject == null || !type.isAssignableFrom(prismObject.getCompileTimeClass())) { - throw new SchemaException("Expected to find '" + type.getSimpleName() + "' but found '" - + prismObject.getCompileTimeClass().getSimpleName() + "' (" + prismObject.toDebugName() - + "). Bad OID in a reference?"); - } - if (InternalsConfig.consistencyChecks) { - prismObject.checkConsistence(); - } - if (InternalsConfig.readEncryptionChecks) { - CryptoUtil.checkEncrypted(prismObject); - } - } - - private RObject createDataObjectFromJAXB(PrismObject prismObject, - PrismIdentifierGenerator.Operation operation) - throws SchemaException { - - PrismIdentifierGenerator generator = new PrismIdentifierGenerator(); - IdGeneratorResult generatorResult = generator.generate(prismObject, operation); - - T object = prismObject.asObjectable(); - - RObject rObject; - Class clazz = ClassMapper.getHQLTypeClass(object.getClass()); - try { - rObject = clazz.newInstance(); - Method method = clazz.getMethod("copyFromJAXB", object.getClass(), clazz, - PrismContext.class, IdGeneratorResult.class); - method.invoke(clazz, object, rObject, getPrismContext(), generatorResult); - } catch (Exception ex) { - String message = ex.getMessage(); - if (StringUtils.isEmpty(message) && ex.getCause() != null) { - message = ex.getCause().getMessage(); - } - throw new SchemaException(message, ex); - } - - return rObject; - } - - /* (non-Javadoc) - * @see com.evolveum.midpoint.repo.api.RepositoryService#getRepositoryDiag() - */ @Override public RepositoryDiag getRepositoryDiag() { LOGGER.debug("Getting repository diagnostics."); @@ -1628,15 +682,6 @@ public void testOrgClosureConsistency(boolean repairIfNecessary, OperationResult getClosureManager().checkAndOrRebuild(this, true, repairIfNecessary, false, false, testResult); } - @PostConstruct - public void initialize() { - getClosureManager().initialize(this); - } - - /* (non-Javadoc) - * @see com.evolveum.midpoint.repo.api.RepositoryService#getVersion(java.lang.Class, java.lang.String, - * com.evolveum.midpoint.schema.result.OperationResult) - */ @Override public String getVersion(Class type, String oid, OperationResult parentResult) throws ObjectNotFoundException, SchemaException { @@ -1658,7 +703,7 @@ public String getVersion(Class type, String oid, Opera try { while (true) { try { - return getVersionAttempt(type, oid, subResult); + return objectRetriever.getVersionAttempt(type, oid, subResult); } catch (RuntimeException ex) { attempt = logOperationAttempt(null, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); @@ -1669,37 +714,6 @@ public String getVersion(Class type, String oid, Opera } } - private String getVersionAttempt(Class type, String oid, OperationResult result) - throws ObjectNotFoundException, SchemaException { - LOGGER_PERFORMANCE.debug("> get version {}, oid={}", new Object[]{type.getSimpleName(), oid}); - - String version = null; - Session session = null; - try { - session = beginReadOnlyTransaction(); - Query query = session.getNamedQuery("getVersion"); - query.setString("oid", oid); - - Number versionLong = (Number) query.uniqueResult(); - if (versionLong == null) { - throw new ObjectNotFoundException("Object '" + type.getSimpleName() - + "' with oid '" + oid + "' was not found."); - } - version = versionLong.toString(); - } catch (RuntimeException ex) { - handleGeneralRuntimeException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); - } - - return version; - } - - /* (non-Javadoc) - * @see com.evolveum.midpoint.repo.api.RepositoryService#searchObjectsIterative(java.lang.Class, - * com.evolveum.midpoint.prism.query.ObjectQuery, com.evolveum.midpoint.schema.ResultHandler, - * com.evolveum.midpoint.schema.result.OperationResult) - */ @Override public SearchResultMetadata searchObjectsIterative(Class type, ObjectQuery query, ResultHandler handler, @@ -1734,9 +748,9 @@ public SearchResultMetadata searchObjectsIterative(Class< if (getConfiguration().isIterativeSearchByPaging()) { if (strictlySequential) { - searchObjectsIterativeByPagingStrictlySequential(type, query, handler, options, subResult); + objectRetriever.searchObjectsIterativeByPagingStrictlySequential(type, query, handler, options, subResult); } else { - searchObjectsIterativeByPaging(type, query, handler, options, subResult); + objectRetriever.searchObjectsIterativeByPaging(type, query, handler, options, subResult); } return null; } @@ -1750,7 +764,7 @@ public SearchResultMetadata searchObjectsIterative(Class< try { while (true) { try { - searchObjectsIterativeAttempt(type, query, handler, options, subResult); + objectRetriever.searchObjectsIterativeAttempt(type, query, handler, options, subResult); return null; } catch (RuntimeException ex) { attempt = logOperationAttempt(null, operation, attempt, ex, subResult); @@ -1762,197 +776,6 @@ public SearchResultMetadata searchObjectsIterative(Class< } } - private void searchObjectsIterativeAttempt(Class type, ObjectQuery query, - ResultHandler handler, - Collection> options, - OperationResult result) throws SchemaException { - Session session = null; - try { - session = beginReadOnlyTransaction(); - RQuery rQuery; - if (isUseNewQueryInterpreter(query)) { - QueryEngine engine = new QueryEngine(getConfiguration(), getPrismContext()); - rQuery = engine.interpret(query, type, options, false, session); - } else { - QueryEngine2 engine = new QueryEngine2(getConfiguration(), getPrismContext()); - rQuery = engine.interpret(query, type, options, false, session); - } - - ScrollableResults results = rQuery.scroll(ScrollMode.FORWARD_ONLY); - try { - Iterator iterator = new ScrollableResultsIterator(results); - while (iterator.hasNext()) { - GetObjectResult object = iterator.next(); - - PrismObject prismObject = updateLoadedObject(object, type, options, session); - if (!handler.handle(prismObject, result)) { - break; - } - } - } finally { - if (results != null) { - results.close(); - } - } - - session.getTransaction().commit(); - } catch (SchemaException | QueryException | RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); - } - } - - private boolean isUseNewQueryInterpreter(ObjectQuery query) { - return query == null || query.isUseNewQueryInterpreter(); - } - - private void searchObjectsIterativeByPaging(Class type, ObjectQuery query, - ResultHandler handler, - Collection> options, - OperationResult result) - throws SchemaException { - - try { - ObjectQuery pagedQuery = query != null ? query.clone() : new ObjectQuery(); - - int offset; - int remaining; - final int batchSize = getConfiguration().getIterativeSearchByPagingBatchSize(); - - ObjectPaging paging = pagedQuery.getPaging(); - - if (paging == null) { - paging = ObjectPaging.createPaging(0, 0); // counts will be filled-in later - pagedQuery.setPaging(paging); - offset = 0; - remaining = countObjects(type, query, result); - } else { - offset = paging.getOffset() != null ? paging.getOffset() : 0; - remaining = paging.getMaxSize() != null ? paging.getMaxSize() : countObjects(type, query, result) - offset; - } - -main: while (remaining > 0) { - paging.setOffset(offset); - paging.setMaxSize(remaining < batchSize ? remaining : batchSize); - - List> objects = searchObjects(type, pagedQuery, options, result); - - for (PrismObject object : objects) { - if (!handler.handle(object, result)) { - break main; - } - } - - if (objects.size() == 0) { - break; // should not occur, but let's check for this to avoid endless loops - } - offset += objects.size(); - remaining -= objects.size(); - } - } finally { - if (result != null && result.isUnknown()) { - result.computeStatus(); - } - } - } - - // Temporary hack. Represents special paging object that means - // "give me objects with OID greater than specified one, sorted by OID ascending". - // - // TODO: replace by using cookie that is part of the standard ObjectPaging - // (but think out all consequences, e.g. conflicts with the other use of the cookie) - public static class ObjectPagingAfterOid extends ObjectPaging { - private String oidGreaterThan; - - public String getOidGreaterThan() { - return oidGreaterThan; - } - - public void setOidGreaterThan(String oidGreaterThan) { - this.oidGreaterThan = oidGreaterThan; - } - - @Override - public String toString() { - return super.toString() + ", after OID: " + oidGreaterThan; - } - - @Override - public ObjectPagingAfterOid clone() { - ObjectPagingAfterOid clone = new ObjectPagingAfterOid(); - copyTo(clone); - return clone; - } - - protected void copyTo(ObjectPagingAfterOid clone) { - super.copyTo(clone); - clone.oidGreaterThan = this.oidGreaterThan; - } - } - - /** - * Strictly-sequential version of paged search. - * - * Assumptions: - * - During processing of returned object(s), any objects can be added, deleted or modified. - * - * Guarantees: - * - We return each object that existed in the moment of search start: - * - exactly once if it was not deleted in the meanwhile, - * - at most once otherwise. - * - However, we may or may not return any objects that were added during the processing. - * - * Constraints: - * - There can be no ordering prescribed. We use our own ordering. - * - Moreover, for simplicity we disallow any explicit paging. - * - * Implementation is very simple - we fetch objects ordered by OID, and remember last OID fetched. - * Obviously no object will be present in output more than once. - * Objects that are not deleted will be there exactly once, provided their oid is not changed. - */ - private void searchObjectsIterativeByPagingStrictlySequential( - Class type, ObjectQuery query, ResultHandler handler, - Collection> options, OperationResult result) - throws SchemaException { - - try { - ObjectQuery pagedQuery = query != null ? query.clone() : new ObjectQuery(); - - String lastOid = ""; - final int batchSize = getConfiguration().getIterativeSearchByPagingBatchSize(); - - if (pagedQuery.getPaging() != null) { - throw new IllegalArgumentException("Externally specified paging is not supported on strictly sequential iterative search."); - } - - ObjectPagingAfterOid paging = new ObjectPagingAfterOid(); - pagedQuery.setPaging(paging); - -main: for (;;) { - paging.setOidGreaterThan(lastOid); - paging.setMaxSize(batchSize); - - List> objects = searchObjects(type, pagedQuery, options, result); - - for (PrismObject object : objects) { - lastOid = object.getOid(); - if (!handler.handle(object, result)) { - break main; - } - } - - if (objects.size() == 0) { - break; - } - } - } finally { - if (result != null && result.isUnknown()) { - result.computeStatus(); - } - } - } - @Override public boolean isAnySubordinate(String upperOrgOid, Collection lowerObjectOids) throws SchemaException { Validate.notNull(upperOrgOid, "upperOrgOid must not be null."); @@ -1973,7 +796,7 @@ public boolean isAnySubordinate(String upperOrgOid, Collection lowerObje try { while (true) { try { - return isAnySubordinateAttempt(upperOrgOid, lowerObjectOids); + return objectRetriever.isAnySubordinateAttempt(upperOrgOid, lowerObjectOids); } catch (RuntimeException ex) { attempt = logOperationAttempt(upperOrgOid, "isAnySubordinate", attempt, ex, null); pm.registerOperationNewTrial(opHandle, attempt); @@ -1984,31 +807,6 @@ public boolean isAnySubordinate(String upperOrgOid, Collection lowerObje } } - private boolean isAnySubordinateAttempt(String upperOrgOid, Collection lowerObjectOids) { - Session session = null; - try { - session = beginTransaction(); - - Query query; - if (lowerObjectOids.size() == 1) { - query = session.getNamedQuery("isAnySubordinateAttempt.oneLowerOid"); - query.setString("dOid", lowerObjectOids.iterator().next()); - } else { - query = session.getNamedQuery("isAnySubordinateAttempt.moreLowerOids"); - query.setParameterList("dOids", lowerObjectOids); - } - query.setString("aOid", upperOrgOid); - - Number number = (Number) query.uniqueResult(); - return number != null && number.longValue() != 0L; - } catch (RuntimeException ex) { - handleGeneralException(ex, session, null); - } finally { - cleanupSessionAndResult(session, null); - } - - throw new SystemException("isAnySubordinateAttempt failed somehow, this really should not happen."); - } @Override public long advanceSequence(String oid, OperationResult parentResult) throws ObjectNotFoundException, @@ -2030,7 +828,7 @@ public long advanceSequence(String oid, OperationResult parentResult) throws Obj try { while (true) { try { - return advanceSequenceAttempt(oid, result); + return sequenceHelper.advanceSequenceAttempt(oid, result); } catch (RuntimeException ex) { attempt = logOperationAttempt(oid, "advanceSequence", attempt, ex, null); pm.registerOperationNewTrial(opHandle, attempt); @@ -2041,87 +839,8 @@ public long advanceSequence(String oid, OperationResult parentResult) throws Obj } } - private long advanceSequenceAttempt(String oid, OperationResult result) throws ObjectNotFoundException, - SchemaException, SerializationRelatedException { - - long returnValue; - - LOGGER.debug("Advancing sequence with oid '{}'.", oid); - LOGGER_PERFORMANCE.debug("> advance sequence, oid={}", oid); - - Session session = null; - try { - session = beginTransaction(); - - PrismObject prismObject = getObject(session, SequenceType.class, oid, null, true); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("OBJECT before:\n{}", prismObject.debugDump()); - } - SequenceType sequence = prismObject.asObjectable(); - - if (!sequence.getUnusedValues().isEmpty()) { - returnValue = sequence.getUnusedValues().remove(0); - } else { - long counter = sequence.getCounter() != null ? sequence.getCounter() : 0L; - long maxCounter = sequence.getMaxCounter() != null ? sequence.getMaxCounter() : Long.MAX_VALUE; - boolean allowRewind = Boolean.TRUE.equals(sequence.isAllowRewind()); - - if (counter < maxCounter) { - returnValue = counter; - sequence.setCounter(counter + 1); - } else if (counter == maxCounter) { - returnValue = counter; - if (allowRewind) { - sequence.setCounter(0L); - } else { - sequence.setCounter(counter + 1); // will produce exception during next run - } - } else { // i.e. counter > maxCounter - if (allowRewind) { // shouldn't occur but... - LOGGER.warn("Sequence {} overflown with allowRewind set to true. Rewinding.", oid); - returnValue = 0; - sequence.setCounter(1L); - } else { - // TODO some better exception... - throw new SystemException("No (next) value available from sequence " + oid + ". Current counter = " + sequence.getCounter() + ", max value = " + sequence.getMaxCounter()); - } - } - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Return value = {}, OBJECT after:\n{}", returnValue, prismObject.debugDump()); - } - - // merge and update object - LOGGER.trace("Translating JAXB to data type."); - RObject rObject = createDataObjectFromJAXB(prismObject, PrismIdentifierGenerator.Operation.MODIFY); - rObject.setVersion(rObject.getVersion() + 1); - - updateFullObject(rObject, prismObject); - session.merge(rObject); - - LOGGER.trace("Before commit..."); - session.getTransaction().commit(); - LOGGER.trace("Committed!"); - - return returnValue; - } catch (ObjectNotFoundException ex) { - rollbackTransaction(session, ex, result, true); - throw ex; - } catch (SchemaException ex) { - rollbackTransaction(session, ex, result, true); - throw ex; - } catch (QueryException | DtoTranslationException | RuntimeException ex) { - handleGeneralException(ex, session, result); // should always throw an exception - throw new SystemException("Exception " + ex + " was not handled correctly", ex); // ...so this shouldn't occur at all - } finally { - cleanupSessionAndResult(session, result); - LOGGER.trace("Session cleaned up."); - } - } - - @Override + @Override public void returnUnusedValuesToSequence(String oid, Collection unusedValues, OperationResult parentResult) throws ObjectNotFoundException, SchemaException { Validate.notEmpty(oid, "Oid must not null or empty."); @@ -2145,7 +864,7 @@ public void returnUnusedValuesToSequence(String oid, Collection unusedValu try { while (true) { try { - returnUnusedValuesToSequenceAttempt(oid, unusedValues, result); + sequenceHelper.returnUnusedValuesToSequenceAttempt(oid, unusedValues, result); return; } catch (RuntimeException ex) { attempt = logOperationAttempt(oid, "returnUnusedValuesToSequence", attempt, ex, null); @@ -2157,65 +876,6 @@ public void returnUnusedValuesToSequence(String oid, Collection unusedValu } } - private void returnUnusedValuesToSequenceAttempt(String oid, Collection unusedValues, OperationResult result) throws ObjectNotFoundException, - SchemaException, SerializationRelatedException { - - LOGGER.debug("Returning unused values of {} to a sequence with oid '{}'.", unusedValues, oid); - LOGGER_PERFORMANCE.debug("> return unused values, oid={}, values={}", oid, unusedValues); - - Session session = null; - try { - session = beginTransaction(); - - PrismObject prismObject = getObject(session, SequenceType.class, oid, null, true); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("OBJECT before:\n{}", prismObject.debugDump()); - } - SequenceType sequence = prismObject.asObjectable(); - int maxUnusedValues = sequence.getMaxUnusedValues() != null ? sequence.getMaxUnusedValues() : 0; - Iterator valuesToReturnIterator = unusedValues.iterator(); - while (valuesToReturnIterator.hasNext() && sequence.getUnusedValues().size() < maxUnusedValues) { - Long valueToReturn = valuesToReturnIterator.next(); - if (valueToReturn == null) { // sanity check - continue; - } - if (!sequence.getUnusedValues().contains(valueToReturn)) { - sequence.getUnusedValues().add(valueToReturn); - } else { - LOGGER.warn("UnusedValues in sequence {} already contains value of {} - ignoring the return request", oid, valueToReturn); - } - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("OBJECT after:\n{}", prismObject.debugDump()); - } - - // merge and update object - LOGGER.trace("Translating JAXB to data type."); - RObject rObject = createDataObjectFromJAXB(prismObject, PrismIdentifierGenerator.Operation.MODIFY); - rObject.setVersion(rObject.getVersion() + 1); - - updateFullObject(rObject, prismObject); - session.merge(rObject); - - LOGGER.trace("Before commit..."); - session.getTransaction().commit(); - LOGGER.trace("Committed!"); - } catch (ObjectNotFoundException ex) { - rollbackTransaction(session, ex, result, true); - throw ex; - } catch (SchemaException ex) { - rollbackTransaction(session, ex, result, true); - throw ex; - } catch (QueryException | DtoTranslationException | RuntimeException ex) { - handleGeneralException(ex, session, result); // should always throw an exception - throw new SystemException("Exception " + ex + " was not handled correctly", ex); // ...so this shouldn't occur at all - } finally { - cleanupSessionAndResult(session, result); - LOGGER.trace("Session cleaned up."); - } - } - @Override public String executeArbitraryQuery(String query, OperationResult result) { Validate.notEmpty(query, "Query must not be empty."); @@ -2235,7 +895,7 @@ public String executeArbitraryQuery(String query, OperationResult result) { try { while (true) { try { - return executeArbitraryQueryAttempt(query, subResult); + return objectRetriever.executeArbitraryQueryAttempt(query, subResult); } catch (RuntimeException ex) { attempt = logOperationAttempt(null, operation, attempt, ex, subResult); pm.registerOperationNewTrial(opHandle, attempt); @@ -2246,50 +906,4 @@ public String executeArbitraryQuery(String query, OperationResult result) { } } - private String executeArbitraryQueryAttempt(String queryString, OperationResult result) { - LOGGER_PERFORMANCE.debug("> execute query {}", queryString); - - Session session = null; - StringBuffer answer = new StringBuffer(); - try { - session = beginReadOnlyTransaction(); // beware, not all databases support read-only transactions! - - Query query = session.createQuery(queryString); - List results = query.list(); - if (results != null) { - answer.append("Result: ").append(results.size()).append(" item(s):\n\n"); - for (Object item : results) { - if (item instanceof Object[]) { - boolean first = true; - for (Object item1 : (Object[]) item) { - if (first) { - first = false; - } else { - answer.append(","); - } - answer.append(item1); - } - } else { - answer.append(item); - } - answer.append("\n"); - } - } - session.getTransaction().rollback(); - } catch (RuntimeException ex) { - handleGeneralException(ex, session, result); - } finally { - cleanupSessionAndResult(session, result); - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Executed query:\n{}\nwith result:\n{}", queryString, answer); - } - - return answer.toString(); - } - - - - } diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/data/common/container/RAccessCertificationCase.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/data/common/container/RAccessCertificationCase.java index 36c83ed2f02..67c07403b61 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/data/common/container/RAccessCertificationCase.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/data/common/container/RAccessCertificationCase.java @@ -352,11 +352,11 @@ private static RAccessCertificationCase toRepo(AccessCertificationCaseType case1 } public AccessCertificationCaseType toJAXB(PrismContext prismContext) throws SchemaException { - return createJaxb(fullObject, prismContext); + return createJaxb(fullObject, prismContext, true); } // TODO find appropriate name - public static AccessCertificationCaseType createJaxb(byte[] fullObject, PrismContext prismContext) throws SchemaException { + public static AccessCertificationCaseType createJaxb(byte[] fullObject, PrismContext prismContext, boolean removeCampaignRef) throws SchemaException { String xml = RUtil.getXmlFromByteArray(fullObject, false); PrismContainer caseContainer; try { @@ -369,6 +369,8 @@ public static AccessCertificationCaseType createJaxb(byte[] fullObject, PrismCon LOGGER.debug("Couldn't parse certification case because of unexpected exception ({}):\nData: {}", e, xml); throw e; } - return caseContainer.getValue().asContainerable().clone(); // clone in order to make it parent-less + AccessCertificationCaseType aCase = caseContainer.getValue().asContainerable().clone(); // clone in order to make it parent-less + aCase.asPrismContainerValue().removeReference(AccessCertificationCaseType.F_CAMPAIGN_REF); + return aCase; } } diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/CertificationCaseHelper.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/CertificationCaseHelper.java index 8976b250f21..97d0006648f 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/CertificationCaseHelper.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/CertificationCaseHelper.java @@ -213,7 +213,7 @@ private void updateCasesContent(Session session, String campaignOid, Collection< if (fullObject == null) { throw new ObjectNotFoundException("Couldn't update cert campaign " + campaignOid + " + by delta with path " + deltaPath + " - specified case does not exist"); } - AccessCertificationCaseType aCase = RAccessCertificationCase.createJaxb(fullObject, prismContext); + AccessCertificationCaseType aCase = RAccessCertificationCase.createJaxb(fullObject, prismContext, false); delta.setParentPath(delta.getParentPath().tail(2)); // remove "case[id]" from the delta path delta.applyTo(aCase.asPrismContainerValue()); @@ -254,7 +254,7 @@ public AccessCertificationCaseType updateLoadedCertificationCase(GetObjectResult Collection> options, Session session) throws SchemaException { - AccessCertificationCaseType aCase = RAccessCertificationCase.createJaxb(result.getFullObject(), prismContext); + AccessCertificationCaseType aCase = RAccessCertificationCase.createJaxb(result.getFullObject(), prismContext, false); nameResolutionHelper.resolveNamesIfRequested(session, aCase.asPrismContainerValue(), options); generalHelper.validateContainerable(aCase, AccessCertificationCaseType.class); return aCase; diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/ObjectRetriever.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/ObjectRetriever.java new file mode 100644 index 00000000000..74ae89095fe --- /dev/null +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/ObjectRetriever.java @@ -0,0 +1,828 @@ +/* + * Copyright (c) 2010-2015 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.helpers; + +import com.evolveum.midpoint.common.InternalsConfig; +import com.evolveum.midpoint.common.crypto.CryptoUtil; +import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismReferenceDefinition; +import com.evolveum.midpoint.prism.parser.XNodeProcessorEvaluationMode; +import com.evolveum.midpoint.prism.query.ObjectPaging; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.sql.ObjectPagingAfterOid; +import com.evolveum.midpoint.repo.sql.SqlRepositoryConfiguration; +import com.evolveum.midpoint.repo.sql.SqlRepositoryServiceImpl; +import com.evolveum.midpoint.repo.sql.data.common.RObject; +import com.evolveum.midpoint.repo.sql.data.common.any.RAnyValue; +import com.evolveum.midpoint.repo.sql.data.common.any.RValueType; +import com.evolveum.midpoint.repo.sql.data.common.type.RObjectExtensionType; +import com.evolveum.midpoint.repo.sql.query.QueryEngine; +import com.evolveum.midpoint.repo.sql.query.QueryException; +import com.evolveum.midpoint.repo.sql.query.RQuery; +import com.evolveum.midpoint.repo.sql.query2.QueryEngine2; +import com.evolveum.midpoint.repo.sql.util.ClassMapper; +import com.evolveum.midpoint.repo.sql.util.DtoTranslationException; +import com.evolveum.midpoint.repo.sql.util.GetObjectResult; +import com.evolveum.midpoint.repo.sql.util.RUtil; +import com.evolveum.midpoint.repo.sql.util.ScrollableResultsIterator; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.ResultHandler; +import com.evolveum.midpoint.schema.SearchResultList; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCaseType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import org.hibernate.Criteria; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.Query; +import org.hibernate.SQLQuery; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.criterion.Restrictions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * @author lazyman, mederly + */ +@Component +public class ObjectRetriever { + + private static final Trace LOGGER = TraceManager.getTrace(SqlRepositoryServiceImpl.class); + private static final Trace LOGGER_PERFORMANCE = TraceManager.getTrace(SqlRepositoryServiceImpl.PERFORMANCE_LOG_NAME); + + @Autowired + @Qualifier("repositoryService") + private RepositoryService repositoryService; + + @Autowired + private LookupTableHelper lookupTableHelper; + + @Autowired + private CertificationCaseHelper caseHelper; + + @Autowired + private TransactionHelper transactionHelper; + + @Autowired + private NameResolutionHelper nameResolutionHelper; + + @Autowired + private PrismContext prismContext; + + public PrismObject getObjectAttempt(Class type, String oid, + Collection> options, + OperationResult result) + throws ObjectNotFoundException, SchemaException { + LOGGER_PERFORMANCE.debug("> get object {}, oid={}", type.getSimpleName(), oid); + PrismObject objectType = null; + + Session session = null; + try { + session = transactionHelper.beginReadOnlyTransaction(); + + objectType = getObjectInternal(session, type, oid, options, false); + + session.getTransaction().commit(); + } catch (ObjectNotFoundException ex) { + GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); + transactionHelper.rollbackTransaction(session, ex, result, !GetOperationOptions.isAllowNotFound(rootOptions)); + throw ex; + } catch (SchemaException ex) { + transactionHelper.rollbackTransaction(session, ex, "Schema error while getting object with oid: " + + oid + ". Reason: " + ex.getMessage(), result, true); + throw ex; + } catch (QueryException | DtoTranslationException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Get object:\n{}", new Object[]{(objectType != null ? objectType.debugDump(3) : null)}); + } + + return objectType; + } + + public PrismObject getObjectInternal(Session session, Class type, String oid, + Collection> options, + boolean lockForUpdate) + throws ObjectNotFoundException, SchemaException, DtoTranslationException, QueryException { + + boolean lockedForUpdateViaHibernate = false; + boolean lockedForUpdateViaSql = false; + + LockOptions lockOptions = new LockOptions(); + //todo fix lock for update!!!!! + if (lockForUpdate) { + if (getConfiguration().isLockForUpdateViaHibernate()) { + lockOptions.setLockMode(LockMode.PESSIMISTIC_WRITE); + lockedForUpdateViaHibernate = true; + } else if (getConfiguration().isLockForUpdateViaSql()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Trying to lock object " + oid + " for update (via SQL)"); + } + long time = System.currentTimeMillis(); + SQLQuery q = session.createSQLQuery("select oid from m_object where oid = ? for update"); + q.setString(0, oid); + Object result = q.uniqueResult(); + if (result == null) { + return throwObjectNotFoundException(type, oid); + } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Locked via SQL (in " + (System.currentTimeMillis() - time) + " ms)"); + } + lockedForUpdateViaSql = true; + } + } + + if (LOGGER.isTraceEnabled()) { + if (lockedForUpdateViaHibernate) { + LOGGER.trace("Getting object " + oid + " with locking for update (via hibernate)"); + } else if (lockedForUpdateViaSql) { + LOGGER.trace("Getting object " + oid + ", already locked for update (via SQL)"); + } else { + LOGGER.trace("Getting object " + oid + " without locking for update"); + } + } + + GetObjectResult fullObject = null; + if (!lockForUpdate) { + Query query = session.getNamedQuery("get.object"); + query.setString("oid", oid); + query.setResultTransformer(GetObjectResult.RESULT_TRANSFORMER); + query.setLockOptions(lockOptions); + + fullObject = (GetObjectResult) query.uniqueResult(); + } else { + // we're doing update after this get, therefore we load full object right now + // (it would be loaded during merge anyway) + // this just loads object to hibernate session, probably will be removed later. Merge after this get + // will be faster. Read and use object only from fullObject column. + // todo remove this later [lazyman] + Criteria criteria = session.createCriteria(ClassMapper.getHQLTypeClass(type)); + criteria.add(Restrictions.eq("oid", oid)); + + criteria.setLockMode(lockOptions.getLockMode()); + RObject obj = (RObject) criteria.uniqueResult(); + + if (obj != null) { + obj.toJAXB(prismContext, null).asPrismObject(); + fullObject = new GetObjectResult(obj.getFullObject(), obj.getStringsCount(), obj.getLongsCount(), + obj.getDatesCount(), obj.getReferencesCount(), obj.getPolysCount(), obj.getBooleansCount()); + } + } + + LOGGER.trace("Got it."); + if (fullObject == null) { + throwObjectNotFoundException(type, oid); + } + + + LOGGER.trace("Transforming data to JAXB type."); + PrismObject prismObject = updateLoadedObject(fullObject, type, options, session); + validateObjectType(prismObject, type); + + return prismObject; + } + + protected SqlRepositoryConfiguration getConfiguration() { + return ((SqlRepositoryServiceImpl) repositoryService).getConfiguration(); + } + + private PrismObject throwObjectNotFoundException(Class type, String oid) + throws ObjectNotFoundException { + throw new ObjectNotFoundException("Object of type '" + type.getSimpleName() + "' with oid '" + oid + + "' was not found.", null, oid); + } + + public PrismObject searchShadowOwnerAttempt(String shadowOid, Collection> options, OperationResult result) { + LOGGER_PERFORMANCE.debug("> search shadow owner for oid={}", shadowOid); + PrismObject owner = null; + Session session = null; + try { + session = transactionHelper.beginReadOnlyTransaction(); + LOGGER.trace("Selecting account shadow owner for account {}.", new Object[]{shadowOid}); + Query query = session.getNamedQuery("searchShadowOwner.getOwner"); + query.setString("oid", shadowOid); + query.setResultTransformer(GetObjectResult.RESULT_TRANSFORMER); + + List focuses = query.list(); + LOGGER.trace("Found {} focuses, transforming data to JAXB types.", + new Object[]{(focuses != null ? focuses.size() : 0)}); + + if (focuses == null || focuses.isEmpty()) { + // account shadow owner was not found + return null; + } + + if (focuses.size() > 1) { + LOGGER.warn("Found {} owners for shadow oid {}, returning first owner.", + new Object[]{focuses.size(), shadowOid}); + } + + GetObjectResult focus = focuses.get(0); + owner = updateLoadedObject(focus, (Class) FocusType.class, options, session); + + session.getTransaction().commit(); + + } catch (SchemaException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + + return owner; + } + + public PrismObject listAccountShadowOwnerAttempt(String accountOid, OperationResult result) + throws ObjectNotFoundException { + LOGGER_PERFORMANCE.debug("> list account shadow owner oid={}", accountOid); + PrismObject userType = null; + Session session = null; + try { + session = transactionHelper.beginReadOnlyTransaction(); + Query query = session.getNamedQuery("listAccountShadowOwner.getUser"); + query.setString("oid", accountOid); + query.setResultTransformer(GetObjectResult.RESULT_TRANSFORMER); + + List users = query.list(); + LOGGER.trace("Found {} users, transforming data to JAXB types.", + new Object[]{(users != null ? users.size() : 0)}); + + if (users == null || users.isEmpty()) { + // account shadow owner was not found + return null; + } + + if (users.size() > 1) { + LOGGER.warn("Found {} users for account oid {}, returning first user. [interface change needed]", + new Object[]{users.size(), accountOid}); + } + + GetObjectResult user = users.get(0); + userType = updateLoadedObject(user, UserType.class, null, session); + + session.getTransaction().commit(); + } catch (SchemaException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + + return userType; + } + + public int countObjectsAttempt(Class type, ObjectQuery query, OperationResult result) { + LOGGER_PERFORMANCE.debug("> count objects {}", new Object[]{type.getSimpleName()}); + + int count = 0; + + Session session = null; + try { + Class hqlType = ClassMapper.getHQLTypeClass(type); + + session = transactionHelper.beginReadOnlyTransaction(); + Number longCount; + if (query == null || query.getFilter() == null) { + // this is 5x faster than count with 3 inner joins, it can probably improved also for queries which + // filters uses only properties from concrete entities like RUser, RRole by improving interpreter [lazyman] + SQLQuery sqlQuery = session.createSQLQuery("SELECT COUNT(*) FROM " + RUtil.getTableName(hqlType)); + longCount = (Number) sqlQuery.uniqueResult(); + } else { + RQuery rQuery; + if (isUseNewQueryInterpreter(query)) { + QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext); + rQuery = engine.interpret(query, type, null, true, session); + } else { + QueryEngine engine = new QueryEngine(getConfiguration(), prismContext); + rQuery = engine.interpret(query, type, null, true, session); + } + + longCount = (Number) rQuery.uniqueResult(); + } + LOGGER.trace("Found {} objects.", longCount); + count = longCount != null ? longCount.intValue() : 0; + } catch (QueryException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + + return count; + } + + + public SearchResultList> searchObjectsAttempt(Class type, ObjectQuery query, + Collection> options, + OperationResult result) throws SchemaException { + LOGGER_PERFORMANCE.debug("> search objects {}", new Object[]{type.getSimpleName()}); + List> list = new ArrayList<>(); + Session session = null; + try { + session = transactionHelper.beginReadOnlyTransaction(); + RQuery rQuery; + + if (isUseNewQueryInterpreter(query)) { + QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext); + rQuery = engine.interpret(query, type, options, false, session); + } else { + QueryEngine engine = new QueryEngine(getConfiguration(), prismContext); + rQuery = engine.interpret(query, type, options, false, session); + } + + List objects = rQuery.list(); + LOGGER.trace("Found {} objects, translating to JAXB.", new Object[]{(objects != null ? objects.size() : 0)}); + + for (GetObjectResult object : objects) { + PrismObject prismObject = updateLoadedObject(object, type, options, session); + list.add(prismObject); + } + + session.getTransaction().commit(); + } catch (QueryException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + + return new SearchResultList>(list); + } + + public SearchResultList searchContainersAttempt(Class type, ObjectQuery query, + Collection> options, + OperationResult result) throws SchemaException { + + if (!(AccessCertificationCaseType.class.equals(type))) { + throw new UnsupportedOperationException("Only AccessCertificationCaseType is supported here now."); + } + + LOGGER_PERFORMANCE.debug("> search containers {}", new Object[]{type.getSimpleName()}); + List list = new ArrayList<>(); + Session session = null; + try { + session = transactionHelper.beginReadOnlyTransaction(); + + QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext); + RQuery rQuery = engine.interpret(query, type, options, false, session); + + List items = rQuery.list(); + LOGGER.trace("Found {} items, translating to JAXB.", items.size()); + + for (GetObjectResult item : items) { + C value = (C) caseHelper.updateLoadedCertificationCase(item, options, session); + list.add(value); + } + + session.getTransaction().commit(); + } catch (QueryException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + + return new SearchResultList(list); + } + + /** + * This method provides object parsing from String and validation. + */ + private PrismObject updateLoadedObject(GetObjectResult result, Class type, + Collection> options, + Session session) throws SchemaException { + + String xml = RUtil.getXmlFromByteArray(result.getFullObject(), getConfiguration().isUseZip()); + PrismObject prismObject; + try { + // "Postel mode": be tolerant what you read. We need this to tolerate (custom) schema changes + prismObject = prismContext.parseObject(xml, XNodeProcessorEvaluationMode.COMPAT); + } catch (SchemaException e) { + LOGGER.debug("Couldn't parse object because of schema exception ({}):\nObject: {}", e, xml); + throw e; + } catch (RuntimeException e) { + LOGGER.debug("Couldn't parse object because of unexpected exception ({}):\nObject: {}", e, xml); + throw e; + } + + if (FocusType.class.isAssignableFrom(prismObject.getCompileTimeClass())) { + if (SelectorOptions.hasToLoadPath(FocusType.F_JPEG_PHOTO, options)) { + //todo improve, use user.hasPhoto flag and take options into account [lazyman] + //this is called only when options contains INCLUDE user/jpegPhoto + Query query = session.getNamedQuery("get.focusPhoto"); + query.setString("oid", prismObject.getOid()); + byte[] photo = (byte[]) query.uniqueResult(); + if (photo != null) { + PrismProperty property = prismObject.findOrCreateProperty(FocusType.F_JPEG_PHOTO); + property.setRealValue(photo); + } + } + } else if (ShadowType.class.equals(prismObject.getCompileTimeClass())) { + //we store it because provisioning now sends it to repo, but it should be transient + prismObject.removeContainer(ShadowType.F_ASSOCIATION); + + LOGGER.debug("Loading definitions for shadow attributes."); + + Short[] counts = result.getCountProjection(); + Class[] classes = GetObjectResult.EXT_COUNT_CLASSES; + + for (int i = 0; i < classes.length; i++) { + if (counts[i] == null || counts[i] == 0) { + continue; + } + + applyShadowAttributeDefinitions(classes[i], prismObject, session); + } + LOGGER.debug("Definitions for attributes loaded. Counts: {}", Arrays.toString(counts)); + } else if (LookupTableType.class.equals(prismObject.getCompileTimeClass())) { + lookupTableHelper.updateLoadedLookupTable(prismObject, options, session); + } else if (AccessCertificationCampaignType.class.equals(prismObject.getCompileTimeClass())) { + caseHelper.updateLoadedCampaign(prismObject, options, session); + } + + nameResolutionHelper.resolveNamesIfRequested(session, prismObject.getValue(), options); + validateObjectType(prismObject, type); + + return prismObject; + } + + + private void applyShadowAttributeDefinitions(Class anyValueType, + PrismObject object, Session session) throws SchemaException { + + PrismContainer attributes = object.findContainer(ShadowType.F_ATTRIBUTES); + + Query query = session.getNamedQuery("getDefinition." + anyValueType.getSimpleName()); + query.setParameter("oid", object.getOid()); + query.setParameter("ownerType", RObjectExtensionType.ATTRIBUTES); + + List values = query.list(); + if (values == null || values.isEmpty()) { + return; + } + + for (Object[] value : values) { + QName name = RUtil.stringToQName((String) value[0]); + QName type = RUtil.stringToQName((String) value[1]); + Item item = attributes.findItem(name); + + // A switch statement used to be here + // but that caused strange trouble with OpenJDK. This if-then-else works. + if (item.getDefinition() == null) { + RValueType rValType = (RValueType) value[2]; + if (rValType == RValueType.PROPERTY) { + PrismPropertyDefinition def = new PrismPropertyDefinition(name, type, object.getPrismContext()); + item.applyDefinition(def, true); + } else if (rValType == RValueType.REFERENCE) { + PrismReferenceDefinition def = new PrismReferenceDefinition(name, type, object.getPrismContext()); + item.applyDefinition(def, true); + } else { + throw new UnsupportedOperationException("Unsupported value type " + rValType); + } + } + } + } + + public List> listResourceObjectShadowsAttempt( + String resourceOid, Class resourceObjectShadowType, OperationResult result) + throws ObjectNotFoundException, SchemaException { + + LOGGER_PERFORMANCE.debug("> list resource object shadows {}, for resource oid={}", + new Object[]{resourceObjectShadowType.getSimpleName(), resourceOid}); + List> list = new ArrayList<>(); + Session session = null; + try { + session = transactionHelper.beginReadOnlyTransaction(); + Query query = session.getNamedQuery("listResourceObjectShadows"); + query.setString("oid", resourceOid); + query.setResultTransformer(GetObjectResult.RESULT_TRANSFORMER); + + List shadows = query.list(); + LOGGER.debug("Query returned {} shadows, transforming to JAXB types.", + new Object[]{(shadows != null ? shadows.size() : 0)}); + + if (shadows != null) { + for (GetObjectResult shadow : shadows) { + PrismObject prismObject = updateLoadedObject(shadow, resourceObjectShadowType, null, session); + list.add(prismObject); + } + } + session.getTransaction().commit(); + } catch (SchemaException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + + return list; + } + + private void validateObjectType(PrismObject prismObject, Class type) + throws SchemaException { + if (prismObject == null || !type.isAssignableFrom(prismObject.getCompileTimeClass())) { + throw new SchemaException("Expected to find '" + type.getSimpleName() + "' but found '" + + prismObject.getCompileTimeClass().getSimpleName() + "' (" + prismObject.toDebugName() + + "). Bad OID in a reference?"); + } + if (InternalsConfig.consistencyChecks) { + prismObject.checkConsistence(); + } + if (InternalsConfig.readEncryptionChecks) { + CryptoUtil.checkEncrypted(prismObject); + } + } + + public String getVersionAttempt(Class type, String oid, OperationResult result) + throws ObjectNotFoundException, SchemaException { + LOGGER_PERFORMANCE.debug("> get version {}, oid={}", new Object[]{type.getSimpleName(), oid}); + + String version = null; + Session session = null; + try { + session = transactionHelper.beginReadOnlyTransaction(); + Query query = session.getNamedQuery("getVersion"); + query.setString("oid", oid); + + Number versionLong = (Number) query.uniqueResult(); + if (versionLong == null) { + throw new ObjectNotFoundException("Object '" + type.getSimpleName() + + "' with oid '" + oid + "' was not found."); + } + version = versionLong.toString(); + } catch (RuntimeException ex) { + transactionHelper.handleGeneralRuntimeException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + + return version; + } + + public void searchObjectsIterativeAttempt(Class type, ObjectQuery query, + ResultHandler handler, + Collection> options, + OperationResult result) throws SchemaException { + Session session = null; + try { + session = transactionHelper.beginReadOnlyTransaction(); + RQuery rQuery; + if (isUseNewQueryInterpreter(query)) { + QueryEngine engine = new QueryEngine(getConfiguration(), prismContext); + rQuery = engine.interpret(query, type, options, false, session); + } else { + QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext); + rQuery = engine.interpret(query, type, options, false, session); + } + + ScrollableResults results = rQuery.scroll(ScrollMode.FORWARD_ONLY); + try { + Iterator iterator = new ScrollableResultsIterator(results); + while (iterator.hasNext()) { + GetObjectResult object = iterator.next(); + + PrismObject prismObject = updateLoadedObject(object, type, options, session); + if (!handler.handle(prismObject, result)) { + break; + } + } + } finally { + if (results != null) { + results.close(); + } + } + + session.getTransaction().commit(); + } catch (SchemaException | QueryException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + } + + public void searchObjectsIterativeByPaging(Class type, ObjectQuery query, + ResultHandler handler, + Collection> options, + OperationResult result) + throws SchemaException { + + try { + ObjectQuery pagedQuery = query != null ? query.clone() : new ObjectQuery(); + + int offset; + int remaining; + final int batchSize = getConfiguration().getIterativeSearchByPagingBatchSize(); + + ObjectPaging paging = pagedQuery.getPaging(); + + if (paging == null) { + paging = ObjectPaging.createPaging(0, 0); // counts will be filled-in later + pagedQuery.setPaging(paging); + offset = 0; + remaining = repositoryService.countObjects(type, query, result); + } else { + offset = paging.getOffset() != null ? paging.getOffset() : 0; + remaining = paging.getMaxSize() != null ? paging.getMaxSize() : repositoryService.countObjects(type, query, result) - offset; + } + +main: while (remaining > 0) { + paging.setOffset(offset); + paging.setMaxSize(remaining < batchSize ? remaining : batchSize); + + List> objects = repositoryService.searchObjects(type, pagedQuery, options, result); + + for (PrismObject object : objects) { + if (!handler.handle(object, result)) { + break main; + } + } + + if (objects.size() == 0) { + break; // should not occur, but let's check for this to avoid endless loops + } + offset += objects.size(); + remaining -= objects.size(); + } + } finally { + if (result != null && result.isUnknown()) { + result.computeStatus(); + } + } + } + + /** + * Strictly-sequential version of paged search. + * + * Assumptions: + * - During processing of returned object(s), any objects can be added, deleted or modified. + * + * Guarantees: + * - We return each object that existed in the moment of search start: + * - exactly once if it was not deleted in the meanwhile, + * - at most once otherwise. + * - However, we may or may not return any objects that were added during the processing. + * + * Constraints: + * - There can be no ordering prescribed. We use our own ordering. + * - Moreover, for simplicity we disallow any explicit paging. + * + * Implementation is very simple - we fetch objects ordered by OID, and remember last OID fetched. + * Obviously no object will be present in output more than once. + * Objects that are not deleted will be there exactly once, provided their oid is not changed. + */ + public void searchObjectsIterativeByPagingStrictlySequential( + Class type, ObjectQuery query, ResultHandler handler, + Collection> options, OperationResult result) + throws SchemaException { + + try { + ObjectQuery pagedQuery = query != null ? query.clone() : new ObjectQuery(); + + String lastOid = ""; + final int batchSize = getConfiguration().getIterativeSearchByPagingBatchSize(); + + if (pagedQuery.getPaging() != null) { + throw new IllegalArgumentException("Externally specified paging is not supported on strictly sequential iterative search."); + } + + ObjectPagingAfterOid paging = new ObjectPagingAfterOid(); + pagedQuery.setPaging(paging); +main: for (;;) { + paging.setOidGreaterThan(lastOid); + paging.setMaxSize(batchSize); + + List> objects = repositoryService.searchObjects(type, pagedQuery, options, result); + + for (PrismObject object : objects) { + lastOid = object.getOid(); + if (!handler.handle(object, result)) { + break main; + } + } + + if (objects.size() == 0) { + break; + } + } + } finally { + if (result != null && result.isUnknown()) { + result.computeStatus(); + } + } + } + + public boolean isAnySubordinateAttempt(String upperOrgOid, Collection lowerObjectOids) { + Session session = null; + try { + session = transactionHelper.beginTransaction(); + + Query query; + if (lowerObjectOids.size() == 1) { + query = session.getNamedQuery("isAnySubordinateAttempt.oneLowerOid"); + query.setString("dOid", lowerObjectOids.iterator().next()); + } else { + query = session.getNamedQuery("isAnySubordinateAttempt.moreLowerOids"); + query.setParameterList("dOids", lowerObjectOids); + } + query.setString("aOid", upperOrgOid); + + Number number = (Number) query.uniqueResult(); + return number != null && number.longValue() != 0L; + } catch (RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, null); + } finally { + transactionHelper.cleanupSessionAndResult(session, null); + } + + throw new SystemException("isAnySubordinateAttempt failed somehow, this really should not happen."); + } + + public String executeArbitraryQueryAttempt(String queryString, OperationResult result) { + LOGGER_PERFORMANCE.debug("> execute query {}", queryString); + + Session session = null; + StringBuffer answer = new StringBuffer(); + try { + session = transactionHelper.beginReadOnlyTransaction(); // beware, not all databases support read-only transactions! + + Query query = session.createQuery(queryString); + List results = query.list(); + if (results != null) { + answer.append("Result: ").append(results.size()).append(" item(s):\n\n"); + for (Object item : results) { + if (item instanceof Object[]) { + boolean first = true; + for (Object item1 : (Object[]) item) { + if (first) { + first = false; + } else { + answer.append(","); + } + answer.append(item1); + } + } else { + answer.append(item); + } + answer.append("\n"); + } + } + session.getTransaction().rollback(); + } catch (RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Executed query:\n{}\nwith result:\n{}", queryString, answer); + } + + return answer.toString(); + } + + private boolean isUseNewQueryInterpreter(ObjectQuery query) { + return query == null || query.isUseNewQueryInterpreter(); + } + + +} diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/ObjectUpdater.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/ObjectUpdater.java new file mode 100644 index 00000000000..e0a70115a48 --- /dev/null +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/ObjectUpdater.java @@ -0,0 +1,545 @@ +/* + * Copyright (c) 2010-2015 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.helpers; + +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.PrismReference; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.ReferenceDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.repo.api.RepoAddOptions; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.sql.SerializationRelatedException; +import com.evolveum.midpoint.repo.sql.SqlRepositoryConfiguration; +import com.evolveum.midpoint.repo.sql.SqlRepositoryServiceImpl; +import com.evolveum.midpoint.repo.sql.data.common.RObject; +import com.evolveum.midpoint.repo.sql.query.QueryException; +import com.evolveum.midpoint.repo.sql.util.ClassMapper; +import com.evolveum.midpoint.repo.sql.util.DtoTranslationException; +import com.evolveum.midpoint.repo.sql.util.IdGeneratorResult; +import com.evolveum.midpoint.repo.sql.util.PrismIdentifierGenerator; +import com.evolveum.midpoint.repo.sql.util.RUtil; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.RetrieveOption; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.DebugUtil; +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.AccessCertificationCampaignType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import org.apache.commons.lang.StringUtils; +import org.hibernate.Criteria; +import org.hibernate.Query; +import org.hibernate.SQLQuery; +import org.hibernate.Session; +import org.hibernate.criterion.Restrictions; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * @author lazyman, mederly + */ + +@Component +public class ObjectUpdater { + + private static final Trace LOGGER = TraceManager.getTrace(SqlRepositoryServiceImpl.class); + private static final Trace LOGGER_PERFORMANCE = TraceManager.getTrace(SqlRepositoryServiceImpl.PERFORMANCE_LOG_NAME); + + @Autowired + @Qualifier("repositoryService") + private RepositoryService repositoryService; + + @Autowired + private TransactionHelper transactionHelper; + + @Autowired + private ObjectRetriever objectRetriever; + + @Autowired + private LookupTableHelper lookupTableHelper; + + @Autowired + private CertificationCaseHelper caseHelper; + + @Autowired + private OrgClosureManager closureManager; + + @Autowired + private PrismContext prismContext; + + public String addObjectAttempt(PrismObject object, RepoAddOptions options, + OperationResult result) throws ObjectAlreadyExistsException, SchemaException { + + LOGGER_PERFORMANCE.debug("> add object {}, oid={}, overwrite={}", + object.getCompileTimeClass().getSimpleName(), object.getOid(), options.isOverwrite()); + + String oid = null; + Session session = null; + OrgClosureManager.Context closureContext = null; + // it is needed to keep the original oid for example for import options. if we do not keep it + // and it was null it can bring some error because the oid is set when the object contains orgRef + // or it is org. and by the import we do not know it so it will be trying to delete non-existing object + String originalOid = object.getOid(); + try { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Object\n{}", new Object[]{object.debugDump()}); + } + + LOGGER.trace("Translating JAXB to data type."); + PrismIdentifierGenerator.Operation operation = options.isOverwrite() ? + PrismIdentifierGenerator.Operation.ADD_WITH_OVERWRITE : + PrismIdentifierGenerator.Operation.ADD; + + RObject rObject = createDataObjectFromJAXB(object, operation); + + session = beginTransaction(); + + closureContext = closureManager.onBeginTransactionAdd(session, object, options.isOverwrite()); + + if (options.isOverwrite()) { + oid = overwriteAddObjectAttempt(object, rObject, originalOid, session, closureContext); + } else { + oid = nonOverwriteAddObjectAttempt(object, rObject, originalOid, session, closureContext); + } + session.getTransaction().commit(); + + LOGGER.trace("Saved object '{}' with oid '{}'", new Object[]{ + object.getCompileTimeClass().getSimpleName(), oid}); + + object.setOid(oid); + } catch (ConstraintViolationException ex) { + handleConstraintViolationException(session, ex, result); + transactionHelper.rollbackTransaction(session, ex, result, true); + + LOGGER.debug("Constraint violation occurred (will be rethrown as ObjectAlreadyExistsException).", ex); + // we don't know if it's only name uniqueness violation, or something else, + // therefore we're throwing it always as ObjectAlreadyExistsException revert + // to the original oid and prevent of unexpected behaviour (e.g. by import with overwrite option) + if (StringUtils.isEmpty(originalOid)) { + object.setOid(null); + } + String constraintName = ex.getConstraintName(); + // Breaker to avoid long unreadable messages + if (constraintName != null && constraintName.length() > SqlRepositoryServiceImpl.MAX_CONSTRAINT_NAME_LENGTH) { + constraintName = null; + } + throw new ObjectAlreadyExistsException("Conflicting object already exists" + + (constraintName == null ? "" : " (violated constraint '" + constraintName + "')"), ex); + } catch (ObjectAlreadyExistsException | SchemaException ex) { + transactionHelper.rollbackTransaction(session, ex, result, true); + throw ex; + } catch (DtoTranslationException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + cleanupClosureAndSessionAndResult(closureContext, session, result); + } + + return oid; + } + + private Session beginTransaction() { + return transactionHelper.beginTransaction(); + } + + private String overwriteAddObjectAttempt(PrismObject object, RObject rObject, + String originalOid, Session session, OrgClosureManager.Context closureContext) + throws ObjectAlreadyExistsException, SchemaException, DtoTranslationException { + + PrismObject oldObject = null; + + //check if object already exists, find differences and increment version if necessary + Collection modifications = null; + if (originalOid != null) { + try { + oldObject = objectRetriever.getObjectInternal(session, object.getCompileTimeClass(), originalOid, null, true); + ObjectDelta delta = object.diff(oldObject); + modifications = delta.getModifications(); + + //we found existing object which will be overwritten, therefore we increment version + Integer version = RUtil.getIntegerFromString(oldObject.getVersion()); + version = (version == null) ? 0 : ++version; + + rObject.setVersion(version); + } catch (QueryException ex) { + transactionHelper.handleGeneralCheckedException(ex, session, null); + } catch (ObjectNotFoundException ex) { + //it's ok that object was not found, therefore we won't be overwriting it + } + } + + updateFullObject(rObject, object); + RObject merged = (RObject) session.merge(rObject); + lookupTableHelper.addLookupTableRows(session, rObject, modifications != null); + caseHelper.addCertificationCampaignCases(session, rObject, modifications != null); + + if (closureManager.isEnabled()) { + OrgClosureManager.Operation operation; + if (modifications == null) { + operation = OrgClosureManager.Operation.ADD; + modifications = createAddParentRefDelta(object); + } else { + operation = OrgClosureManager.Operation.MODIFY; + } + closureManager.updateOrgClosure(oldObject, modifications, session, merged.getOid(), object.getCompileTimeClass(), + operation, closureContext); + } + return merged.getOid(); + } + + private List createAddParentRefDelta(PrismObject object) { + PrismReference parentOrgRef = object.findReference(ObjectType.F_PARENT_ORG_REF); + if (parentOrgRef == null || parentOrgRef.isEmpty()) { + return new ArrayList<>(); + } + + PrismObjectDefinition def = object.getDefinition(); + ReferenceDelta delta = ReferenceDelta.createModificationAdd(new ItemPath(ObjectType.F_PARENT_ORG_REF), + def, parentOrgRef.getClonedValues()); + + return Arrays.asList(delta); + } + + public void updateFullObject(RObject object, PrismObject savedObject) + throws DtoTranslationException, SchemaException { + LOGGER.debug("Updating full object xml column start."); + savedObject.setVersion(Integer.toString(object.getVersion())); + + if (FocusType.class.isAssignableFrom(savedObject.getCompileTimeClass())) { + savedObject.removeProperty(FocusType.F_JPEG_PHOTO); + } else if (LookupTableType.class.equals(savedObject.getCompileTimeClass())) { + PrismContainer table = savedObject.findContainer(LookupTableType.F_ROW); + savedObject.remove(table); + } else if (AccessCertificationCampaignType.class.equals(savedObject.getCompileTimeClass())) { + PrismContainer caseContainer = savedObject.findContainer(AccessCertificationCampaignType.F_CASE); + savedObject.remove(caseContainer); + } + + String xml = prismContext.serializeObjectToString(savedObject, PrismContext.LANG_XML); + byte[] fullObject = RUtil.getByteArrayFromXml(xml, getConfiguration().isUseZip()); + + if (LOGGER.isTraceEnabled()) LOGGER.trace("Storing full object\n{}", xml); + + object.setFullObject(fullObject); + + LOGGER.debug("Updating full object xml column finish."); + } + + protected SqlRepositoryConfiguration getConfiguration() { + return ((SqlRepositoryServiceImpl) repositoryService).getConfiguration(); + } + + private String nonOverwriteAddObjectAttempt(PrismObject object, RObject rObject, + String originalOid, Session session, OrgClosureManager.Context closureContext) + throws ObjectAlreadyExistsException, SchemaException, DtoTranslationException { + + // check name uniqueness (by type) + if (StringUtils.isNotEmpty(originalOid)) { + LOGGER.trace("Checking oid uniqueness."); + //todo improve this table name bullshit + Class hqlType = ClassMapper.getHQLTypeClass(object.getCompileTimeClass()); + SQLQuery query = session.createSQLQuery("select count(*) from " + RUtil.getTableName(hqlType) + + " where oid=:oid"); + query.setString("oid", object.getOid()); + + Number count = (Number) query.uniqueResult(); + if (count != null && count.longValue() > 0) { + throw new ObjectAlreadyExistsException("Object '" + object.getCompileTimeClass().getSimpleName() + + "' with oid '" + object.getOid() + "' already exists."); + } + } + + updateFullObject(rObject, object); + + LOGGER.trace("Saving object (non overwrite)."); + String oid = (String) session.save(rObject); + lookupTableHelper.addLookupTableRows(session, rObject, false); + caseHelper.addCertificationCampaignCases(session, rObject, false); + + if (closureManager.isEnabled()) { + Collection modifications = createAddParentRefDelta(object); + closureManager.updateOrgClosure(null, modifications, session, oid, object.getCompileTimeClass(), + OrgClosureManager.Operation.ADD, closureContext); + } + + return oid; + } + + + public void deleteObjectAttempt(Class type, String oid, OperationResult result) + throws ObjectNotFoundException { + LOGGER_PERFORMANCE.debug("> delete object {}, oid={}", new Object[]{type.getSimpleName(), oid}); + Session session = null; + OrgClosureManager.Context closureContext = null; + try { + session = beginTransaction(); + + closureContext = closureManager.onBeginTransactionDelete(session, type, oid); + + Criteria query = session.createCriteria(ClassMapper.getHQLTypeClass(type)); + query.add(Restrictions.eq("oid", oid)); + RObject object = (RObject) query.uniqueResult(); + if (object == null) { + throw new ObjectNotFoundException("Object of type '" + type.getSimpleName() + "' with oid '" + oid + + "' was not found.", null, oid); + } + + closureManager.updateOrgClosure(null, null, session, oid, type, OrgClosureManager.Operation.DELETE, closureContext); + + session.delete(object); + if (LookupTableType.class.equals(type)) { + lookupTableHelper.deleteLookupTableRows(session, oid); + } + if (AccessCertificationCampaignType.class.equals(type)) { + caseHelper.deleteCertificationCampaignCases(session, oid); + } + + session.getTransaction().commit(); + } catch (ObjectNotFoundException ex) { + transactionHelper.rollbackTransaction(session, ex, result, true); + throw ex; + } catch (RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + cleanupClosureAndSessionAndResult(closureContext, session, result); + } + } + + public void modifyObjectAttempt(Class type, String oid, + Collection modifications, + OperationResult result) throws ObjectNotFoundException, + SchemaException, ObjectAlreadyExistsException, SerializationRelatedException { + + // shallow clone - because some methods, e.g. filterLookupTableModifications manipulate this collection + modifications = new ArrayList<>(modifications); + + LOGGER.debug("Modifying object '{}' with oid '{}'.", new Object[]{type.getSimpleName(), oid}); + LOGGER_PERFORMANCE.debug("> modify object {}, oid={}, modifications={}", + new Object[]{type.getSimpleName(), oid, modifications}); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Modifications:\n{}", new Object[]{DebugUtil.debugDump(modifications)}); + } + + Session session = null; + OrgClosureManager.Context closureContext = null; + try { + session = beginTransaction(); + + closureContext = closureManager.onBeginTransactionModify(session, type, oid, modifications); + + Collection lookupTableModifications = lookupTableHelper.filterLookupTableModifications(type, modifications); + Collection campaignCaseModifications = caseHelper.filterCampaignCaseModifications(type, modifications); + + // JpegPhoto (RFocusPhoto) is a special kind of entity. First of all, it is lazily loaded, because photos are really big. + // Each RFocusPhoto naturally belongs to one RFocus, so it would be appropriate to set orphanRemoval=true for focus-photo + // association. However, this leads to a strange problem when merging in-memory RFocus object with the database state: + // If in-memory RFocus object has no photo associated (because of lazy loading), then the associated RFocusPhoto is deleted. + // + // To prevent this behavior, we've set orphanRemoval to false. Fortunately, the remove operation on RFocus + // seems to be still cascaded to RFocusPhoto. What we have to implement ourselves, however, is removal of RFocusPhoto + // _without_ removing of RFocus. In order to know whether the photo has to be removed, we have to retrieve + // its value, apply the delta (e.g. if the delta is a DELETE VALUE X, we have to know whether X matches current + // value of the photo), and if the resulting value is empty, we have to manually delete the RFocusPhoto instance. + // + // So the first step is to retrieve the current value of photo - we obviously do this only if the modifications + // deal with the jpegPhoto property. + Collection> options; + boolean containsFocusPhotoModification = FocusType.class.isAssignableFrom(type) && containsPhotoModification(modifications); + if (containsFocusPhotoModification) { + options = Arrays.asList(SelectorOptions.create(FocusType.F_JPEG_PHOTO, GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE))); + } else { + options = null; + } + + // TODO skip processing if there are no modifications other than row/case ones + + // get object + PrismObject prismObject = objectRetriever.getObjectInternal(session, type, oid, options, true); + // apply diff + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("OBJECT before:\n{}", new Object[]{prismObject.debugDump()}); + } + PrismObject originalObject = null; + if (closureManager.isEnabled()) { + originalObject = prismObject.clone(); + } + ItemDelta.applyTo(modifications, prismObject); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("OBJECT after:\n{}", prismObject.debugDump()); + } + + // Continuing the photo treatment: should we remove the (now obsolete) focus photo? + // We have to test prismObject at this place, because updateFullObject (below) removes photo property from the prismObject. + boolean shouldPhotoBeRemoved = containsFocusPhotoModification && ((FocusType) prismObject.asObjectable()).getJpegPhoto() == null; + + // merge and update object + LOGGER.trace("Translating JAXB to data type."); + RObject rObject = createDataObjectFromJAXB(prismObject, PrismIdentifierGenerator.Operation.MODIFY); + rObject.setVersion(rObject.getVersion() + 1); + + updateFullObject(rObject, prismObject); + session.merge(rObject); + + lookupTableHelper.updateLookupTableData(session, rObject, lookupTableModifications); + caseHelper.updateCampaignCases(session, rObject, campaignCaseModifications); + + if (closureManager.isEnabled()) { + closureManager.updateOrgClosure(originalObject, modifications, session, oid, type, OrgClosureManager.Operation.MODIFY, closureContext); + } + + // JpegPhoto cleanup: As said before, if a focus has to have no photo (after modifications are applied), + // we have to remove the photo manually. + if (shouldPhotoBeRemoved) { + Query query = session.createQuery("delete RFocusPhoto where ownerOid = :oid"); + query.setParameter("oid", prismObject.getOid()); + query.executeUpdate(); + LOGGER.trace("Focus photo for {} was deleted", prismObject.getOid()); + } + + LOGGER.trace("Before commit..."); + session.getTransaction().commit(); + LOGGER.trace("Committed!"); + } catch (ObjectNotFoundException ex) { + transactionHelper.rollbackTransaction(session, ex, result, true); + throw ex; + } catch (ConstraintViolationException ex) { + handleConstraintViolationException(session, ex, result); + + transactionHelper.rollbackTransaction(session, ex, result, true); + + LOGGER.debug("Constraint violation occurred (will be rethrown as ObjectAlreadyExistsException).", ex); + // we don't know if it's only name uniqueness violation, or something else, + // therefore we're throwing it always as ObjectAlreadyExistsException + + //todo improve (we support only 5 DB, so we should probably do some hacking in here) + throw new ObjectAlreadyExistsException(ex); + } catch (SchemaException ex) { + transactionHelper.rollbackTransaction(session, ex, result, true); + throw ex; + } catch (QueryException | DtoTranslationException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); + } finally { + cleanupClosureAndSessionAndResult(closureContext, session, result); + LOGGER.trace("Session cleaned up."); + } + } + + private boolean containsPhotoModification(Collection modifications) { + ItemPath photoPath = new ItemPath(FocusType.F_JPEG_PHOTO); + for (ItemDelta delta : modifications) { + ItemPath path = delta.getPath(); + if (path.isEmpty()) { + throw new UnsupportedOperationException("Focus cannot be modified via empty-path modification"); + } else if (photoPath.isSubPathOrEquivalent(path)) { // actually, "subpath" variant should not occur + return true; + } + } + + return false; + } + + private void cleanupClosureAndSessionAndResult(final OrgClosureManager.Context closureContext, final Session session, final OperationResult result) { + if (closureContext != null) { + closureManager.cleanUpAfterOperation(closureContext, session); + } + transactionHelper.cleanupSessionAndResult(session, result); + } + + private void handleConstraintViolationException(Session session, ConstraintViolationException ex, OperationResult result) { + + // BRUTAL HACK - in PostgreSQL, concurrent changes in parentRefOrg sometimes cause the following exception + // "duplicate key value violates unique constraint "XXXX". This is *not* an ObjectAlreadyExistsException, + // more likely it is a serialization-related one. + // + // TODO: somewhat generalize this approach - perhaps by retrying all operations not dealing with OID/name uniqueness + + SQLException sqlException = transactionHelper.findSqlException(ex); + if (sqlException != null) { + SQLException nextException = sqlException.getNextException(); + LOGGER.debug("ConstraintViolationException = {}; SQL exception = {}; embedded SQL exception = {}", new Object[]{ex, sqlException, nextException}); + String[] ok = new String[]{ + "duplicate key value violates unique constraint \"m_org_closure_pkey\"", + "duplicate key value violates unique constraint \"m_reference_pkey\"" + }; + String msg1; + if (sqlException.getMessage() != null) { + msg1 = sqlException.getMessage(); + } else { + msg1 = ""; + } + String msg2; + if (nextException != null && nextException.getMessage() != null) { + msg2 = nextException.getMessage(); + } else { + msg2 = ""; + } + for (int i = 0; i < ok.length; i++) { + if (msg1.contains(ok[i]) || msg2.contains(ok[i])) { + transactionHelper.rollbackTransaction(session, ex, result, false); + throw new SerializationRelatedException(ex); + } + } + } + } + + public RObject createDataObjectFromJAXB(PrismObject prismObject, + PrismIdentifierGenerator.Operation operation) + throws SchemaException { + + PrismIdentifierGenerator generator = new PrismIdentifierGenerator(); + IdGeneratorResult generatorResult = generator.generate(prismObject, operation); + + T object = prismObject.asObjectable(); + + RObject rObject; + Class clazz = ClassMapper.getHQLTypeClass(object.getClass()); + try { + rObject = clazz.newInstance(); + Method method = clazz.getMethod("copyFromJAXB", object.getClass(), clazz, + PrismContext.class, IdGeneratorResult.class); + method.invoke(clazz, object, rObject, prismContext, generatorResult); + } catch (Exception ex) { + String message = ex.getMessage(); + if (StringUtils.isEmpty(message) && ex.getCause() != null) { + message = ex.getCause().getMessage(); + } + throw new SchemaException(message, ex); + } + + return rObject; + } + +} diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/OrgClosureManager.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/OrgClosureManager.java similarity index 97% rename from repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/OrgClosureManager.java rename to repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/OrgClosureManager.java index dd0d9aa3280..ea8c7af8248 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/OrgClosureManager.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/OrgClosureManager.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.evolveum.midpoint.repo.sql; +package com.evolveum.midpoint.repo.sql.helpers; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ReferenceDelta; import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.repo.sql.SqlRepositoryConfiguration; +import com.evolveum.midpoint.repo.sql.SqlRepositoryServiceImpl; import com.evolveum.midpoint.repo.sql.data.common.ROrgClosure; import com.evolveum.midpoint.repo.sql.data.common.other.RObjectType; import com.evolveum.midpoint.schema.ResultHandler; @@ -45,7 +47,11 @@ import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.type.IntegerType; import org.hibernate.type.StringType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -71,21 +77,20 @@ * @author lazyman * @author mederly */ +@Component +@DependsOn("repositoryService") public class OrgClosureManager { private static final Trace LOGGER = TraceManager.getTrace(OrgClosureManager.class); + @Autowired + private SqlRepositoryServiceImpl sqlRepositoryService; + private static boolean DUMP_TABLES = false; private static final boolean COUNT_CLOSURE_RECORDS = false; static final String CLOSURE_TABLE_NAME = "m_org_closure"; public static final String TEMP_DELTA_TABLE_NAME_FOR_ORACLE = "m_org_closure_temp_delta"; - private SqlRepositoryConfiguration repoConfiguration; - - public OrgClosureManager(SqlRepositoryConfiguration repoConfiguration) { - this.repoConfiguration = repoConfiguration; - } - // only for single-thread performance testing long lastOperationDuration; @@ -214,19 +219,22 @@ public void cleanUpAfterOperation(Context closureContext, Session session) { } } - public void initialize(SqlRepositoryServiceImpl service) { + @PostConstruct + public void initialize() { OperationResult result = new OperationResult(OrgClosureManager.class.getName() + ".initialize"); if (!isEnabled()) { return; } + SqlRepositoryConfiguration repoConfiguration = sqlRepositoryService.getConfiguration(); + if (isOracle()) { - initializeOracleTemporaryTable(service); + initializeOracleTemporaryTable(sqlRepositoryService); } - if (autoUpdateClosureTableStructure(service)) { + if (autoUpdateClosureTableStructure(sqlRepositoryService)) { // need to rebuild the content of the closure table after re-creating it anew - checkAndOrRebuild(service, false, true, repoConfiguration.isStopOnOrgClosureStartupFailure(), true, result); + checkAndOrRebuild(sqlRepositoryService, false, true, repoConfiguration.isStopOnOrgClosureStartupFailure(), true, result); } else { boolean check, rebuild; switch (repoConfiguration.getOrgClosureStartupAction()) { @@ -247,7 +255,7 @@ public void initialize(SqlRepositoryServiceImpl service) { default: throw new IllegalArgumentException("Invalid value: " + repoConfiguration.getOrgClosureStartupAction()); } - checkAndOrRebuild(service, check, rebuild, repoConfiguration.isStopOnOrgClosureStartupFailure(), true, result); + checkAndOrRebuild(sqlRepositoryService, check, rebuild, repoConfiguration.isStopOnOrgClosureStartupFailure(), true, result); } } @@ -261,7 +269,7 @@ public void initialize(SqlRepositoryServiceImpl service) { // returns true if the table was re-created private boolean autoUpdateClosureTableStructure(SqlRepositoryServiceImpl service) { - if (repoConfiguration.isSkipOrgClosureStructureCheck()) { + if (sqlRepositoryService.getConfiguration().isSkipOrgClosureStructureCheck()) { LOGGER.debug("Skipping org closure structure check."); return false; } @@ -339,7 +347,7 @@ public void execute(Connection connection) throws SQLException { } public boolean isEnabled() { - return !repoConfiguration.isIgnoreOrgClosure(); + return !sqlRepositoryService.getConfiguration().isIgnoreOrgClosure(); } /** @@ -1307,23 +1315,23 @@ private List retainExistingOids(Collection oids, Session session } private boolean isMySQL() { - return repoConfiguration.isUsingMySQL(); + return sqlRepositoryService.getConfiguration().isUsingMySQL(); } private boolean isOracle() { - return repoConfiguration.isUsingOracle(); + return sqlRepositoryService.getConfiguration().isUsingOracle(); } private boolean isSQLServer() { - return repoConfiguration.isUsingSQLServer(); + return sqlRepositoryService.getConfiguration().isUsingSQLServer(); } private boolean isH2() { - return repoConfiguration.isUsingH2(); + return sqlRepositoryService.getConfiguration().isUsingH2(); } private boolean isPostgreSQL() { - return repoConfiguration.isUsingPostgreSQL(); + return sqlRepositoryService.getConfiguration().isUsingPostgreSQL(); } //endregion diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/SequenceHelper.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/SequenceHelper.java new file mode 100644 index 00000000000..a1541bd2181 --- /dev/null +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/SequenceHelper.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2010-2015 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.helpers; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.repo.sql.SerializationRelatedException; +import com.evolveum.midpoint.repo.sql.SqlRepositoryServiceImpl; +import com.evolveum.midpoint.repo.sql.data.common.RObject; +import com.evolveum.midpoint.repo.sql.query.QueryException; +import com.evolveum.midpoint.repo.sql.util.DtoTranslationException; +import com.evolveum.midpoint.repo.sql.util.PrismIdentifierGenerator; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SequenceType; +import org.hibernate.Session; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Iterator; + +/** + * @author mederly + */ +@Component +public class SequenceHelper { + + @Autowired + private ObjectRetriever objectRetriever; + + @Autowired + private ObjectUpdater objectUpdater; + + @Autowired + private TransactionHelper transactionHelper; + + private static final Trace LOGGER = TraceManager.getTrace(SqlRepositoryServiceImpl.class); + private static final Trace LOGGER_PERFORMANCE = TraceManager.getTrace(SqlRepositoryServiceImpl.PERFORMANCE_LOG_NAME); + + public long advanceSequenceAttempt(String oid, OperationResult result) throws ObjectNotFoundException, + SchemaException, SerializationRelatedException { + + long returnValue; + + LOGGER.debug("Advancing sequence with oid '{}'.", oid); + LOGGER_PERFORMANCE.debug("> advance sequence, oid={}", oid); + + Session session = null; + try { + session = transactionHelper.beginTransaction(); + + PrismObject prismObject = objectRetriever.getObjectInternal(session, SequenceType.class, oid, null, true); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("OBJECT before:\n{}", prismObject.debugDump()); + } + SequenceType sequence = prismObject.asObjectable(); + + if (!sequence.getUnusedValues().isEmpty()) { + returnValue = sequence.getUnusedValues().remove(0); + } else { + long counter = sequence.getCounter() != null ? sequence.getCounter() : 0L; + long maxCounter = sequence.getMaxCounter() != null ? sequence.getMaxCounter() : Long.MAX_VALUE; + boolean allowRewind = Boolean.TRUE.equals(sequence.isAllowRewind()); + + if (counter < maxCounter) { + returnValue = counter; + sequence.setCounter(counter + 1); + } else if (counter == maxCounter) { + returnValue = counter; + if (allowRewind) { + sequence.setCounter(0L); + } else { + sequence.setCounter(counter + 1); // will produce exception during next run + } + } else { // i.e. counter > maxCounter + if (allowRewind) { // shouldn't occur but... + LOGGER.warn("Sequence {} overflown with allowRewind set to true. Rewinding.", oid); + returnValue = 0; + sequence.setCounter(1L); + } else { + // TODO some better exception... + throw new SystemException("No (next) value available from sequence " + oid + ". Current counter = " + sequence.getCounter() + ", max value = " + sequence.getMaxCounter()); + } + } + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Return value = {}, OBJECT after:\n{}", returnValue, prismObject.debugDump()); + } + + // merge and update object + LOGGER.trace("Translating JAXB to data type."); + RObject rObject = objectUpdater.createDataObjectFromJAXB(prismObject, PrismIdentifierGenerator.Operation.MODIFY); + rObject.setVersion(rObject.getVersion() + 1); + + objectUpdater.updateFullObject(rObject, prismObject); + session.merge(rObject); + + LOGGER.trace("Before commit..."); + session.getTransaction().commit(); + LOGGER.trace("Committed!"); + + return returnValue; + } catch (ObjectNotFoundException ex) { + transactionHelper.rollbackTransaction(session, ex, result, true); + throw ex; + } catch (SchemaException ex) { + transactionHelper.rollbackTransaction(session, ex, result, true); + throw ex; + } catch (QueryException | DtoTranslationException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); // should always throw an exception + throw new SystemException("Exception " + ex + " was not handled correctly", ex); // ...so this shouldn't occur at all + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + LOGGER.trace("Session cleaned up."); + } + } + + public void returnUnusedValuesToSequenceAttempt(String oid, Collection unusedValues, OperationResult result) throws ObjectNotFoundException, + SchemaException, SerializationRelatedException { + + LOGGER.debug("Returning unused values of {} to a sequence with oid '{}'.", unusedValues, oid); + LOGGER_PERFORMANCE.debug("> return unused values, oid={}, values={}", oid, unusedValues); + + Session session = null; + try { + session = transactionHelper.beginTransaction(); + + PrismObject prismObject = objectRetriever.getObjectInternal(session, SequenceType.class, oid, null, true); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("OBJECT before:\n{}", prismObject.debugDump()); + } + SequenceType sequence = prismObject.asObjectable(); + int maxUnusedValues = sequence.getMaxUnusedValues() != null ? sequence.getMaxUnusedValues() : 0; + Iterator valuesToReturnIterator = unusedValues.iterator(); + while (valuesToReturnIterator.hasNext() && sequence.getUnusedValues().size() < maxUnusedValues) { + Long valueToReturn = valuesToReturnIterator.next(); + if (valueToReturn == null) { // sanity check + continue; + } + if (!sequence.getUnusedValues().contains(valueToReturn)) { + sequence.getUnusedValues().add(valueToReturn); + } else { + LOGGER.warn("UnusedValues in sequence {} already contains value of {} - ignoring the return request", oid, valueToReturn); + } + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("OBJECT after:\n{}", prismObject.debugDump()); + } + + // merge and update object + LOGGER.trace("Translating JAXB to data type."); + RObject rObject = objectUpdater.createDataObjectFromJAXB(prismObject, PrismIdentifierGenerator.Operation.MODIFY); + rObject.setVersion(rObject.getVersion() + 1); + + objectUpdater.updateFullObject(rObject, prismObject); + session.merge(rObject); + + LOGGER.trace("Before commit..."); + session.getTransaction().commit(); + LOGGER.trace("Committed!"); + } catch (ObjectNotFoundException ex) { + transactionHelper.rollbackTransaction(session, ex, result, true); + throw ex; + } catch (SchemaException ex) { + transactionHelper.rollbackTransaction(session, ex, result, true); + throw ex; + } catch (QueryException | DtoTranslationException | RuntimeException ex) { + transactionHelper.handleGeneralException(ex, session, result); // should always throw an exception + throw new SystemException("Exception " + ex + " was not handled correctly", ex); // ...so this shouldn't occur at all + } finally { + transactionHelper.cleanupSessionAndResult(session, result); + LOGGER.trace("Session cleaned up."); + } + } +} diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/TransactionHelper.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/TransactionHelper.java new file mode 100644 index 00000000000..8038cabef85 --- /dev/null +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/helpers/TransactionHelper.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2010-2015 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.helpers; + +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.sql.SqlRepositoryServiceImpl; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import org.hibernate.Session; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.sql.SQLException; + +/** + * @author mederly + */ +@Component +public class TransactionHelper { + + private static final Trace LOGGER = TraceManager.getTrace(SqlRepositoryServiceImpl.class); + private static final Trace LOGGER_PERFORMANCE = TraceManager.getTrace(SqlRepositoryServiceImpl.PERFORMANCE_LOG_NAME); + + @Autowired + @Qualifier("repositoryService") + private RepositoryService repositoryService; + + private SqlRepositoryServiceImpl getSqlRepo() { + return (SqlRepositoryServiceImpl) repositoryService; + } + + public Session beginReadOnlyTransaction() { + return beginTransaction(getSqlRepo().getConfiguration().isUseReadOnlyTransactions()); + } + + public Session beginTransaction() { + return beginTransaction(false); + } + + public Session beginTransaction(boolean readOnly) { + return getSqlRepo().beginTransaction(readOnly); + } + + public void rollbackTransaction(Session session, Exception ex, OperationResult result, boolean fatal) { + getSqlRepo().rollbackTransaction(session, ex, result, fatal); + } + + public void rollbackTransaction(Session session, Exception ex, String message, OperationResult result, boolean fatal) { + getSqlRepo().rollbackTransaction(session, ex, message, result, fatal); + } + + public void rollbackTransaction(Session session) { + getSqlRepo().rollbackTransaction(session); + } + + public void cleanupSessionAndResult(Session session, OperationResult result) { + getSqlRepo().cleanupSessionAndResult(session, result); + } + + public void handleGeneralException(Exception ex, Session session, OperationResult result) { + getSqlRepo().handleGeneralException(ex, session, result); + } + + public void handleGeneralRuntimeException(RuntimeException ex, Session session, OperationResult result) { + getSqlRepo().handleGeneralRuntimeException(ex, session, result); + } + + public void handleGeneralCheckedException(Exception ex, Session session, OperationResult result) { + getSqlRepo().handleGeneralCheckedException(ex, session, result); + } + + public SQLException findSqlException(Throwable ex) { + return getSqlRepo().findSqlException(ex); + } +} \ No newline at end of file diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query/QueryInterpreter.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query/QueryInterpreter.java index aff1ae533c0..72a9304c21e 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query/QueryInterpreter.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query/QueryInterpreter.java @@ -22,6 +22,7 @@ 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.repo.sql.ObjectPagingAfterOid; import com.evolveum.midpoint.repo.sql.SqlRepositoryConfiguration; import com.evolveum.midpoint.repo.sql.query.definition.Definition; import com.evolveum.midpoint.repo.sql.query.definition.EntityDefinition; @@ -52,8 +53,6 @@ import java.lang.reflect.Modifier; import java.util.*; -import static com.evolveum.midpoint.repo.sql.SqlRepositoryServiceImpl.*; - /** * @author lazyman */ diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/QueryInterpreter2.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/QueryInterpreter2.java index efaee739bce..230f89fb4ed 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/QueryInterpreter2.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/query2/QueryInterpreter2.java @@ -80,7 +80,7 @@ import java.util.HashMap; import java.util.Map; -import static com.evolveum.midpoint.repo.sql.SqlRepositoryServiceImpl.ObjectPagingAfterOid; +import com.evolveum.midpoint.repo.sql.ObjectPagingAfterOid; /** * Interprets midPoint queries by translating them to hibernate (HQL) ones.