From 10da2f6ef641bc53a8c0461174e75407a8560c3c Mon Sep 17 00:00:00 2001 From: Jens Pelzetter Date: Sat, 6 Jun 2020 16:15:15 +0200 Subject: [PATCH] RESTful API for managing Domains --- .../java/org/libreccm/api/ApiConstants.java | 1 + .../java/org/libreccm/api/admin/AdminApi.java | 2 +- .../admin/categorization/CategoriesApi.java | 14 + .../api/admin/categorization/DomainsApi.java | 259 ++++++++++++++++-- .../admin/categorization/dto/CategoryId.java | 13 + .../dto/DomainOwnershipData.java | 22 ++ .../categorization/DomainOwnership.java | 22 +- .../categorization/DomainRepository.java | 52 +++- 8 files changed, 355 insertions(+), 30 deletions(-) diff --git a/ccm-core/src/main/java/org/libreccm/api/ApiConstants.java b/ccm-core/src/main/java/org/libreccm/api/ApiConstants.java index b980ef666..40e057d4c 100644 --- a/ccm-core/src/main/java/org/libreccm/api/ApiConstants.java +++ b/ccm-core/src/main/java/org/libreccm/api/ApiConstants.java @@ -28,6 +28,7 @@ public final class ApiConstants { // Nothing } + public static final String IDENTIFIER_PREFIX_ID = "ID-"; public static final String IDENTIFIER_PREFIX_UUID = "UUID-"; diff --git a/ccm-core/src/main/java/org/libreccm/api/admin/AdminApi.java b/ccm-core/src/main/java/org/libreccm/api/admin/AdminApi.java index a1a19b301..8a6307346 100644 --- a/ccm-core/src/main/java/org/libreccm/api/admin/AdminApi.java +++ b/ccm-core/src/main/java/org/libreccm/api/admin/AdminApi.java @@ -27,7 +27,7 @@ import org.libreccm.api.admin.categorization.DomainsApi; */ @ApplicationPath("/api/admin") public class AdminApi extends Application { - + @Override public Set> getClasses() { final Set> classes = new HashSet<>(); 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 15a0429fa..9d5cae72a 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,11 +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.CategoryManager; +import org.libreccm.categorization.CategoryRepository; import org.libreccm.core.CoreConstants; import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.RequiresPrivilege; import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; import javax.transaction.Transactional; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -47,6 +50,15 @@ import javax.ws.rs.core.MediaType; @Path("/categories") public class CategoriesApi { + @Inject + private CategoryManager categoryManager; + + @Inject + private CategoryRepository categoryRepository; + + @Inject + private CategorizationApiRepository repository; + @GET @Path("/{domainIdentifier}/{path:^[\\w\\-/]+$}") @Produces(MediaType.APPLICATION_JSON) @@ -57,6 +69,8 @@ public class CategoriesApi { @PathParam("domainIdentifier") final String domainIdentifierParam, @PathParam("path") final String categoryPathTokens ) { + + throw new UnsupportedOperationException(); } diff --git a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/DomainsApi.java b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/DomainsApi.java index 39a041f95..891484ae4 100644 --- a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/DomainsApi.java +++ b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/DomainsApi.java @@ -18,16 +18,29 @@ */ package org.libreccm.api.admin.categorization; +import org.libreccm.api.Identifier; +import org.libreccm.api.IdentifierParser; import org.libreccm.api.admin.categorization.dto.DomainData; import org.libreccm.api.admin.categorization.dto.DomainOwnershipData; import org.libreccm.api.dto.ListView; +import org.libreccm.categorization.Domain; +import org.libreccm.categorization.DomainManager; +import org.libreccm.categorization.DomainRepository; import org.libreccm.core.CoreConstants; import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.RequiresPrivilege; +import org.libreccm.web.ApplicationRepository; +import org.libreccm.web.CcmApplication; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; import javax.transaction.Transactional; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -39,16 +52,40 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; /** * * @author Jens Pelzetter */ @RequestScoped -@Path("/domains") +@Path("/" + DomainsApi.RESOURCE_PATH) public class DomainsApi { + public static final String RESOURCE_PATH = "domains"; + + @Context + private UriInfo uriInfo; + + @Inject + private ApplicationRepository applicationRepository; + + @Inject + private DomainManager domainManager; + + @Inject + private DomainRepository domainRepository; + + @Inject + private CategorizationApiRepository repository; + + @Inject + private IdentifierParser identifierParser; + @GET @Path("/") @Produces(MediaType.APPLICATION_JSON) @@ -59,7 +96,16 @@ public class DomainsApi { @QueryParam("limit") @DefaultValue("20") final int limit, @QueryParam("offset") @DefaultValue("0") final int offset ) { - throw new UnsupportedOperationException(); + return new ListView<>( + domainRepository + .findAll(limit, offset) + .stream() + .map(DomainData::new) + .collect(Collectors.toList()), + domainRepository.countAll(), + limit, + offset + ); } @GET @@ -70,18 +116,29 @@ public class DomainsApi { @Transactional(Transactional.TxType.REQUIRED) public DomainData getDomain( @PathParam("domainIdentifier") final String domainIdentifierParam) { - throw new UnsupportedOperationException(); + + return new DomainData(repository.findDomain(domainIdentifierParam)); } @POST @Path("/") @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public ListView addDomain(final DomainData domainData) { - throw new UnsupportedOperationException(); + public Response addDomain(final DomainData domainData) { + final Domain domain = domainManager + .createDomain( + domainData.getDomainKey(), domainData.getRoot().getName() + ); + + return Response.created( + uriInfo + .getBaseUriBuilder() + .path(RESOURCE_PATH) + .path(domain.getDomainKey()) + .build() + ).build(); } @PUT @@ -91,10 +148,51 @@ public class DomainsApi { @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public DomainData updateDomain( + public Response updateDomain( @PathParam("domainIdentifier") final String domainIdentifierParam, final DomainData domainData) { - throw new UnsupportedOperationException(); + + final Domain domain = repository.findDomain(domainIdentifierParam); + + boolean updated = false; + + if (!domainData.getDescription().equals(domain.getDescription())) { + domain.setDescription(domainData.getDescription()); + updated = true; + } + + final String domainReleased = DateTimeFormatter.ISO_DATE_TIME.format( + domain.getReleased().toInstant() + ); + if (!domainData.getReleased().equals(domainReleased)) { + final TemporalAccessor released = DateTimeFormatter.ISO_DATE_TIME + .parse(domainData.getReleased()); + final Instant relasedInstant = Instant.from(released); + domain.setReleased(Date.from(relasedInstant)); + updated = true; + } + + if (!domainData.getTitle().equals(domain.getTitle())) { + domain.setTitle(domainData.getTitle()); + updated = true; + } + + if (!domainData.getVersion().equals(domain.getVersion())) { + domain.setVersion(domainData.getVersion()); + updated = true; + } + + if (updated) { + domainRepository.save(domain); + } + + return Response + .ok( + String.format( + "Domain %s updated successfully.", domain.getDomainKey() + ) + ) + .build(); } @DELETE @@ -102,21 +200,43 @@ public class DomainsApi { @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public DomainData deleteDomain( - @PathParam("domainIdentifier") final String domainIdentifierParam) { - throw new UnsupportedOperationException(); + public Response deleteDomain( + @PathParam("domainIdentifier") final String domainIdentifierParam + ) { + final Domain domain = repository.findDomain(domainIdentifierParam); + final String domainKey = domain.getDomainKey(); + + domainRepository.delete(domain); + + return Response + .ok( + String.format( + "Domain %s deleted successfully.", domainKey + ) + ).build(); } - + @GET @Path("/{domainIdentifier}/owners") @Produces(MediaType.APPLICATION_JSON) @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public List getOwners( - @PathParam("domainIdentifier") final String domainIdentifierParam + public ListView getOwners( + @PathParam("domainIdentifier") final String domainIdentifierParam, + @QueryParam("limit") @DefaultValue("20") final int limit, + @QueryParam("offset") @DefaultValue("0") final int offset ) { - throw new UnsupportedOperationException(); + final Domain domain = repository.findDomain(domainIdentifierParam); + return new ListView<>( + domainRepository + .findOwnersOfDomainAsStream(domain, limit, offset) + .map(DomainOwnershipData::new) + .collect(Collectors.toList()), + domainRepository.countOwnersOfDomain(domain), + limit, + offset + ); } @GET @@ -125,23 +245,116 @@ public class DomainsApi { @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public List addOwner( + public Response addOwner( @PathParam("domainIdentifier") final String domainIdentifierParam, @PathParam("ownerIdentifier") final String ownerIdentifierParam ) { - throw new UnsupportedOperationException(); + final Domain domain = repository.findDomain(domainIdentifierParam); + final CcmApplication owner; + final Identifier ownerIdentifier = identifierParser.parseIdentifier( + ownerIdentifierParam + ); + switch (ownerIdentifier.getType()) { + case ID: + owner = applicationRepository + .findById(Long.parseLong(ownerIdentifier.getIdentifier())) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No CcmApplication with ID %s found.", + ownerIdentifier.getIdentifier() + ), + Response.Status.NOT_FOUND + ) + ); + break; + case UUID: + owner = applicationRepository + .findByUuid(ownerIdentifier.getIdentifier()) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No CcmApplication with UUID %s found.", + ownerIdentifier.getIdentifier() + ), + Response.Status.NOT_FOUND + ) + ); + break; + default: + throw new WebApplicationException( + "CcmApplications can only be identified by ID or UUID.", + Response.Status.BAD_REQUEST + ); + } + + domainManager.addDomainOwner(owner, domain); + + return Response.ok( + String.format( + "CcmApplication %s is now an owner of the Domain %s", + owner.getPrimaryUrl(), + domain.getDomainKey() + ) + ).build(); } - - @DELETE + + @DELETE @Path("/{domainIdentifier}/owners/{ownerIdentifier}") @AuthorizationRequired @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @Transactional(Transactional.TxType.REQUIRED) - public List removeOwner( + public Response removeOwner( @PathParam("domainIdentifier") final String domainIdentifierParam, @PathParam("ownerIdentifier") final String ownerIdentifierParam ) { - throw new UnsupportedOperationException(); + final Domain domain = repository.findDomain(domainIdentifierParam); + final CcmApplication owner; + final Identifier ownerIdentifier = identifierParser.parseIdentifier( + ownerIdentifierParam + ); + switch (ownerIdentifier.getType()) { + case ID: + owner = applicationRepository + .findById(Long.parseLong(ownerIdentifier.getIdentifier())) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No CcmApplication with ID %s found.", + ownerIdentifier.getIdentifier() + ), + Response.Status.NOT_FOUND + ) + ); + break; + case UUID: + owner = applicationRepository + .findByUuid(ownerIdentifier.getIdentifier()) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No CcmApplication with UUID %s found.", + ownerIdentifier.getIdentifier() + ), + Response.Status.NOT_FOUND + ) + ); + break; + default: + throw new WebApplicationException( + "CcmApplications can only be identified by ID or UUID.", + Response.Status.BAD_REQUEST + ); + } + + domainManager.removeDomainOwner(owner, domain); + + return Response.ok( + String.format( + "CcmApplication %s no longer owns Domain %s", + owner.getPrimaryUrl(), + domain.getDomainKey() + ) + ).build(); } - -} +} \ No newline at end of file diff --git a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/CategoryId.java b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/CategoryId.java index db8d263a2..2e36f9e0b 100644 --- a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/CategoryId.java +++ b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/CategoryId.java @@ -32,6 +32,8 @@ public class CategoryId { private String uuid; private String uniqueId; + + private String name; /** * Constructor for creating empty instances. @@ -49,6 +51,7 @@ public class CategoryId { categoryId = category.getObjectId(); uuid = category.getUuid(); uniqueId = category.getUniqueId(); + name = category.getName(); } public long getCategoryId() { @@ -75,4 +78,14 @@ public class CategoryId { this.uniqueId = uniqueId; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + } diff --git a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/DomainOwnershipData.java b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/DomainOwnershipData.java index fde3571d9..fc35f24c6 100644 --- a/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/DomainOwnershipData.java +++ b/ccm-core/src/main/java/org/libreccm/api/admin/categorization/dto/DomainOwnershipData.java @@ -31,8 +31,12 @@ public class DomainOwnershipData { private String uuid; + private String context; + private CcmApplicationId owner; + private long ownerOrder; + public DomainOwnershipData() { // Nothing } @@ -40,7 +44,9 @@ public class DomainOwnershipData { public DomainOwnershipData(final DomainOwnership ownership) { ownershipId = ownership.getOwnershipId(); uuid = ownership.getUuid(); + context = ownership.getContext(); owner = new CcmApplicationId(ownership.getOwner()); + ownerOrder = ownership.getOwnerOrder(); } public long getOwnershipId() { @@ -67,4 +73,20 @@ public class DomainOwnershipData { this.owner = owner; } + public String getContext() { + return context; + } + + public void setContext(final String context) { + this.context = context; + } + + public long getOwnerOrder() { + return ownerOrder; + } + + public void setOwnerOrder(final long ownerOrder) { + this.ownerOrder = ownerOrder; + } + } diff --git a/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnership.java b/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnership.java index 887fffbed..aafe8f4b1 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnership.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/DomainOwnership.java @@ -57,12 +57,24 @@ import javax.xml.bind.annotation.XmlElement; @NamedQuery( name = "DomainOwnership.findByUuid", query = "SELECT o FROM DomainOwnership o WHERE o.uuid = :uuid" - ) - , + ), @NamedQuery( name = "DomainOwnership.findByOwnerAndDomain", query = "SELECT o FROM DomainOwnership o " - + "WHERE o.owner = :owner AND o.domain = :domain") + + "WHERE o.owner = :owner AND o.domain = :domain" + ), + @NamedQuery( + name = "DomainOwnership.findForDomain", + query = "SELECT o FROM DomainOwnership o " + + "WHERE o.domain = :domain " + + "ORDER BY o.ownerOrder" + ), + @NamedQuery( + name = "DomainOwnershop.countForDomain", + query = "SELECT COUNT(o) " + + "FROM DomainOwnership o " + + "WHERE o.domain = :domain" + ) }) @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "uuid") @@ -77,7 +89,7 @@ public class DomainOwnership implements Serializable, Exportable { @Column(name = "OWNERSHIP_ID") @GeneratedValue(strategy = GenerationType.AUTO) private long ownershipId; - + @Column(name = "UUID", unique = true, nullable = false) @XmlElement(name = "uuid", namespace = CoreConstants.CORE_XML_NS) private String uuid; @@ -132,7 +144,7 @@ public class DomainOwnership implements Serializable, Exportable { protected void setUuid(final String uuid) { this.uuid = uuid; } - + public CcmApplication getOwner() { return owner; } diff --git a/ccm-core/src/main/java/org/libreccm/categorization/DomainRepository.java b/ccm-core/src/main/java/org/libreccm/categorization/DomainRepository.java index 17681c952..2f5b7c3c2 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/DomainRepository.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/DomainRepository.java @@ -27,10 +27,13 @@ import javax.persistence.EntityGraph; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import javax.transaction.Transactional; + import java.net.URI; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.UUID; +import java.util.stream.Stream; /** * A repository for executing CRUD operations on {@link Domain} objects. @@ -55,7 +58,7 @@ public class DomainRepository extends AbstractEntityRepository { public String getIdAttributeName() { return "objectId"; } - + @Override public Long getIdOfEntity(final Domain entity) { return entity.getObjectId(); @@ -138,6 +141,53 @@ public class DomainRepository extends AbstractEntityRepository { } } + public List findOwnersOfDomain( + final Domain domain, final int limit, final int offset + ) { + return getEntityManager() + .createNamedQuery( + "DomainOwnership.findForDomain", DomainOwnership.class + ) + .setParameter( + "domain", + Objects.requireNonNull( + domain, "Can't find owners for domain null." + ) + ) + .setMaxResults(limit) + .setFirstResult(offset) + .getResultList(); + } + + public Stream findOwnersOfDomainAsStream( + final Domain domain, final int limit, final int offset + ) { + return getEntityManager() + .createNamedQuery( + "DomainOwnership.findForDomain", DomainOwnership.class + ) + .setParameter( + "domain", + Objects.requireNonNull( + domain, "Can't find owners for domain null." + ) + ) + .setMaxResults(limit) + .setFirstResult(offset) + .getResultStream(); + } + + public long countOwnersOfDomain(final Domain domain) { + return getEntityManager() + .createNamedQuery("DomainOwnershop.countForDomain", Long.class) + .setParameter( + "domain", + Objects.requireNonNull( + domain, "Can't find owners for domain null." + ) + ).getSingleResult(); + } + public List search(final String term) { final TypedQuery query = getEntityManager() .createNamedQuery("Domain.search", Domain.class);