diff --git a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/CategoriesApi.java b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/CategoriesApi.java index 7965700b1..59c85bd81 100644 --- a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/CategoriesApi.java +++ b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/CategoriesApi.java @@ -21,10 +21,14 @@ package org.libreccm.api.admin.categorization; import org.libreccm.api.admin.categorization.dto.CategorizationData; import org.libreccm.api.admin.categorization.dto.CategoryData; import org.libreccm.api.dto.ListView; +import org.libreccm.categorization.Categorization; import org.libreccm.categorization.Category; import org.libreccm.categorization.CategoryManager; import org.libreccm.categorization.CategoryRepository; import org.libreccm.categorization.Domain; +import org.libreccm.categorization.ObjectNotAssignedToCategoryException; +import org.libreccm.core.CcmObject; +import org.libreccm.core.CcmObjectRepository; import org.libreccm.core.CoreConstants; import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.RequiresPrivilege; @@ -71,6 +75,9 @@ public class CategoriesApi { @Inject private CategorizationApiRepository repository; + @Inject + private CcmObjectRepository ccmObjectRepository; + @GET @Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}") @Produces(MediaType.APPLICATION_JSON) @@ -496,10 +503,23 @@ public class CategoriesApi { @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) public ListView getObjectsInCategory( - @PathParam("domainIdentifier") final String domainIdentifierParam, - @PathParam("path") final String categoryPathTokens + @PathParam("domainIdentifier") final String domainIdentifier, + @PathParam("path") final String categoryPath, + @QueryParam("limit") final int limit, + @QueryParam("offset") final int offset ) { - throw new UnsupportedOperationException(); + final Domain domain = repository.findDomain(domainIdentifier); + final Category category = findCategory(domain, categoryPath); + + return new ListView<>( + categoryRepository + .findObjectsInCategoryAsStream(category, limit, offset) + .map(CategorizationData::new) + .collect(Collectors.toList()), + categoryRepository.countObjectsInCategory(category), + limit, + offset + ); } @GET @@ -509,88 +529,253 @@ public class CategoriesApi { @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) public ListView getCategoryObjectsInCategory( - @PathParam("categoryId") final long categoryId + @PathParam("categoryId") final long categoryId, + @QueryParam("limit") final int limit, + @QueryParam("offset") final int offset ) { - throw new UnsupportedOperationException(); + final Category category = findCategory(categoryId); + + return new ListView<>( + categoryRepository + .findObjectsInCategoryAsStream(category, limit, offset) + .map(CategorizationData::new) + .collect(Collectors.toList()), + categoryRepository.countObjectsInCategory(category), + limit, + offset + ); } @GET - @Path("/UUID-{categoryId}/objects") + @Path("/UUID-{uuid}/objects") @Produces(MediaType.APPLICATION_JSON) @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) public ListView getObjectsInCategory( - @PathParam("categoryId") final String categoryUuid + @PathParam("uuid") final String uuid, + @QueryParam("limit") final int limit, + @QueryParam("offset") final int offset ) { - throw new UnsupportedOperationException(); + final Category category = findCategory(uuid); + + return new ListView<>( + categoryRepository + .findObjectsInCategoryAsStream(category, limit, offset) + .map(CategorizationData::new) + .collect(Collectors.toList()), + categoryRepository.countObjectsInCategory(category), + limit, + offset + ); } - @PUT - @Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/objects/{objectIdentifier}") - @Produces(MediaType.APPLICATION_JSON) + @POST + @Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/objects/") + @Consumes(MediaType.APPLICATION_JSON) @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public CategoryData addObjectToCategory( - @PathParam("domainIdentifier") final String domainIdentifierParam, - @PathParam("path") final String categoryPathTokens, - @PathParam("objectIdentifier") final String objectIdentifierParam + public Response addObjectToCategory( + @PathParam("domainIdentifier") final String domainIdentifier, + @PathParam("path") final String categoryPath, + final CategorizationData categorizationData ) { - throw new UnsupportedOperationException(); + if (categorizationData == null) { + throw new WebApplicationException( + "Missing CategorizationData.", Response.Status.BAD_REQUEST + ); + } + final Domain domain = repository.findDomain(domainIdentifier); + final Category category = findCategory(domain, categoryPath); + final CcmObject object = findObject(categorizationData.getUuid()); + + final Categorization categorization; + if (categorizationData.getType() == null) { + categorization = categoryManager.addObjectToCategory( + object, category + ); + } else { + categorization = categoryManager.addObjectToCategory( + object, category, categorizationData.getType() + ); + } + + return Response.created( + uriInfo + .getBaseUriBuilder() + .path(domain.getDomainKey()) + .path(categoryPath) + .path("objects") + .path(categorization.getUuid()) + .build() + ).build(); } - @PUT + @POST @Path("/ID-{categoryId}/objects/{objectIdentifier}") - @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public CategoryData addObjectToCategory( + public Response addObjectToCategory( @PathParam("categoryId") final long categoryId, - @PathParam("objectIdentifier") final String objectIdentifier + final CategorizationData categorizationData ) { - throw new UnsupportedOperationException(); + if (categorizationData == null) { + throw new WebApplicationException( + "Missing CategorizationData.", Response.Status.BAD_REQUEST + ); + } + + final Category category = findCategory(categoryId); + final CcmObject object = findObject(categorizationData.getUuid()); + + final Categorization categorization; + if (categorizationData.getType() == null) { + categorization = categoryManager.addObjectToCategory( + object, category + ); + } else { + categorization = categoryManager.addObjectToCategory( + object, category, categorizationData.getType() + ); + } + + return Response.created( + uriInfo + .getBaseUriBuilder() + .path(String.format("ID-%d", category.getObjectId())) + .path("objects") + .path(categorization.getUuid()) + .build() + ).build(); } - @PUT - @Path("/UUID-{categoryId}/objects/objectIdentifier") - @Produces(MediaType.APPLICATION_JSON) + @POST + @Path("/UUID-{categoryUuid}/objects/objectIdentifier") + @Consumes(MediaType.APPLICATION_JSON) @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public CategoryData addObjectsToCategory( - @PathParam("categoryId") final String categoryUuid, - @PathParam("objectIdentifier") final String objectIdentifier + public Response addObjectsToCategory( + @PathParam("categoryUuid") final String uuid, + final CategorizationData categorizationData ) { - throw new UnsupportedOperationException(); + if (categorizationData == null) { + throw new WebApplicationException( + "Missing CategorizationData.", Response.Status.BAD_REQUEST + ); + } + + final Category category = findCategory(uuid); + final CcmObject object = findObject(categorizationData.getUuid()); + + final Categorization categorization; + if (categorizationData.getType() == null) { + categorization = categoryManager.addObjectToCategory( + object, category + ); + } else { + categorization = categoryManager.addObjectToCategory( + object, category, categorizationData.getType() + ); + } + + return Response.created( + uriInfo + .getBaseUriBuilder() + .path(String.format("UUID-%s", category.getUuid())) + .path("objects") + .path(categorization.getUuid()) + .build() + ).build(); } @DELETE @Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/objects/{objectIdentifier}") - @Produces(MediaType.APPLICATION_JSON) @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public CategoryData removeObjectFromCategory( - @PathParam("domainIdentifier") final String domainIdentifierParam, - @PathParam("path") final String categoryPathTokens, - @PathParam("objectIdentifier") final String objectIdentifierParam + public Response removeObjectFromCategory( + @PathParam("domainIdentifier") final String domainIdentifier, + @PathParam("path") final String categoryPath, + @PathParam("objectIdentifier") final String categorizationUuid ) { - throw new UnsupportedOperationException(); + final Domain domain = repository.findDomain(domainIdentifier); + final Category category = findCategory(domain, categoryPath); + final Categorization categorization = categoryRepository + .findObjectInCategory(category, categorizationUuid) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No Categorization %s in Category %s of Domain %s.", + categorizationUuid, + categoryPath, + domain.getDomainKey() + ), + Response.Status.NOT_FOUND + ) + ); + + try { + categoryManager.removeObjectFromCategory( + categorization.getOwner(), category + ); + return Response.ok( + String.format( + "Categorization %s of Category %s of Domain %s " + + "deleted successfully.", + categorization.getUuid(), + categoryPath, + domain.getDomainKey() + ) + ).build(); + } catch (ObjectNotAssignedToCategoryException ex) { + throw new WebApplicationException( + Response.Status.INTERNAL_SERVER_ERROR + ); + } } @DELETE @Path("/ID-{categoryId}/objects/{objectIdentifier}") - @Produces(MediaType.APPLICATION_JSON) @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public CategoryData removeObjectFromCategory( + public Response removeObjectFromCategory( @PathParam("categoryId") final long categoryId, - @PathParam("objectIdentifier") final String objectIdentifier + @PathParam("objectIdentifier") final String categorizationUuid ) { - throw new UnsupportedOperationException(); + final Category category = findCategory(categoryId); + final Categorization categorization = categoryRepository + .findObjectInCategory(category, categorizationUuid) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No Categorization %s in Category %d.", + categorizationUuid, categoryId + ), + Response.Status.NOT_FOUND + ) + ); + + try { + categoryManager.removeObjectFromCategory( + categorization.getOwner(), category + ); + return Response.ok( + String.format( + "Categorization %s of Category %d deleted successfully.", + categorization.getUuid(), + categoryId + ) + ).build(); + } catch (ObjectNotAssignedToCategoryException ex) { + throw new WebApplicationException( + Response.Status.INTERNAL_SERVER_ERROR + ); + } } @DELETE @@ -599,11 +784,39 @@ public class CategoriesApi { @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public CategoryData removeObjectsFromCategory( + public Response removeObjectsFromCategory( @PathParam("categoryId") final String categoryUuid, - @PathParam("objectIdentifier") final String objectIdentifier + @PathParam("objectIdentifier") final String categorizationUuid ) { - throw new UnsupportedOperationException(); + final Category category = findCategory(categoryUuid); + final Categorization categorization = categoryRepository + .findObjectInCategory(category, categorizationUuid) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No Categorization %s in Category %s.", + categorizationUuid, categorizationUuid + ), + Response.Status.NOT_FOUND + ) + ); + + try { + categoryManager.removeObjectFromCategory( + categorization.getOwner(), category + ); + return Response.ok( + String.format( + "Categorization %s of Category %s deleted successfully.", + categorization.getUuid(), + categorizationUuid + ) + ).build(); + } catch (ObjectNotAssignedToCategoryException ex) { + throw new WebApplicationException( + Response.Status.INTERNAL_SERVER_ERROR + ); + } } private Category findCategory(final Domain domain, final String path) { @@ -647,4 +860,22 @@ public class CategoriesApi { ); } + private CcmObject findObject(final String uuid) { + if (uuid == null) { + throw new WebApplicationException( + "No UUID for object", Response.Status.BAD_REQUEST + ); + } + + return ccmObjectRepository + .findObjectByUuid(uuid) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No CcmObject with UUID %s found.", uuid + ) + ) + ); + } + } diff --git a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/CategorizationData.java b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/CategorizationData.java index 1e54827c6..cd8695b44 100644 --- a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/CategorizationData.java +++ b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/CategorizationData.java @@ -20,7 +20,6 @@ package org.libreccm.api.admin.categorization.dto; import org.libreccm.api.core.dto.CcmObjectId; import org.libreccm.categorization.Categorization; -import org.libreccm.categorization.Category; import java.util.Objects; diff --git a/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java b/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java index 9fd2d02e0..8f226acca 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java @@ -62,36 +62,37 @@ import javax.persistence.Table; name = "Categorization.find", query = "SELECT c FROM Categorization c " + "WHERE c.category = :category " - + "AND c.categorizedObject = :object") - , + + "AND c.categorizedObject = :object"), + @NamedQuery( + name = "Categorization.findForCategory", + query = "SELECT c FROM Categorization c " + + "WHERE c.uuid = :uuid " + + "AND c.category = :category" + ), @NamedQuery( name = "Categorization.isAssignedTo", query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " + "FROM Categorization c " + "WHERE c.category = :category " - + "AND c.categorizedObject = :object") - , + + "AND c.categorizedObject = :object"), @NamedQuery( name = "Categorization.isAssignedToWithType", query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " + "FROM Categorization c " + "WHERE c.category = :category " + "AND c.categorizedObject = :object " - + "AND c.type = :type") - , + + "AND c.type = :type"), @NamedQuery( name = "Categorization.findIndexObject", query = "SELECT c.categorizedObject FROM Categorization c " + "WHERE c.category = :category " - + "AND c.indexObject = TRUE") - , + + "AND c.indexObject = TRUE"), @NamedQuery( name = "Categorization.findIndexObjectCategorization", query = "SELECT c FROM Categorization c " + "WHERE c.category = :category " + "AND c.indexObject = TRUE" - ) - , + ), @NamedQuery( name = "Categorization.hasIndexObject", query = "SELECT (CASE WHEN COUNT(c.categorizedObject) > 0 THEN true " diff --git a/ccm-core/src/main/java/org/libreccm/categorization/Category.java b/ccm-core/src/main/java/org/libreccm/categorization/Category.java index e004bad07..82a5dd126 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/Category.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/Category.java @@ -95,6 +95,12 @@ import javax.xml.bind.annotation.XmlRootElement; query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " + "FROM Categorization c " + "WHERE c.categorizedObject = :object"), + @NamedQuery( + name = "Category.findObjects", + query = "SELECT c FROM Categorization c " + + "WHERE c.category = :category " + + "ORDER BY c.objectOrder" + ), @NamedQuery( name = "Category.countObjects", query = "SELECT COUNT(c) FROM Categorization c " diff --git a/ccm-core/src/main/java/org/libreccm/categorization/CategoryManager.java b/ccm-core/src/main/java/org/libreccm/categorization/CategoryManager.java index 41dc93296..9085ac75b 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/CategoryManager.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/CategoryManager.java @@ -76,12 +76,12 @@ public class CategoryManager implements Serializable { private PermissionChecker permissionChecker; /** - * Assigns an category to an object. - * - * Please note: Because the association between {@link Category} and {@code + * Assigns an category to an object.Please note: Because the association + * between {@link Category} and {@code * CcmObject} is a many-to-many association we use an association object to - * store the additional attributes of the association. The - * {@link Categorization} entity is completely managed by this class. + * store the additional attributes of the association. + * + * The {@link Categorization} entity is completely managed by this class. * * If either {@code object} or the {@code category} parameter are * {@code null} an {@link IllegalArgumentException} is thrown because @@ -91,15 +91,18 @@ public class CategoryManager implements Serializable { * {@code null}. * @param category The category to which the object should be assigned. Can * never be {@code null}. + * + * @return The new {@link Categorization} object for the category and the + * object. */ @AuthorizationRequired @Transactional(Transactional.TxType.REQUIRED) - public void addObjectToCategory( + public Categorization addObjectToCategory( final CcmObject object, @RequiresPrivilege(PRIVILEGE_MANAGE_CATEGORY_OBJECTS) final Category category) { - addObjectToCategory(object, category, null); + return addObjectToCategory(object, category, null); } /** @@ -119,10 +122,13 @@ public class CategoryManager implements Serializable { * @param category The category to which the object should be assigned. Can * never be {@code null}. * @param type Type of the categorisation. + * + * @return The new {@link Categorization} object for the category and the + * object. */ @AuthorizationRequired @Transactional(Transactional.TxType.REQUIRED) - public void addObjectToCategory( + public Categorization addObjectToCategory( final CcmObject object, @RequiresPrivilege(PRIVILEGE_MANAGE_CATEGORY_OBJECTS) final Category category, @@ -161,6 +167,8 @@ public class CategoryManager implements Serializable { shiro.getSystemUser().execute(() -> { entityManager.persist(categorization); }); + + return categorization; } /** @@ -878,22 +886,22 @@ public class CategoryManager implements Serializable { */ @Transactional(Transactional.TxType.REQUIRED) public List getCategoriesInPath(final Category ofCategory) { - + Objects.requireNonNull(ofCategory); - + List categories = new ArrayList<>(); - + Category current = categoryRepo.findById(ofCategory.getObjectId()) .orElseThrow(() -> new IllegalArgumentException(String.format( "No category with ID %d in the database. Where did that ID come from?", ofCategory.getObjectId()))); - - while(current.getParentCategory() != null) { + + while (current.getParentCategory() != null) { categories.add(current); current = current.getParentCategory(); } Collections.reverse(categories); - + return categories; } diff --git a/ccm-core/src/main/java/org/libreccm/categorization/CategoryRepository.java b/ccm-core/src/main/java/org/libreccm/categorization/CategoryRepository.java index a89a04a41..155331353 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/CategoryRepository.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/CategoryRepository.java @@ -243,6 +243,57 @@ public class CategoryRepository extends AbstractEntityRepository ).getSingleResult(); } + @Transactional(Transactional.TxType.REQUIRED) + public List findObjectsInCategory( + final Category category, + final int limit, + final int offset + ) { + return getEntityManager() + .createNamedQuery("Category.findObjects", Categorization.class) + .setParameter("category", category) + .setMaxResults(limit) + .setFirstResult(offset) + .getResultList(); + } + + @Transactional(Transactional.TxType.REQUIRED) + public Stream findObjectsInCategoryAsStream( + final Category category, + final int limit, + final int offset + ) { + return getEntityManager() + .createNamedQuery("Category.findObjects", Categorization.class) + .setParameter("category", category) + .setMaxResults(limit) + .setFirstResult(offset) + .getResultStream(); + } + + @Transactional(Transactional.TxType.REQUIRED) + public long countObjectsInCategory(final Category category) { + return getEntityManager() + .createNamedQuery("Category.countObjects", Long.class) + .setParameter("category", category) + .getSingleResult(); + } + + @Transactional(Transactional.TxType.REQUIRED) + public Optional findObjectInCategory( + final Category category, final String categorizationUuid + ) { + return getEntityManager() + .createNamedQuery( + "Categorization.findForCategory", + Categorization.class + ) + .setParameter("uuid", categorizationUuid) + .setParameter("category", category) + .getResultStream() + .findFirst(); + } + private boolean filterCategoryByName(final Category category, final String name) { LOGGER.debug("#findByPath(Domain, String): c = {}",