alpine.persistence.AbstractAlpineQueryManager Maven / Gradle / Ivy
/*
* This file is part of Alpine.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) Steve Springett. All Rights Reserved.
*/
package alpine.persistence;
import alpine.common.logging.Logger;
import alpine.resources.AlpineRequest;
import alpine.common.validation.RegexSequence;
import io.jsonwebtoken.lang.Collections;
import org.apache.commons.collections4.CollectionUtils;
import org.datanucleus.api.jdo.JDOQuery;
import javax.jdo.FetchPlan;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import java.lang.reflect.Field;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.UUID;
/**
* Base persistence manager that implements AutoCloseable so that the PersistenceManager will
* be automatically closed when used in a try-with-resource block.
*
* @author Steve Springett
* @since 1.0.0
*/
public abstract class AbstractAlpineQueryManager implements AutoCloseable {
private static final Logger LOGGER = Logger.getLogger(AbstractAlpineQueryManager.class);
private static final ServiceLoader IpmfServiceLoader = ServiceLoader.load(IPersistenceManagerFactory.class);
protected final Principal principal;
protected Pagination pagination;
protected final String filter;
protected final String orderBy;
protected final OrderDirection orderDirection;
protected final PersistenceManager pm;
public static Optional getPersistenceManagerFactory() {
return IpmfServiceLoader.findFirst();
}
/**
* Specifies a non-default PersistenceManager to use.
* @param pm the JDO PersistenceManager to use
* @since 1.4.3
*/
public AbstractAlpineQueryManager(final PersistenceManager pm) {
this.pm = pm;
principal = null;
pagination = new Pagination(Pagination.Strategy.NONE, 0, 0);
filter = null;
orderBy = null;
orderDirection = OrderDirection.UNSPECIFIED;
}
/**
* Default constructor
*/
public AbstractAlpineQueryManager() {
final Optional ipmf = getPersistenceManagerFactory();
pm = (ipmf.isEmpty()) ? null : ipmf.get().getPersistenceManager();
principal = null;
pagination = new Pagination(Pagination.Strategy.NONE, 0, 0);
filter = null;
orderBy = null;
orderDirection = OrderDirection.UNSPECIFIED;
}
/**
* Constructs a new QueryManager with the following:
* @param principal a Principal, or null
* @param pagination a Pagination request, or null
* @param filter a String filter, or null
* @param orderBy the field to order by
* @param orderDirection the sorting direction
* @since 1.0.0
*/
public AbstractAlpineQueryManager(final Principal principal, final Pagination pagination, final String filter,
final String orderBy, final OrderDirection orderDirection) {
final Optional ipmf = getPersistenceManagerFactory();
pm = (ipmf.isEmpty()) ? null : ipmf.get().getPersistenceManager();
this.principal = principal;
this.pagination = pagination;
this.filter = filter;
this.orderBy = orderBy;
this.orderDirection = orderDirection;
}
/**
* Constructs a new QueryManager. Deconstructs the specified AlpineRequest
* into its individual components including pagination and ordering.
* @param request an AlpineRequest object
* @since 1.0.0
*/
public AbstractAlpineQueryManager(final AlpineRequest request) {
final Optional ipmf = getPersistenceManagerFactory();
pm = (ipmf.isEmpty()) ? null : ipmf.get().getPersistenceManager();
this.principal = request.getPrincipal();
this.pagination = request.getPagination();
this.filter = request.getFilter();
this.orderBy = request.getOrderBy();
this.orderDirection = request.getOrderDirection();
}
/**
* Constructs a new QueryManager. Deconstructs the specified AlpineRequest
* into its individual components including pagination and ordering.
* @param pm the JDO PersistenceManager to use
* @param request an AlpineRequest object
* @since 1.9.3
*/
public AbstractAlpineQueryManager(final PersistenceManager pm, final AlpineRequest request) {
this.pm = pm;
this.principal = request.getPrincipal();
this.pagination = request.getPagination();
this.filter = request.getFilter();
this.orderBy = request.getOrderBy();
this.orderDirection = request.getOrderDirection();
}
/**
* Wrapper around {@link Query#execute()} that adds transparent support for
* pagination and ordering of results via {@link #decorate(Query)}.
* @param query the JDO Query object to execute
* @return a PaginatedResult object
* @since 1.0.0
*/
public PaginatedResult execute(final Query query) {
final long count = getCount(query);
decorate(query);
return new PaginatedResult()
.objects(query.execute())
.total(count);
}
/**
* Wrapper around {@link Query#execute(Object)} that adds transparent support for
* pagination and ordering of results via {@link #decorate(Query)}.
* @param query the JDO Query object to execute
* @param p1 the value of the first parameter declared.
* @return a PaginatedResult object
* @since 1.0.0
*/
public PaginatedResult execute(final Query query, final Object p1) {
final long count = getCount(query, p1);
decorate(query);
return new PaginatedResult()
.objects(query.execute(p1))
.total(count);
}
/**
* Wrapper around {@link Query#execute(Object, Object)} that adds transparent support for
* pagination and ordering of results via {@link #decorate(Query)}.
* @param query the JDO Query object to execute
* @param p1 the value of the first parameter declared.
* @param p2 the value of the second parameter declared.
* @return a PaginatedResult object
* @since 1.0.0
*/
public PaginatedResult execute(final Query query, final Object p1, final Object p2) {
final long count = getCount(query, p1, p2);
decorate(query);
return new PaginatedResult()
.objects(query.execute(p1, p2))
.total(count);
}
/**
* Wrapper around {@link Query#execute(Object, Object, Object)} that adds transparent support for
* pagination and ordering of results via {@link #decorate(Query)}.
* @param query the JDO Query object to execute
* @param p1 the value of the first parameter declared.
* @param p2 the value of the second parameter declared.
* @param p3 the value of the third parameter declared.
* @return a PaginatedResult object
* @since 1.0.0
*/
public PaginatedResult execute(final Query query, final Object p1, final Object p2, final Object p3) {
final long count = getCount(query, p1, p2, p3);
decorate(query);
return new PaginatedResult()
.objects(query.execute(p1, p2, p3))
.total(count);
}
/**
* Wrapper around {@link Query#executeWithArray(Object...)} that adds transparent support for
* pagination and ordering of results via {@link #decorate(Query)}.
* @param query the JDO Query object to execute
* @param parameters the Object
array with all of the parameters
* @return a PaginatedResult object
* @since 1.0.0
*/
public PaginatedResult execute(final Query query, final Object... parameters) {
final long count = getCount(query, parameters);
decorate(query);
return new PaginatedResult()
.objects(query.executeWithArray(parameters))
.total(count);
}
/**
* Wrapper around {@link Query#executeWithMap(Map)} that adds transparent support for
* pagination and ordering of results via {@link #decorate(Query)}.
* @param query the JDO Query object to execute
* @param parameters the Map
containing all of the parameters.
* @return a PaginatedResult object
* @since 1.0.0
*/
public PaginatedResult execute(final Query query, final Map parameters) {
final long count = getCount(query, parameters);
decorate(query);
return new PaginatedResult()
.objects(query.executeWithMap(parameters))
.total(count);
}
/**
* Advances the pagination based on the previous pagination settings. This is purely a
* convenience method as the method by itself is not aware of the query being executed,
* the result count, etc.
* @since 1.0.0
*/
public void advancePagination() {
if (pagination.isPaginated()) {
pagination = new Pagination(pagination.getStrategy(), pagination.getOffset() + pagination.getLimit(), pagination.getLimit());
}
}
/**
* Given a query, this method will decorate that query with pagination, ordering,
* and sorting direction. Specific checks are performed to ensure the execution
* of the query is capable of being paged and that ordering can be securely performed.
* @param query the JDO Query object to execute
* @return a Collection of objects
* @since 1.0.0
*/
public Query decorate(final Query query) {
// Clear the result to fetch if previously specified (i.e. by getting count)
query.setResult(null);
if (pagination != null && pagination.isPaginated()) {
final long begin = pagination.getOffset();
final long end = begin + pagination.getLimit();
query.setRange(begin, end);
}
if (orderBy != null && RegexSequence.Pattern.STRING_IDENTIFIER.matcher(orderBy).matches() && orderDirection != OrderDirection.UNSPECIFIED) {
// Check to see if the specified orderBy field is defined in the class being queried.
boolean found = false;
final org.datanucleus.store.query.Query iq = ((JDOQuery) query).getInternalQuery();
final String candidateField = orderBy.contains(".") ? orderBy.substring(0, orderBy.indexOf('.')) : orderBy;
for (final Field field: iq.getCandidateClass().getDeclaredFields()) {
if (candidateField.equals(field.getName())) {
found = true;
break;
}
}
if (found) {
query.setOrdering(orderBy + " " + orderDirection.name().toLowerCase());
}
}
return query;
}
/**
* Returns the number of items that would have resulted from returning all object.
* This method is performant in that the objects are not actually retrieved, only
* the count.
* @param query the query to return a count from
* @return the number of items
* @since 1.0.0
*/
public long getCount(final Query query) {
//query.addExtension("datanucleus.query.resultSizeMethod", "count");
final String ordering = ((JDOQuery) query).getInternalQuery().getOrdering();
query.setResult("count(id)");
query.setOrdering(null);
final long count = (Long) query.execute();
query.setOrdering(ordering);
return count;
}
/**
* Returns the number of items that would have resulted from returning all object.
* This method is performant in that the objects are not actually retrieved, only
* the count.
* @param query the query to return a count from
* @param p1 the value of the first parameter declared.
* @return the number of items
* @since 1.0.0
*/
public long getCount(final Query query, final Object p1) {
final String ordering = ((JDOQuery) query).getInternalQuery().getOrdering();
query.setResult("count(id)");
query.setOrdering(null);
final long count = (Long) query.execute(p1);
query.setOrdering(ordering);
return count;
}
/**
* Returns the number of items that would have resulted from returning all object.
* This method is performant in that the objects are not actually retrieved, only
* the count.
* @param query the query to return a count from
* @param p1 the value of the first parameter declared.
* @param p2 the value of the second parameter declared.
* @return the number of items
* @since 1.0.0
*/
public long getCount(final Query query, final Object p1, final Object p2) {
final String ordering = ((JDOQuery) query).getInternalQuery().getOrdering();
query.setResult("count(id)");
query.setOrdering(null);
final long count = (Long) query.execute(p1, p2);
query.setOrdering(ordering);
return count;
}
/**
* Returns the number of items that would have resulted from returning all object.
* This method is performant in that the objects are not actually retrieved, only
* the count.
* @param query the query to return a count from
* @param p1 the value of the first parameter declared.
* @param p2 the value of the second parameter declared.
* @param p3 the value of the third parameter declared.
* @return the number of items
* @since 1.0.0
*/
public long getCount(final Query query, final Object p1, final Object p2, final Object p3) {
final String ordering = ((JDOQuery) query).getInternalQuery().getOrdering();
query.setResult("count(id)");
query.setOrdering(null);
final long count = (Long) query.execute(p1, p2, p3);
query.setOrdering(ordering);
return count;
}
/**
* Returns the number of items that would have resulted from returning all object.
* This method is performant in that the objects are not actually retrieved, only
* the count.
* @param query the query to return a count from
* @param parameters the Object
array with all of the parameters
* @return the number of items
* @since 1.0.0
*/
public long getCount(final Query query, final Object... parameters) {
final String ordering = ((JDOQuery) query).getInternalQuery().getOrdering();
query.setResult("count(id)");
query.setOrdering(null);
final long count = (Long) query.executeWithArray(parameters);
query.setOrdering(ordering);
return count;
}
/**
* Returns the number of items that would have resulted from returning all object.
* This method is performant in that the objects are not actually retrieved, only
* the count.
* @param query the query to return a count from
* @param parameters the Map
containing all of the parameters.
* @return the number of items
* @since 1.0.0
*/
public long getCount(final Query query, final Map parameters) {
final String ordering = ((JDOQuery) query).getInternalQuery().getOrdering();
query.setResult("count(id)");
query.setOrdering(null);
final long count = (Long) query.executeWithMap(parameters);
query.setOrdering(ordering);
return count;
}
/**
* Returns the number of items that would have resulted from returning all object.
* This method is performant in that the objects are not actually retrieved, only
* the count.
* @param cls the persistence-capable class to query
* @return the number of items
* @param candidate type for the query
* @since 1.0.0
*/
public long getCount(final Class cls) {
final Query query = pm.newQuery(cls);
//query.addExtension("datanucleus.query.resultSizeMethod", "count");
query.setResult("count(id)");
return (Long) query.execute();
}
/**
* Persists the specified PersistenceCapable object.
* @param object a PersistenceCapable object
* @param the type to return
* @return the persisted object
*/
@SuppressWarnings("unchecked")
public T persist(T object) {
pm.currentTransaction().begin();
pm.makePersistent(object);
pm.currentTransaction().commit();
pm.getFetchPlan().setDetachmentOptions(FetchPlan.DETACH_LOAD_FIELDS);
pm.refresh(object);
return object;
}
/**
* Persists the specified PersistenceCapable objects.
* @param pcs an array of PersistenceCapable objects
* @param the type to return
* @return the persisted objects
*/
@SuppressWarnings("unchecked")
public T[] persist(T... pcs) {
pm.currentTransaction().begin();
pm.makePersistentAll(pcs);
pm.currentTransaction().commit();
pm.getFetchPlan().setDetachmentOptions(FetchPlan.DETACH_LOAD_FIELDS);
pm.refreshAll(pcs);
return pcs;
}
/**
* Persists the specified PersistenceCapable objects.
* @param pcs a collection of PersistenceCapable objects
* @param the type to return
* @return the persisted objects
*/
@SuppressWarnings("unchecked")
public Collection persist(Collection pcs) {
pm.currentTransaction().begin();
pm.makePersistentAll(pcs);
pm.currentTransaction().commit();
pm.getFetchPlan().setDetachmentOptions(FetchPlan.DETACH_LOAD_FIELDS);
pm.refreshAll(pcs);
return pcs;
}
/**
* Deletes one or more PersistenceCapable objects.
* @param objects an array of one or more objects to delete
* @since 1.0.0
*/
public void delete(Object... objects) {
pm.currentTransaction().begin();
pm.deletePersistentAll(objects);
pm.currentTransaction().commit();
}
/**
* Deletes one or more PersistenceCapable objects.
* @param collection a collection of one or more objects to delete
* @since 1.0.0
*/
public void delete(Collection collection) {
pm.currentTransaction().begin();
pm.deletePersistentAll(collection);
pm.currentTransaction().commit();
}
/**
* Refreshes and detaches an object by its ID.
* @param A type parameter. This type will be returned
* @param clazz the persistence class to retrive the ID for
* @param id the object id to retrieve
* @return an object of the specified type
* @since 1.3.0
*/
public T detach(Class clazz, Object id) {
pm.getFetchPlan().setDetachmentOptions(FetchPlan.DETACH_LOAD_FIELDS);
return pm.detachCopy(pm.getObjectById(clazz, id));
}
/**
* Refreshes and detaches an objects.
* @param pcs the instances to detach
* @param the type to return
* @return the detached instances
* @since 1.3.0
*/
public List detach(List pcs) {
pm.getFetchPlan().setDetachmentOptions(FetchPlan.DETACH_LOAD_FIELDS);
return new ArrayList<>(pm.detachCopyAll(pcs));
}
/**
* Refreshes and detaches an objects.
* @param pcs the instances to detach
* @param the type to return
* @return the detached instances
* @since 1.3.0
*/
public Set detach(Set pcs) {
pm.getFetchPlan().setDetachmentOptions(FetchPlan.DETACH_LOAD_FIELDS);
return new LinkedHashSet<>(pm.detachCopyAll(pcs));
}
/**
* Retrieves an object by its ID.
* @param A type parameter. This type will be returned
* @param clazz the persistence class to retrive the ID for
* @param id the object id to retrieve
* @return an object of the specified type
* @since 1.0.0
*/
public T getObjectById(Class clazz, Object id) {
return pm.getObjectById(clazz, id);
}
/**
* Retrieves an object by its UUID.
* @param A type parameter. This type will be returned
* @param clazz the persistence class to retrive the ID for
* @param uuid the uuid of the object to retrieve
* @return an object of the specified type
* @since 1.0.0
*/
@SuppressWarnings("unchecked")
public T getObjectByUuid(Class clazz, UUID uuid) {
final Query query = pm.newQuery(clazz, "uuid == :uuid");
final List result = (List) query.execute(uuid);
return Collections.isEmpty(result) ? null : result.get(0);
}
/**
* Retrieves an object by its UUID.
* @param A type parameter. This type will be returned
* @param clazz the persistence class to retrive the ID for
* @param uuid the uuid of the object to retrieve
* @return an object of the specified type
* @since 1.0.0
*/
@SuppressWarnings("unchecked")
public T getObjectByUuid(Class clazz, String uuid) {
return getObjectByUuid(clazz, UUID.fromString(uuid));
}
/**
* Retrieves an object by its UUID.
* @param A type parameter. This type will be returned
* @param clazz the persistence class to retrive the ID for
* @param uuid the uuid of the object to retrieve
* @param fetchGroup the JDO fetchgroup to use when making the query
* @return an object of the specified type
* @since 1.0.0
*/
@SuppressWarnings("unchecked")
public T getObjectByUuid(Class clazz, UUID uuid, String fetchGroup) {
pm.getFetchPlan().addGroup(fetchGroup);
return getObjectByUuid(clazz, uuid);
}
/**
* Retrieves an object by its UUID.
* @param A type parameter. This type will be returned
* @param clazz the persistence class to retrive the ID for
* @param uuid the uuid of the object to retrieve
* @param fetchGroup the JDO fetchgroup to use when making the query
* @return an object of the specified type
* @since 1.0.0
*/
@SuppressWarnings("unchecked")
public T getObjectByUuid(Class clazz, String uuid, String fetchGroup) {
return getObjectByUuid(clazz, UUID.fromString(uuid), fetchGroup);
}
/**
* Used to return the first record in a collection. This method is intended to be used
* to wrap {@link Query#execute()} and its derivatives.
* @param object a collection object (or anything that extends collection)
* @param the type of object returned, or null if object was null, not a collection, or collection was empty
* @return A single results
* @since 1.4.4
*/
@SuppressWarnings("unchecked")
public T singleResult(Object object) {
if (object == null) {
return null;
}
if (object instanceof Collection) {
final Collection result = (Collection)object;
return CollectionUtils.isEmpty(result) ? null : result.iterator().next();
}
return null;
}
/**
* Closes the PersistenceManager instance.
* @since 1.0.0
*/
public void close() {
if (pm != null) {
pm.close();
}
}
public PersistenceManager getPersistenceManager() {
return pm;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy