/* Copyright 2011-2014 Red Hat, Inc This file is part of PressGang CCMS. PressGang CCMS is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PressGang CCMS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PressGang CCMS. If not, see <http://www.gnu.org/licenses/>. */ package org.jboss.pressgang.ccms.server.rest.v1.factory.base; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaQuery; import java.util.List; import org.jboss.pressgang.ccms.filter.utils.JPAUtils; import org.jboss.pressgang.ccms.model.base.AuditedEntity; import org.jboss.pressgang.ccms.model.base.PressGangEntity; import org.jboss.pressgang.ccms.rest.v1.collections.base.RESTBaseEntityCollectionItemV1; import org.jboss.pressgang.ccms.rest.v1.collections.base.RESTBaseEntityCollectionV1; import org.jboss.pressgang.ccms.rest.v1.entities.base.RESTBaseEntityV1; import org.jboss.pressgang.ccms.rest.v1.expansion.ExpandDataDetails; import org.jboss.pressgang.ccms.rest.v1.expansion.ExpandDataTrunk; import org.jboss.pressgang.ccms.server.utils.EnversUtilities; import org.jboss.resteasy.spi.InternalServerErrorException; /** * A factory used to create collections of REST entity objects */ public class RESTEntityCollectionFactory { public static <T extends RESTBaseEntityV1<T>, U extends PressGangEntity, V extends RESTBaseEntityCollectionV1<T, V, W>, W extends RESTBaseEntityCollectionItemV1<T, V, W>> V create( final Class<V> clazz, final RESTEntityFactory<T, U, V, W> dataObjectFactory, final List<U> entities, final String expandName, final String dataType, final ExpandDataTrunk parentExpand, final String baseUrl, final EntityManager entityManager) { return create(clazz, dataObjectFactory, entities, null, null, null, expandName, dataType, parentExpand, baseUrl, true, entityManager); } public static <T extends RESTBaseEntityV1<T>, U extends PressGangEntity, V extends RESTBaseEntityCollectionV1<T, V, W>, W extends RESTBaseEntityCollectionItemV1<T, V, W>> V create( final Class<V> clazz, final RESTEntityFactory<T, U, V, W> dataObjectFactory, final List<U> entities, final String expandName, final String dataType, final ExpandDataTrunk parentExpand, final String baseUrl, final Number revision, final EntityManager entityManager) { return create(clazz, dataObjectFactory, entities, null, revision, null, expandName, dataType, parentExpand, baseUrl, true, entityManager); } public static <T extends RESTBaseEntityV1<T>, U extends PressGangEntity, V extends RESTBaseEntityCollectionV1<T, V, W>, W extends RESTBaseEntityCollectionItemV1<T, V, W>> V create( final Class<V> clazz, final RESTEntityFactory<T, U, V, W> dataObjectFactory, final U parent, final List<Number> revisions, final String expandName, final String dataType, final ExpandDataTrunk parentExpand, final String baseUrl, final EntityManager entityManager) { return create(clazz, dataObjectFactory, null, parent, null, revisions, expandName, dataType, parentExpand, baseUrl, true, entityManager); } /** * Create a Collection of REST Entities from a collection of Database Entities. * * @param clazz The Class of the Collection Object that should be returned by the method. * @param dataObjectFactory The factory to convert the database entity to a REST entity * @param parent The parent from which to find previous versions * @param revisions A list of Envers revision numbers that we want to add to the collection * @param expandName The name of the collection that we are working with * @param dataType The type of data that is returned through the REST interface * @param parentExpand The parent objects expansion details * @param baseUrl The base of the url that was used to access this collection * @param revision The revision number of the Parent entity, if it's not the latest version. * @param entityManager The EntityManager being used to provide data for the collection. * @return A REST collection from a collection of database entities. */ public static <T extends RESTBaseEntityV1<T>, U extends PressGangEntity, V extends RESTBaseEntityCollectionV1<T, V, W>, W extends RESTBaseEntityCollectionItemV1<T, V, W>> V create( final Class<V> clazz, final RESTEntityFactory<T, U, V, W> dataObjectFactory, final U parent, final List<Number> revisions, final String expandName, final String dataType, final ExpandDataTrunk parentExpand, final String baseUrl, final Number revision, final EntityManager entityManager) { return create(clazz, dataObjectFactory, null, parent, revision, revisions, expandName, dataType, parentExpand, baseUrl, true, entityManager); } /** * Create a Collection of REST Entities from a collection of Database Entities. * * @param clazz The Class of the Collection Object that should be returned by the method. * @param dataObjectFactory The factory to convert the database entity to a REST entity * @param entities A collection of numbers mapped to database entities. If isRevsionMap is true, these numbers are envers * revision numbers. If isRevsionMap is false, these numbers have no meaning. * @param expandName The name of the collection that we are working with * @param dataType The type of data that is returned through the REST interface * @param parentExpand The parent objects expansion details * @param baseUrl The base of the url that was used to access this collection * @param expandParentReferences If any Parent references in entities should be expanded. * @param entityManager The EntityManager being used to provide data for the collection. * @return a REST collection from a collection of database entities. */ public static <T extends RESTBaseEntityV1<T>, U extends PressGangEntity, V extends RESTBaseEntityCollectionV1<T, V, W>, W extends RESTBaseEntityCollectionItemV1<T, V, W>> V create( final Class<V> clazz, final RESTEntityFactory<T, U, V, W> dataObjectFactory, final List<U> entities, final String expandName, final String dataType, final ExpandDataTrunk parentExpand, final String baseUrl, final boolean expandParentReferences, final EntityManager entityManager) { return create(clazz, dataObjectFactory, entities, null, null, null, expandName, dataType, parentExpand, baseUrl, expandParentReferences, entityManager); } /** * Create a Collection of REST Entities from a collection of Database Entities. * * @param clazz The Class of the Collection Object that should be returned by the method. * @param dataObjectFactory The factory to convert the database entity to a REST entity * @param entities A collection of numbers mapped to database entities. If isRevsionMap is true, these numbers are envers * revision numbers. If isRevsionMap is false, these numbers have no meaning. * @param expandName The name of the collection that we are working with * @param dataType The type of data that is returned through the REST interface * @param parentExpand The parent objects expansion details * @param baseUrl The base of the url that was used to access this collection * @param revision The revision number of the Parent entity, if it's not the latest version. * @param expandParentReferences If any Parent references in entities should be expanded. * @param entityManager The EntityManager being used to provide data for the collection. * @return a REST collection from a collection of database entities. */ public static <T extends RESTBaseEntityV1<T>, U extends PressGangEntity, V extends RESTBaseEntityCollectionV1<T, V, W>, W extends RESTBaseEntityCollectionItemV1<T, V, W>> V create( final Class<V> clazz, final RESTEntityFactory<T, U, V, W> dataObjectFactory, final List<U> entities, final String expandName, final String dataType, final ExpandDataTrunk parentExpand, final String baseUrl, final Number revision, final boolean expandParentReferences, final EntityManager entityManager) { return create(clazz, dataObjectFactory, entities, null, revision, null, expandName, dataType, parentExpand, baseUrl, expandParentReferences, entityManager); } /** * Create a Collection of REST Entities from a collection of Database Entities. * * @param clazz The Class of the Collection Object that should be returned by the method. * @param dataObjectFactory The factory to convert the database entity to a REST entity * @param entities A collection of numbers mapped to database entities. If isRevsionMap is true, these numbers are envers * revision numbers. If isRevsionMap is false, these numbers have no meaning. * @param parent The parent from which to find previous versions * @param parentRevision The revision number of the Parent entity, if it's not the latest version. * @param revisions A list of Envers revision numbers that we want to add to the collection * @param expandName The name of the collection that we are working with * @param dataType The type of data that is returned through the REST interface * @param parentExpand The parent objects expansion details * @param baseUrl The base of the url that was used to access this collection * @param expandParentReferences If any Parent references in entities should be expanded. * @param entityManager The EntityManager being used to provide data for the collection. * @return a REST collection from a collection of database entities. */ public static <T extends RESTBaseEntityV1<T>, U extends PressGangEntity, V extends RESTBaseEntityCollectionV1<T, V, W>, W extends RESTBaseEntityCollectionItemV1<T, V, W>> V create( final Class<V> clazz, final RESTEntityFactory<T, U, V, W> dataObjectFactory, final List<U> entities, final U parent, final Number parentRevision, final List<Number> revisions, final String expandName, final String dataType, final ExpandDataTrunk parentExpand, final String baseUrl, final boolean expandParentReferences, final EntityManager entityManager) { V retValue = null; try { retValue = clazz.newInstance(); } catch (final Exception ex) { throw new InternalServerErrorException(ex); } /* * either the entities collection needs to be set, or the revisions and parent */ if (!(entities != null || (revisions != null && parent != null))) return retValue; final boolean usingRevisions = entities == null; retValue.setExpand(expandName); /* * Un-comment this to make it so that size is always shown. Don't forget to comment out line 154 below. */ // retValue.setSize(usingRevisions ? revisions.size() : entities.size()); final ExpandDataTrunk expand = parentExpand != null ? parentExpand.get(expandName) : null; if (expand != null) { if (expand.getTrunk().getName().equals(expandName)) { assert baseUrl != null : "Parameter baseUrl can not be null if parameter expand is not null"; final ExpandDataDetails indexes = expand.getTrunk(); /* * Un-comment this to make it so that size is only shown when the expand is set. Don't forget to comment out * line 136 above. */ final int size = usingRevisions ? revisions.size() : entities.size(); retValue.setSize(size); // Find the start reference for the response using the start and size of the entity list. int start = 0; if (indexes.getStart() != null) { final int startIndex = indexes.getStart(); if (startIndex < 0) { start = Math.max(0, size + startIndex); } else { start = Math.min(startIndex, size == 0 ? 0 : size - 1); } } // Find the end reference for the response using the start and size of the entity list. int end = size; if (indexes.getEnd() != null) { final int endIndex = indexes.getEnd(); if (endIndex < 0) { end = Math.max(0, size + endIndex + 1); } else { end = Math.min(endIndex, size); } } // Fix the start and end if the two overlap final int fixedStart = Math.min(start, end); final int fixedEnd = Math.max(start, end); retValue.setStartExpandIndex(fixedStart); retValue.setEndExpandIndex(fixedEnd); // Get the entities requested from the entity list and create the REST Entities. for (int i = fixedStart; i < fixedEnd; i++) { U dbEntity = null; // Find the Entity to be used Number revision = parentRevision; if (usingRevisions && parent instanceof AuditedEntity) { /* * Looking up an Envers previous version is an expensive operation. So instead of getting a complete * collection and only adding those we need to the REST collection (like we do with standard related * entities in the database), when it comes to Envers we only retrieve the previous versions when * they are specifically requested. * * This means that we only have to request the list of revision numbers (supplied to us via the * revisions parameter) instead of having to request every revision. */ revision = revisions.get(i); dbEntity = (U) EnversUtilities.getRevision(entityManager, (AuditedEntity) parent, revision); } else { dbEntity = entities.get(i); } // If the entity was found then create the REST Entity if (dbEntity != null) { final T restEntity = dataObjectFactory.createRESTEntityFromDBEntity(dbEntity, baseUrl, dataType, expand, revision, expandParentReferences); // Add the item to the return value retValue.addItem(restEntity); } } } } return retValue; } /** * Create a Collection of REST Entities from a collection of Database Entities. * * @param clazz The Class of the Collection Object that should be returned by the method. * @param dataObjectFactory The factory to convert the database entity to a REST entity * @param query The CriteriaQuery that holds the details on which entities to create the collection from. * @param expandName The name of the collection that we are working with * @param dataType The type of data that is returned through the REST interface * @param parentExpand The parent objects expansion details * @param baseUrl The base of the url that was used to access this collection * @param expandParentReferences If any Parent references in entities should be expanded. * @param entityManager The EntityManager being used to provide data for the collection. * @return a REST collection from a collection of database entities. */ public static <T extends RESTBaseEntityV1<T>, U extends PressGangEntity, V extends RESTBaseEntityCollectionV1<T, V, W>, W extends RESTBaseEntityCollectionItemV1<T, V, W>> V create( final Class<V> clazz, final RESTEntityFactory<T, U, V, W> dataObjectFactory, final CriteriaQuery<U> query, final String expandName, final String dataType, final ExpandDataTrunk parentExpand, final String baseUrl, final boolean expandParentReferences, final EntityManager entityManager) { V retValue = null; try { retValue = clazz.newInstance(); } catch (final Exception ex) { throw new InternalServerErrorException(ex); } /* * There must be a query otherwise just return */ if (query == null) return retValue; retValue.setExpand(expandName); /* * Un-comment this to make it so that size is always shown. Don't forget to comment out line 303 below. */ //final int size = JPAUtils.count(entityManager, query).intValue(); //retValue.setSize(size); final ExpandDataTrunk expand = parentExpand != null ? parentExpand.get(expandName) : null; if (expand != null) { if (expand.getTrunk().getName().equals(expandName)) { assert baseUrl != null : "Parameter baseUrl can not be null if parameter expand is not null"; final ExpandDataDetails indexes = expand.getTrunk(); /* * Un-comment this to make it so that size is only shown when the expand is set. Don't forget to comment out * line 287 above. */ final int size = JPAUtils.count(entityManager, query).intValue(); retValue.setSize(size); // Find the start reference for the response using the start and size of the entity list. int start = 0; if (indexes.getStart() != null) { final int startIndex = indexes.getStart(); if (startIndex < 0) { start = Math.max(0, size + startIndex); } else { start = Math.min(startIndex, size == 0 ? 0 : size - 1); } } // Find the end reference for the response using the start and size of the entity list. int end = size; if (indexes.getEnd() != null) { final int endIndex = indexes.getEnd(); if (endIndex < 0) { end = Math.max(0, size + endIndex + 1); } else { end = Math.min(endIndex, size); } } // Fix the start and end if the two overlap final int fixedStart = Math.min(start, end); final int fixedEnd = Math.max(start, end); retValue.setStartExpandIndex(fixedStart); retValue.setEndExpandIndex(fixedEnd); // Create the query final TypedQuery<U> query1 = entityManager.createQuery(query); // Set the start and the end query1.setFirstResult(fixedStart); query1.setMaxResults(fixedEnd - fixedStart); // Get the results final List<U> entities = query1.getResultList(); // Get the entities requested from the entity list and create the REST Entities. for (int i = 0; i < entities.size(); i++) { U dbEntity = entities.get(i); // If the entity was found then create the REST Entity if (dbEntity != null) { final T restEntity = dataObjectFactory.createRESTEntityFromDBEntity(dbEntity, baseUrl, dataType, expand, null, expandParentReferences); // Add the item to the return value retValue.addItem(restEntity); } } } } return retValue; } }