/*
* 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.crypto.CryptoUtil;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.marshaller.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.QueryException;
import com.evolveum.midpoint.repo.sql.query.RQuery;
import com.evolveum.midpoint.repo.sql.query2.QueryEngine2;
import com.evolveum.midpoint.repo.sql.query2.RQueryImpl;
import com.evolveum.midpoint.repo.sql.query2.hqm.QueryParameterValue;
import com.evolveum.midpoint.repo.sql.util.*;
import com.evolveum.midpoint.schema.*;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.util.Holder;
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.*;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.hibernate.*;
import org.hibernate.criterion.Restrictions;
import org.jetbrains.annotations.NotNull;
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.*;
/**
* @author lazyman, mederly
*/
@Component
public class ObjectRetriever {
public static final String CLASS_DOT = ObjectRetriever.class.getName() + ".";
public static final String OPERATION_GET_OBJECT_INTERNAL = CLASS_DOT + "getObjectInternal";
private static final Trace LOGGER = TraceManager.getTrace(ObjectRetriever.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 BaseHelper baseHelper;
@Autowired
private NameResolutionHelper nameResolutionHelper;
@Autowired
private PrismContext prismContext;
public <T extends ObjectType> PrismObject<T> getObjectAttempt(Class<T> type, String oid,
Collection<SelectorOptions<GetOperationOptions>> options,
OperationResult result)
throws ObjectNotFoundException, SchemaException {
LOGGER_PERFORMANCE.debug("> get object {}, oid={}", type.getSimpleName(), oid);
PrismObject<T> objectType = null;
Session session = null;
try {
session = baseHelper.beginReadOnlyTransaction();
objectType = getObjectInternal(session, type, oid, options, false, result);
session.getTransaction().commit();
} catch (ObjectNotFoundException ex) {
GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options);
baseHelper.rollbackTransaction(session, ex, result, !GetOperationOptions.isAllowNotFound(rootOptions));
throw ex;
} catch (SchemaException ex) {
baseHelper.rollbackTransaction(session, ex, "Schema error while getting object with oid: "
+ oid + ". Reason: " + ex.getMessage(), result, true);
throw ex;
} catch (DtoTranslationException | RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, result);
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Get object:\n{}", new Object[]{(objectType != null ? objectType.debugDump(3) : null)});
}
return objectType;
}
public <T extends ObjectType> PrismObject<T> getObjectInternal(Session session, Class<T> type, String oid,
Collection<SelectorOptions<GetOperationOptions>> options,
boolean lockForUpdate, OperationResult operationResult)
throws ObjectNotFoundException, SchemaException, DtoTranslationException {
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()) {
LOGGER.trace("Trying to lock object {} for update (via SQL)", oid);
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 {} ms)", System.currentTimeMillis() - time);
}
lockedForUpdateViaSql = true;
}
}
if (LOGGER.isTraceEnabled()) {
if (lockedForUpdateViaHibernate) {
LOGGER.trace("Getting object {} with locking for update (via hibernate)", oid);
} else if (lockedForUpdateViaSql) {
LOGGER.trace("Getting object {}, already locked for update (via SQL)", oid);
} else {
LOGGER.trace("Getting object {} without locking for update", oid);
}
}
GetObjectResult fullObject = null;
if (!lockForUpdate) {
Query query = session.getNamedQuery("get.object");
query.setString("oid", oid);
query.setResultTransformer(GetObjectResult.RESULT_STYLE.getResultTransformer());
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) {
fullObject = new GetObjectResult(obj.getOid(), 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<T> prismObject = updateLoadedObject(fullObject, type, oid, options, null, session, operationResult);
validateObjectType(prismObject, type);
// this was implemented to allow report parsing errors as warnings to upper layers;
// however, it causes problems when serialization problems are encountered: in such cases, we put
// FATAL_ERROR to the result here, and it should be then removed or muted (which is a complication)
// -- so, as the parsing errors are not implemented, we disabled this code as well
// subResult.computeStatusIfUnknown();
// if (subResult.isWarning() || subResult.isError() || subResult.isInProgress()) {
// prismObject.asObjectable().setFetchResult(subResult.createOperationResultType());
// }
return prismObject;
}
protected SqlRepositoryConfiguration getConfiguration() {
return baseHelper.getConfiguration();
}
private <T extends ObjectType> PrismObject<T> throwObjectNotFoundException(Class<T> type, String oid)
throws ObjectNotFoundException {
throw new ObjectNotFoundException("Object of type '" + type.getSimpleName() + "' with oid '" + oid
+ "' was not found.", null, oid);
}
public <F extends FocusType> PrismObject<F> searchShadowOwnerAttempt(String shadowOid, Collection<SelectorOptions<GetOperationOptions>> options, OperationResult result) {
LOGGER_PERFORMANCE.debug("> search shadow owner for oid={}", shadowOid);
PrismObject<F> owner = null;
Session session = null;
try {
session = baseHelper.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_STYLE.getResultTransformer());
List<GetObjectResult> focuses = query.list();
LOGGER.trace("Found {} focuses, transforming data to JAXB types.", 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<F>) FocusType.class, null, options, null, session, result);
session.getTransaction().commit();
} catch (SchemaException | RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, result);
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
return owner;
}
public PrismObject<UserType> listAccountShadowOwnerAttempt(String accountOid, OperationResult result)
throws ObjectNotFoundException {
LOGGER_PERFORMANCE.debug("> list account shadow owner oid={}", accountOid);
PrismObject<UserType> userType = null;
Session session = null;
try {
session = baseHelper.beginReadOnlyTransaction();
Query query = session.getNamedQuery("listAccountShadowOwner.getUser");
query.setString("oid", accountOid);
query.setResultTransformer(GetObjectResult.RESULT_STYLE.getResultTransformer());
List<GetObjectResult> users = query.list();
LOGGER.trace("Found {} users, transforming data to JAXB types.", 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, null, null, session, result);
session.getTransaction().commit();
} catch (SchemaException | RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, result);
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
return userType;
}
public <T extends ObjectType> int countObjectsAttempt(Class<T> type, ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options, OperationResult result) {
LOGGER_PERFORMANCE.debug("> count objects {}", type.getSimpleName());
int count = 0;
Session session = null;
try {
Class<? extends RObject> hqlType = ClassMapper.getHQLTypeClass(type);
session = baseHelper.beginReadOnlyTransaction();
Number longCount;
if (query == null || query.getFilter() == null) {
if (GetOperationOptions.isDistinct(SelectorOptions.findRootOptions(options))) {
throw new UnsupportedOperationException("Distinct option is not supported here"); // TODO
}
// 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;
QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext);
rQuery = engine.interpret(query, type, options, true, session);
longCount = (Number) rQuery.uniqueResult();
}
LOGGER.trace("Found {} objects.", longCount);
count = longCount != null ? longCount.intValue() : 0;
session.getTransaction().commit();
} catch (QueryException | RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, result);
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
return count;
}
@NotNull
public <T extends ObjectType> SearchResultList<PrismObject<T>> searchObjectsAttempt(Class<T> type, ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options,
OperationResult result) throws SchemaException {
LOGGER_PERFORMANCE.debug("> search objects {}", new Object[]{type.getSimpleName()});
Session session = null;
try {
session = baseHelper.beginReadOnlyTransaction();
RQuery rQuery;
QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext);
rQuery = engine.interpret(query, type, options, false, session);
List<GetObjectResult> queryResult = rQuery.list();
LOGGER.trace("Found {} objects, translating to JAXB.", queryResult != null ? queryResult.size() : 0);
List<PrismObject<T>> list = queryResultToPrismObjects(queryResult, type, options, session, result);
session.getTransaction().commit();
return new SearchResultList<>(list);
} catch (QueryException | RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, result);
throw new IllegalStateException("shouldn't get here");
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
}
@NotNull
private <T extends ObjectType> List<PrismObject<T>> queryResultToPrismObjects(List<GetObjectResult> objects, Class<T> type,
Collection<SelectorOptions<GetOperationOptions>> options,
Session session, OperationResult result) throws SchemaException {
List<PrismObject<T>> rv = new ArrayList<>();
if (objects != null) {
for (GetObjectResult object : objects) {
String oid = object.getOid();
Holder<PrismObject<T>> partialValueHolder = new Holder<>();
PrismObject<T> prismObject;
try {
prismObject = updateLoadedObject(object, type, oid, options, partialValueHolder, session, result);
} catch (Throwable t) {
if (!partialValueHolder.isEmpty()) {
prismObject = partialValueHolder.getValue();
} else {
prismObject = prismContext.createObject(type);
prismObject.setOid(oid);
prismObject.asObjectable().setName(PolyStringType.fromOrig("Unreadable object"));
}
result.recordFatalError("Couldn't retrieve " + type + " " + oid + ": " + t.getMessage(), t);
prismObject.asObjectable().setFetchResult(result.createOperationResultType());
}
rv.add(prismObject);
}
}
return rv;
}
public <C extends Containerable> SearchResultList<C> searchContainersAttempt(Class<C> type, ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options, OperationResult result) throws SchemaException {
boolean cases = AccessCertificationCaseType.class.equals(type);
boolean workItems = AccessCertificationWorkItemType.class.equals(type);
if (!cases && !workItems) {
throw new UnsupportedOperationException("Only AccessCertificationCaseType or AccessCertificationWorkItemType is supported here now.");
}
LOGGER_PERFORMANCE.debug("> search containers {}", type.getSimpleName());
List<C> list = new ArrayList<>();
Session session = null;
try {
session = baseHelper.beginReadOnlyTransaction();
QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext);
RQuery rQuery = engine.interpret(query, type, options, false, session);
if (cases) {
List<GetContainerableResult> items = rQuery.list();
LOGGER.trace("Found {} items (cases), translating to JAXB.", items.size());
Map<String,PrismObject<AccessCertificationCampaignType>> campaignsCache = new HashMap<>();
for (GetContainerableResult item : items) {
@SuppressWarnings({ "raw", "unchecked" })
C value = (C) caseHelper.updateLoadedCertificationCase(item, campaignsCache, options, session, result);
list.add(value);
}
} else {
assert workItems;
List<GetCertificationWorkItemResult> items = rQuery.list();
LOGGER.trace("Found {} work items, translating to JAXB.", items.size());
Map<String,PrismContainerValue<AccessCertificationCaseType>> casesCache = new HashMap<>();
Map<String,PrismObject<AccessCertificationCampaignType>> campaignsCache = new HashMap<>();
for (GetCertificationWorkItemResult item : items) {
//LOGGER.trace("- {}", item);
@SuppressWarnings({ "raw", "unchecked" })
C value = (C) caseHelper.updateLoadedCertificationWorkItem(item, casesCache, campaignsCache, options, engine, session, result);
list.add(value);
}
}
nameResolutionHelper.resolveNamesIfRequested(session, PrismContainerValue.asPrismContainerValues(list), options);
session.getTransaction().commit();
} catch (QueryException | RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, result);
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
list.forEach(c -> ObjectTypeUtil.normalizeAllRelations(c.asPrismContainerValue()));
return new SearchResultList<>(list);
}
/**
* This method provides object parsing from String and validation.
*/
private <T extends ObjectType> PrismObject<T> updateLoadedObject(GetObjectResult result, Class<T> type,
String oid, Collection<SelectorOptions<GetOperationOptions>> options,
Holder<PrismObject<T>> partialValueHolder,
Session session, OperationResult operationResult) throws SchemaException {
String xml = RUtil.getXmlFromByteArray(result.getFullObject(), getConfiguration().isUseZip());
PrismObject<T> prismObject;
try {
// "Postel mode": be tolerant what you read. We need this to tolerate (custom) schema changes
ParsingContext parsingContext = ParsingContext.forMode(XNodeProcessorEvaluationMode.COMPAT);
prismObject = prismContext.parserFor(xml).context(parsingContext).parse();
// TODO enable if needed
// if (parsingContext.hasWarnings()) {
// for (String warning : parsingContext.getWarnings()) {
// operationResult.createSubresult("parseObject").recordWarning(warning);
// }
// }
} catch (SchemaException | RuntimeException | Error e) {
// This is a serious thing. We have corrupted XML in the repo. This may happen even
// during system init. We want really loud and detailed error here.
LOGGER.error("Couldn't parse object {} {}: {}: {}\n{}",
type.getSimpleName(), oid, e.getClass().getName(), e.getMessage(), xml, e);
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);
}
if (partialValueHolder != null) {
partialValueHolder.setValue(prismObject);
}
nameResolutionHelper.resolveNamesIfRequested(session, prismObject.getValue(), options);
validateObjectType(prismObject, type);
ObjectTypeUtil.normalizeAllRelations(prismObject);
return prismObject;
}
private void applyShadowAttributeDefinitions(Class<? extends RAnyValue> 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<Object[]> 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<Object> def = new PrismPropertyDefinitionImpl<Object>(name, type, object.getPrismContext());
item.applyDefinition(def, true);
} else if (rValType == RValueType.REFERENCE) {
PrismReferenceDefinition def = new PrismReferenceDefinitionImpl(name, type, object.getPrismContext());
item.applyDefinition(def, true);
} else {
throw new UnsupportedOperationException("Unsupported value type " + rValType);
}
}
}
}
public <T extends ShadowType> List<PrismObject<T>> listResourceObjectShadowsAttempt(
String resourceOid, Class<T> resourceObjectShadowType, OperationResult result)
throws ObjectNotFoundException, SchemaException {
LOGGER_PERFORMANCE.debug("> list resource object shadows {}, for resource oid={}",
new Object[]{resourceObjectShadowType.getSimpleName(), resourceOid});
List<PrismObject<T>> list = new ArrayList<>();
Session session = null;
try {
session = baseHelper.beginReadOnlyTransaction();
Query query = session.getNamedQuery("listResourceObjectShadows");
query.setString("oid", resourceOid);
query.setResultTransformer(GetObjectResult.RESULT_STYLE.getResultTransformer());
List<GetObjectResult> shadows = query.list();
LOGGER.debug("Query returned {} shadows, transforming to JAXB types.", shadows != null ? shadows.size() : 0);
if (shadows != null) {
for (GetObjectResult shadow : shadows) {
PrismObject<T> prismObject = updateLoadedObject(shadow, resourceObjectShadowType, null, null, null, session, result);
list.add(prismObject);
}
}
session.getTransaction().commit();
} catch (SchemaException | RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, result);
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
return list;
}
private <T extends ObjectType> void validateObjectType(PrismObject<T> prismObject, Class<T> 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 <T extends ObjectType> String getVersionAttempt(Class<T> 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 = baseHelper.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();
session.getTransaction().commit();
} catch (RuntimeException ex) {
baseHelper.handleGeneralRuntimeException(ex, session, result);
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
return version;
}
public <T extends ObjectType> void searchObjectsIterativeAttempt(Class<T> type, ObjectQuery query,
ResultHandler<T> handler,
Collection<SelectorOptions<GetOperationOptions>> options,
OperationResult result) throws SchemaException {
Session session = null;
try {
session = baseHelper.beginReadOnlyTransaction();
RQuery rQuery;
QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext);
rQuery = engine.interpret(query, type, options, false, session);
ScrollableResults results = rQuery.scroll(ScrollMode.FORWARD_ONLY);
try {
Iterator<GetObjectResult> iterator = new ScrollableResultsIterator(results);
while (iterator.hasNext()) {
GetObjectResult object = iterator.next();
// TODO treat exceptions encountered within the next call
PrismObject<T> prismObject = updateLoadedObject(object, type, null, options, null, session, result);
if (!handler.handle(prismObject, result)) {
break;
}
}
} finally {
if (results != null) {
results.close();
}
}
session.getTransaction().commit();
} catch (SchemaException | QueryException | RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, result);
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
}
public <T extends ObjectType> void searchObjectsIterativeByPaging(Class<T> type, ObjectQuery query,
ResultHandler<T> handler,
Collection<SelectorOptions<GetOperationOptions>> 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<PrismObject<T>> objects = repositoryService.searchObjects(type, pagedQuery, options, result);
for (PrismObject<T> 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();
}
result.setSummarizeSuccesses(true);
result.summarize();
}
}
/**
* 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 <T extends ObjectType> void searchObjectsIterativeByPagingStrictlySequential(
Class<T> type, ObjectQuery query, ResultHandler<T> handler,
Collection<SelectorOptions<GetOperationOptions>> 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<PrismObject<T>> objects = repositoryService.searchObjects(type, pagedQuery, options, result);
for (PrismObject<T> 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<String> lowerObjectOids) {
Session session = null;
try {
session = baseHelper.beginReadOnlyTransaction();
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();
session.getTransaction().commit();
return number != null && number.longValue() != 0L;
} catch (RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, null);
} finally {
baseHelper.cleanupSessionAndResult(session, null);
}
throw new SystemException("isAnySubordinateAttempt failed somehow, this really should not happen.");
}
public RepositoryQueryDiagResponse executeQueryDiagnosticsRequest(RepositoryQueryDiagRequest request, OperationResult result) {
LOGGER_PERFORMANCE.debug("> execute query diagnostics {}", request);
Session session = null;
try {
session = baseHelper.beginReadOnlyTransaction(); // beware, not all databases support read-only transactions!
final String implementationLevelQuery;
final Map<String, RepositoryQueryDiagResponse.ParameterValue> implementationLevelQueryParameters;
final Query query;
final boolean isMidpointQuery = request.getImplementationLevelQuery() == null;
if (isMidpointQuery) {
QueryEngine2 engine = new QueryEngine2(getConfiguration(), prismContext);
RQueryImpl rQuery = (RQueryImpl) engine.interpret(request.getQuery(), request.getType(), null, false, session);
query = rQuery.getQuery();
implementationLevelQuery = query.getQueryString();
implementationLevelQueryParameters = new HashMap<>();
for (Map.Entry<String, QueryParameterValue> entry : rQuery.getQuerySource().getParameters().entrySet()) {
implementationLevelQueryParameters.put(entry.getKey(),
new RepositoryQueryDiagResponse.ParameterValue(entry.getValue().getValue(), entry.getValue().toString()));
}
} else {
implementationLevelQuery = (String) request.getImplementationLevelQuery();
implementationLevelQueryParameters = new HashMap<>();
query = session.createQuery(implementationLevelQuery);
}
List<?> objects = request.isTranslateOnly() ? null : query.list();
if (isMidpointQuery && objects != null) {
// raw GetObjectResult instances are useless outside repo-sql-impl module, so we'll convert them to objects
@SuppressWarnings("unchecked")
List<GetObjectResult> listOfGetObjectResults = (List<GetObjectResult>) objects;
objects = queryResultToPrismObjects(listOfGetObjectResults, request.getType(), null, session, result);
}
RepositoryQueryDiagResponse response = new RepositoryQueryDiagResponse(objects, implementationLevelQuery, implementationLevelQueryParameters);
session.getTransaction().rollback();
return response;
} catch (SchemaException | QueryException | RuntimeException ex) {
baseHelper.handleGeneralException(ex, session, result);
throw new IllegalStateException("shouldn't get here");
} finally {
baseHelper.cleanupSessionAndResult(session, result);
}
}
}