RESTful API for Categories

Former-commit-id: 3b6f069895
restapi
Jens Pelzetter 2020-06-07 11:15:48 +02:00
parent 813ff58daa
commit 264736422f
6 changed files with 362 additions and 66 deletions

View File

@ -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.CategorizationData;
import org.libreccm.api.admin.categorization.dto.CategoryData; import org.libreccm.api.admin.categorization.dto.CategoryData;
import org.libreccm.api.dto.ListView; import org.libreccm.api.dto.ListView;
import org.libreccm.categorization.Categorization;
import org.libreccm.categorization.Category; import org.libreccm.categorization.Category;
import org.libreccm.categorization.CategoryManager; import org.libreccm.categorization.CategoryManager;
import org.libreccm.categorization.CategoryRepository; import org.libreccm.categorization.CategoryRepository;
import org.libreccm.categorization.Domain; 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.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege; import org.libreccm.security.RequiresPrivilege;
@ -71,6 +75,9 @@ public class CategoriesApi {
@Inject @Inject
private CategorizationApiRepository repository; private CategorizationApiRepository repository;
@Inject
private CcmObjectRepository ccmObjectRepository;
@GET @GET
@Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}") @Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@ -496,10 +503,23 @@ public class CategoriesApi {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public ListView<CategorizationData> getObjectsInCategory( public ListView<CategorizationData> getObjectsInCategory(
@PathParam("domainIdentifier") final String domainIdentifierParam, @PathParam("domainIdentifier") final String domainIdentifier,
@PathParam("path") final String categoryPathTokens @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 @GET
@ -509,88 +529,253 @@ public class CategoriesApi {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public ListView<CategorizationData> getCategoryObjectsInCategory( public ListView<CategorizationData> 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 @GET
@Path("/UUID-{categoryId}/objects") @Path("/UUID-{uuid}/objects")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public ListView<CategorizationData> getObjectsInCategory( public ListView<CategorizationData> 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 @POST
@Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/objects/{objectIdentifier}") @Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/objects/")
@Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public CategoryData addObjectToCategory( public Response addObjectToCategory(
@PathParam("domainIdentifier") final String domainIdentifierParam, @PathParam("domainIdentifier") final String domainIdentifier,
@PathParam("path") final String categoryPathTokens, @PathParam("path") final String categoryPath,
@PathParam("objectIdentifier") final String objectIdentifierParam 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()
);
} }
@PUT return Response.created(
uriInfo
.getBaseUriBuilder()
.path(domain.getDomainKey())
.path(categoryPath)
.path("objects")
.path(categorization.getUuid())
.build()
).build();
}
@POST
@Path("/ID-{categoryId}/objects/{objectIdentifier}") @Path("/ID-{categoryId}/objects/{objectIdentifier}")
@Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public CategoryData addObjectToCategory( public Response addObjectToCategory(
@PathParam("categoryId") final long categoryId, @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
);
} }
@PUT final Category category = findCategory(categoryId);
@Path("/UUID-{categoryId}/objects/objectIdentifier") final CcmObject object = findObject(categorizationData.getUuid());
@Produces(MediaType.APPLICATION_JSON)
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();
}
@POST
@Path("/UUID-{categoryUuid}/objects/objectIdentifier")
@Consumes(MediaType.APPLICATION_JSON)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public CategoryData addObjectsToCategory( public Response addObjectsToCategory(
@PathParam("categoryId") final String categoryUuid, @PathParam("categoryUuid") final String uuid,
@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(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 @DELETE
@Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/objects/{objectIdentifier}") @Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}/objects/{objectIdentifier}")
@Produces(MediaType.APPLICATION_JSON)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public CategoryData removeObjectFromCategory( public Response removeObjectFromCategory(
@PathParam("domainIdentifier") final String domainIdentifierParam, @PathParam("domainIdentifier") final String domainIdentifier,
@PathParam("path") final String categoryPathTokens, @PathParam("path") final String categoryPath,
@PathParam("objectIdentifier") final String objectIdentifierParam @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 @DELETE
@Path("/ID-{categoryId}/objects/{objectIdentifier}") @Path("/ID-{categoryId}/objects/{objectIdentifier}")
@Produces(MediaType.APPLICATION_JSON)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public CategoryData removeObjectFromCategory( public Response removeObjectFromCategory(
@PathParam("categoryId") final long categoryId, @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 @DELETE
@ -599,11 +784,39 @@ public class CategoriesApi {
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public CategoryData removeObjectsFromCategory( public Response removeObjectsFromCategory(
@PathParam("categoryId") final String categoryUuid, @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) { 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
)
)
);
}
} }

View File

@ -20,7 +20,6 @@ package org.libreccm.api.admin.categorization.dto;
import org.libreccm.api.core.dto.CcmObjectId; import org.libreccm.api.core.dto.CcmObjectId;
import org.libreccm.categorization.Categorization; import org.libreccm.categorization.Categorization;
import org.libreccm.categorization.Category;
import java.util.Objects; import java.util.Objects;

View File

@ -62,36 +62,37 @@ import javax.persistence.Table;
name = "Categorization.find", name = "Categorization.find",
query = "SELECT c FROM Categorization c " query = "SELECT c FROM Categorization c "
+ "WHERE c.category = :category " + "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( @NamedQuery(
name = "Categorization.isAssignedTo", name = "Categorization.isAssignedTo",
query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) "
+ "FROM Categorization c " + "FROM Categorization c "
+ "WHERE c.category = :category " + "WHERE c.category = :category "
+ "AND c.categorizedObject = :object") + "AND c.categorizedObject = :object"),
,
@NamedQuery( @NamedQuery(
name = "Categorization.isAssignedToWithType", name = "Categorization.isAssignedToWithType",
query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) "
+ "FROM Categorization c " + "FROM Categorization c "
+ "WHERE c.category = :category " + "WHERE c.category = :category "
+ "AND c.categorizedObject = :object " + "AND c.categorizedObject = :object "
+ "AND c.type = :type") + "AND c.type = :type"),
,
@NamedQuery( @NamedQuery(
name = "Categorization.findIndexObject", name = "Categorization.findIndexObject",
query = "SELECT c.categorizedObject FROM Categorization c " query = "SELECT c.categorizedObject FROM Categorization c "
+ "WHERE c.category = :category " + "WHERE c.category = :category "
+ "AND c.indexObject = TRUE") + "AND c.indexObject = TRUE"),
,
@NamedQuery( @NamedQuery(
name = "Categorization.findIndexObjectCategorization", name = "Categorization.findIndexObjectCategorization",
query = "SELECT c FROM Categorization c " query = "SELECT c FROM Categorization c "
+ "WHERE c.category = :category " + "WHERE c.category = :category "
+ "AND c.indexObject = TRUE" + "AND c.indexObject = TRUE"
) ),
,
@NamedQuery( @NamedQuery(
name = "Categorization.hasIndexObject", name = "Categorization.hasIndexObject",
query = "SELECT (CASE WHEN COUNT(c.categorizedObject) > 0 THEN true " query = "SELECT (CASE WHEN COUNT(c.categorizedObject) > 0 THEN true "

View File

@ -95,6 +95,12 @@ import javax.xml.bind.annotation.XmlRootElement;
query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) " query = "SELECT (CASE WHEN COUNT(c) > 0 THEN true ELSE false END) "
+ "FROM Categorization c " + "FROM Categorization c "
+ "WHERE c.categorizedObject = :object"), + "WHERE c.categorizedObject = :object"),
@NamedQuery(
name = "Category.findObjects",
query = "SELECT c FROM Categorization c "
+ "WHERE c.category = :category "
+ "ORDER BY c.objectOrder"
),
@NamedQuery( @NamedQuery(
name = "Category.countObjects", name = "Category.countObjects",
query = "SELECT COUNT(c) FROM Categorization c " query = "SELECT COUNT(c) FROM Categorization c "

View File

@ -76,12 +76,12 @@ public class CategoryManager implements Serializable {
private PermissionChecker permissionChecker; private PermissionChecker permissionChecker;
/** /**
* Assigns an category to an object. * Assigns an category to an object.Please note: Because the association
* * between {@link Category} and {@code
* Please note: Because the association between {@link Category} and {@code
* CcmObject} is a many-to-many association we use an association object to * CcmObject} is a many-to-many association we use an association object to
* store the additional attributes of the association. The * store the additional attributes of the association.
* {@link Categorization} entity is completely managed by this class. *
* The {@link Categorization} entity is completely managed by this class.
* *
* If either {@code object} or the {@code category} parameter are * If either {@code object} or the {@code category} parameter are
* {@code null} an {@link IllegalArgumentException} is thrown because * {@code null} an {@link IllegalArgumentException} is thrown because
@ -91,15 +91,18 @@ public class CategoryManager implements Serializable {
* {@code null}. * {@code null}.
* @param category The category to which the object should be assigned. Can * @param category The category to which the object should be assigned. Can
* never be {@code null}. * never be {@code null}.
*
* @return The new {@link Categorization} object for the category and the
* object.
*/ */
@AuthorizationRequired @AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public void addObjectToCategory( public Categorization addObjectToCategory(
final CcmObject object, final CcmObject object,
@RequiresPrivilege(PRIVILEGE_MANAGE_CATEGORY_OBJECTS) @RequiresPrivilege(PRIVILEGE_MANAGE_CATEGORY_OBJECTS)
final Category category) { 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 * @param category The category to which the object should be assigned. Can
* never be {@code null}. * never be {@code null}.
* @param type Type of the categorisation. * @param type Type of the categorisation.
*
* @return The new {@link Categorization} object for the category and the
* object.
*/ */
@AuthorizationRequired @AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public void addObjectToCategory( public Categorization addObjectToCategory(
final CcmObject object, final CcmObject object,
@RequiresPrivilege(PRIVILEGE_MANAGE_CATEGORY_OBJECTS) @RequiresPrivilege(PRIVILEGE_MANAGE_CATEGORY_OBJECTS)
final Category category, final Category category,
@ -161,6 +167,8 @@ public class CategoryManager implements Serializable {
shiro.getSystemUser().execute(() -> { shiro.getSystemUser().execute(() -> {
entityManager.persist(categorization); entityManager.persist(categorization);
}); });
return categorization;
} }
/** /**
@ -888,7 +896,7 @@ public class CategoryManager implements Serializable {
"No category with ID %d in the database. Where did that ID come from?", "No category with ID %d in the database. Where did that ID come from?",
ofCategory.getObjectId()))); ofCategory.getObjectId())));
while(current.getParentCategory() != null) { while (current.getParentCategory() != null) {
categories.add(current); categories.add(current);
current = current.getParentCategory(); current = current.getParentCategory();
} }

View File

@ -243,6 +243,57 @@ public class CategoryRepository extends AbstractEntityRepository<Long, Category>
).getSingleResult(); ).getSingleResult();
} }
@Transactional(Transactional.TxType.REQUIRED)
public List<Categorization> 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<Categorization> 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<Categorization> 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, private boolean filterCategoryByName(final Category category,
final String name) { final String name) {
LOGGER.debug("#findByPath(Domain, String): c = {}", LOGGER.debug("#findByPath(Domain, String): c = {}",