Merge pull request #1750 from 4Science/DS-3593

DS-3593 allow aggregation of endpoints by category / Include UUID in serialization
This commit is contained in:
Andrea Bollini
2017-05-25 18:29:56 +02:00
committed by GitHub
24 changed files with 117 additions and 53 deletions

View File

@@ -49,7 +49,7 @@ import org.springframework.web.bind.annotation.RestController;
* *
*/ */
@RestController @RestController
@RequestMapping("/api/core/{model}") @RequestMapping("/api/{apiCategory}/{model}")
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class RestResourceController implements InitializingBean { public class RestResourceController implements InitializingBean {
@Autowired @Autowired
@@ -65,8 +65,9 @@ public class RestResourceController implements InitializingBean {
// this doesn't work as we don't have an active http request // this doesn't work as we don't have an active http request
// see https://github.com/spring-projects/spring-hateoas/issues/408 // see https://github.com/spring-projects/spring-hateoas/issues/408
// Link l = linkTo(this.getClass(), r).withRel(r); // Link l = linkTo(this.getClass(), r).withRel(r);
String plural = English.plural(r); String[] split = r.split("\\.", 2);
Link l = new Link("/api/core/" + plural, plural); String plural = English.plural(split[1]);
Link l = new Link("/api/" + split[0] + "/" + plural, plural);
links.add(l); links.add(l);
System.out.println(l.getRel() + " " + l.getHref()); System.out.println(l.getRel() + " " + l.getHref());
} }
@@ -75,44 +76,44 @@ public class RestResourceController implements InitializingBean {
@RequestMapping(method = RequestMethod.GET, value = "/{id:\\d+}") @RequestMapping(method = RequestMethod.GET, value = "/{id:\\d+}")
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
DSpaceResource<RestModel> findOne(@PathVariable String model, @PathVariable Integer id, @RequestParam(required=false) String projection) { DSpaceResource<RestModel> findOne(@PathVariable String apiCategory, @PathVariable String model, @PathVariable Integer id, @RequestParam(required=false) String projection) {
return findOneInternal(model, id, projection); return findOneInternal(apiCategory, model, id, projection);
} }
@RequestMapping(method = RequestMethod.GET, value = "/{uuid:[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}}") @RequestMapping(method = RequestMethod.GET, value = "/{uuid:[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}}")
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
DSpaceResource<RestModel> findOne(@PathVariable String model, @PathVariable UUID uuid, @RequestParam(required=false) String projection) { DSpaceResource<RestModel> findOne(@PathVariable String apiCategory, @PathVariable String model, @PathVariable UUID uuid, @RequestParam(required=false) String projection) {
return findOneInternal(model, uuid, projection); return findOneInternal(apiCategory, model, uuid, projection);
} }
private <ID extends Serializable> DSpaceResource<RestModel> findOneInternal(String model, ID id, String projection) { private <ID extends Serializable> DSpaceResource<RestModel> findOneInternal(String apiCategory, String model, ID id, String projection) {
DSpaceRestRepository<RestModel, ID> repository = utils.getResourceRepository(model); DSpaceRestRepository<RestModel, ID> repository = utils.getResourceRepository(apiCategory, model);
RestModel modelObject = null; RestModel modelObject = null;
try { try {
modelObject = repository.findOne(id); modelObject = repository.findOne(id);
} catch (ClassCastException e) { } catch (ClassCastException e) {
} }
if (modelObject == null) { if (modelObject == null) {
throw new ResourceNotFoundException(model + " with id: " + id + " not found"); throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found");
} }
DSpaceResource result = repository.wrapResource(modelObject); DSpaceResource result = repository.wrapResource(modelObject);
return result; return result;
} }
@RequestMapping(method = RequestMethod.GET, value = "/{id:\\d+}/{rel}") @RequestMapping(method = RequestMethod.GET, value = "/{id:\\d+}/{rel}")
ResourceSupport findRel(@PathVariable String model, @PathVariable Integer id, @PathVariable String rel, @RequestParam(required=false) String projection) { ResourceSupport findRel(@PathVariable String apiCategory, @PathVariable String model, @PathVariable Integer id, @PathVariable String rel, @RequestParam(required=false) String projection) {
return findRelInternal(model, id, rel, projection); return findRelInternal(apiCategory, model, id, rel, projection);
} }
@RequestMapping(method = RequestMethod.GET, value = "/{uuid:[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}}/{rel}") @RequestMapping(method = RequestMethod.GET, value = "/{uuid:[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}}/{rel}")
ResourceSupport findRel(@PathVariable String model, @PathVariable UUID uuid, @PathVariable String rel, @RequestParam(required=false) String projection) { ResourceSupport findRel(@PathVariable String apiCategory, @PathVariable String model, @PathVariable UUID uuid, @PathVariable String rel, @RequestParam(required=false) String projection) {
return findRelInternal(model, uuid, rel, projection); return findRelInternal(apiCategory, model, uuid, rel, projection);
} }
private <ID extends Serializable> ResourceSupport findRelInternal(String model, ID uuid, String rel, String projection) { private <ID extends Serializable> ResourceSupport findRelInternal(String apiCategory, String model, ID uuid, String rel, String projection) {
// FIXME this is a very bad implementation as it leads most of times to // FIXME this is a very bad implementation as it leads most of times to
// more round-trip on the database and retrieval of unneeded infromation // more round-trip on the database and retrieval of unneeded infromation
DSpaceRestRepository<RestModel, ID> repository = utils.getResourceRepository(model); DSpaceRestRepository<RestModel, ID> repository = utils.getResourceRepository(apiCategory, model);
RestModel modelObject = repository.findOne(uuid); RestModel modelObject = repository.findOne(uuid);
DSpaceResource result = repository.wrapResource(modelObject, rel); DSpaceResource result = repository.wrapResource(modelObject, rel);
if (result.getLink(rel) == null) { if (result.getLink(rel) == null) {
@@ -126,15 +127,14 @@ public class RestResourceController implements InitializingBean {
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
<T extends RestModel> PagedResources<DSpaceResource<T>> findAll(@PathVariable String model, Pageable page, PagedResourcesAssembler assembler, @RequestParam(required=false) String projection) { <T extends RestModel> PagedResources<DSpaceResource<T>> findAll(@PathVariable String apiCategory, @PathVariable String model, Pageable page, PagedResourcesAssembler assembler, @RequestParam(required=false) String projection) {
DSpaceRestRepository<T, ?> repository = utils.getResourceRepository(model); DSpaceRestRepository<T, ?> repository = utils.getResourceRepository(apiCategory, model);
// Link link = entityLinks.linkFor(getResourceClass(model), model, page).withSelfRel(); // Link link = entityLinks.linkFor(getResourceClass(model), model, page).withSelfRel();
Link link = linkTo(this.getClass(), model).withSelfRel(); Link link = linkTo(this.getClass(), apiCategory, model).withSelfRel();
Page<DSpaceResource<T>> resources; Page<DSpaceResource<T>> resources;
try { try {
resources = repository.findAll(page).map(repository::wrapResource); resources = repository.findAll(page).map(repository::wrapResource);
// resources.forEach(r -> {
// Link linkToSingleResource = Utils.linkToSingleResource(r, Link.REL_SELF); // Link linkToSingleResource = Utils.linkToSingleResource(r, Link.REL_SELF);
// r.add(linkToSingleResource); // r.add(linkToSingleResource);
// }); // });

View File

@@ -18,9 +18,11 @@ import org.springframework.web.bind.annotation.ResponseStatus;
*/ */
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "This endpoint is not found in the system") @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "This endpoint is not found in the system")
public class RepositoryNotFoundException extends RuntimeException { public class RepositoryNotFoundException extends RuntimeException {
String apiCategory;
String model; String model;
public RepositoryNotFoundException(String model) { public RepositoryNotFoundException(String apiCategory, String model) {
this.apiCategory = apiCategory;
this.model = model; this.model = model;
} }

View File

@@ -21,7 +21,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
*/ */
public class BitstreamFormatRest extends BaseObjectRest<Integer> { public class BitstreamFormatRest extends BaseObjectRest<Integer> {
public static final String NAME = "bitstreamformat"; public static final String NAME = "bitstreamformat";
public static final String CATEGORY = RestModel.CORE;
private String shortDescription; private String shortDescription;
private String description; private String description;
@@ -82,6 +84,12 @@ public class BitstreamFormatRest extends BaseObjectRest<Integer> {
this.extensions = extensions; this.extensions = extensions;
} }
@JsonIgnore
@Override
public String getCategory() {
return CATEGORY;
}
@Override @Override
public String getType() { public String getType() {
return NAME; return NAME;

View File

@@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonProperty.Access;
*/ */
public class BitstreamRest extends DSpaceObjectRest { public class BitstreamRest extends DSpaceObjectRest {
public static final String NAME = "bitstream"; public static final String NAME = "bitstream";
public static final String CATEGORY = RestModel.CORE;
private String bundleName; private String bundleName;
// avoid to serialize this object inline as we want a full resource embedded // avoid to serialize this object inline as we want a full resource embedded
@@ -68,6 +69,11 @@ public class BitstreamRest extends DSpaceObjectRest {
this.sequenceId = sequenceId; this.sequenceId = sequenceId;
} }
@Override
public String getCategory() {
return CATEGORY;
}
@Override @Override
public String getType() { public String getType() {
return NAME; return NAME;

View File

@@ -15,7 +15,13 @@ package org.dspace.app.rest.model;
*/ */
public class CollectionRest extends DSpaceObjectRest { public class CollectionRest extends DSpaceObjectRest {
public static final String NAME = "collection"; public static final String NAME = "collection";
public static final String CATEGORY = RestModel.CORE;
@Override
public String getCategory() {
return CATEGORY;
}
@Override @Override
public String getType() { public String getType() {
return NAME; return NAME;

View File

@@ -15,7 +15,13 @@ package org.dspace.app.rest.model;
*/ */
public class CommunityRest extends DSpaceObjectRest { public class CommunityRest extends DSpaceObjectRest {
public static final String NAME = "community"; public static final String NAME = "community";
public static final String CATEGORY = RestModel.CORE;
@Override
public String getCategory() {
return CATEGORY;
}
@Override @Override
public String getType() { public String getType() {
return NAME; return NAME;

View File

@@ -11,8 +11,6 @@ import java.util.List;
import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.RestResourceController;
import com.fasterxml.jackson.annotation.JsonIgnore;
/** /**
* Base REST representation for all the DSpaceObjects * Base REST representation for all the DSpaceObjects
* *
@@ -20,17 +18,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
* *
*/ */
public abstract class DSpaceObjectRest extends BaseObjectRest<String> { public abstract class DSpaceObjectRest extends BaseObjectRest<String> {
@JsonIgnore
private String uuid; private String uuid;
private String name; private String name;
private String handle; private String handle;
private String type;
List<MetadataEntryRest> metadata; List<MetadataEntryRest> metadata;
@Override @Override
@JsonIgnore
public String getId() { public String getId() {
return uuid; return uuid;
} }
@@ -68,7 +63,6 @@ public abstract class DSpaceObjectRest extends BaseObjectRest<String> {
} }
@Override @Override
@JsonIgnore
public Class getController() { public Class getController() {
return RestResourceController.class; return RestResourceController.class;
} }

View File

@@ -12,8 +12,6 @@ import java.util.List;
import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.RestResourceController;
import com.fasterxml.jackson.annotation.JsonIgnore;
/** /**
* The EPerson REST Resource * The EPerson REST Resource
* *
@@ -22,7 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
*/ */
public class EPersonRest extends DSpaceObjectRest { public class EPersonRest extends DSpaceObjectRest {
public static final String NAME = "eperson"; public static final String NAME = "eperson";
public static final String CATEGORY = RestModel.EPERSON;
private String netid; private String netid;
private Date lastActive; private Date lastActive;
@@ -102,7 +100,11 @@ public class EPersonRest extends DSpaceObjectRest {
} }
@Override @Override
@JsonIgnore public String getCategory() {
return CATEGORY;
}
@Override
public Class getController() { public Class getController() {
return RestResourceController.class; return RestResourceController.class;
} }

View File

@@ -21,6 +21,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
*/ */
public class GroupRest extends DSpaceObjectRest { public class GroupRest extends DSpaceObjectRest {
public static final String NAME = "group"; public static final String NAME = "group";
public static final String CATEGORY = RestModel.EPERSON;
private String name; private String name;
@@ -31,6 +33,11 @@ public class GroupRest extends DSpaceObjectRest {
// https://jira.duraspace.org/browse/DS-3483 // https://jira.duraspace.org/browse/DS-3483
private List<GroupRest> groups; private List<GroupRest> groups;
@Override
public String getCategory() {
return CATEGORY;
}
@Override @Override
public String getType() { public String getType() {
return NAME; return NAME;

View File

@@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
*/ */
public class ItemRest extends DSpaceObjectRest { public class ItemRest extends DSpaceObjectRest {
public static final String NAME = "item"; public static final String NAME = "item";
public static final String CATEGORY = RestModel.CORE;
private boolean inArchive = false; private boolean inArchive = false;
private boolean discoverable = false; private boolean discoverable = false;
private boolean withdrawn = false; private boolean withdrawn = false;
@@ -32,6 +33,11 @@ public class ItemRest extends DSpaceObjectRest {
List<BitstreamRest> bitstreams; List<BitstreamRest> bitstreams;
@Override
public String getCategory() {
return CATEGORY;
}
@Override @Override
public String getType() { public String getType() {
return NAME; return NAME;

View File

@@ -19,7 +19,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
*/ */
public class MetadataFieldRest extends BaseObjectRest<Integer> { public class MetadataFieldRest extends BaseObjectRest<Integer> {
public static final String NAME = "metadatafield"; public static final String NAME = "metadatafield";
public static final String CATEGORY = RestModel.CORE;
@JsonIgnore @JsonIgnore
private MetadataSchemaRest schema; private MetadataSchemaRest schema;
@@ -67,8 +68,12 @@ public class MetadataFieldRest extends BaseObjectRest<Integer> {
} }
@Override @Override
@JsonIgnore
public Class getController() { public Class getController() {
return RestResourceController.class; return RestResourceController.class;
} }
@Override
public String getCategory() {
return CATEGORY;
}
} }

View File

@@ -21,7 +21,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
*/ */
public class MetadataSchemaRest extends BaseObjectRest<Integer> { public class MetadataSchemaRest extends BaseObjectRest<Integer> {
public static final String NAME = "metadataschema"; public static final String NAME = "metadataschema";
public static final String CATEGORY = RestModel.CORE;
private String prefix; private String prefix;
private String namespace; private String namespace;
@@ -48,8 +49,12 @@ public class MetadataSchemaRest extends BaseObjectRest<Integer> {
} }
@Override @Override
@JsonIgnore
public Class getController() { public Class getController() {
return RestResourceController.class; return RestResourceController.class;
} }
@Override
public String getCategory() {
return CATEGORY;
}
} }

View File

@@ -7,6 +7,8 @@
*/ */
package org.dspace.app.rest.model; package org.dspace.app.rest.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
/** /**
* Methods to implement to make a REST resource addressable * Methods to implement to make a REST resource addressable
* *
@@ -14,7 +16,15 @@ package org.dspace.app.rest.model;
* *
*/ */
public interface RestModel { public interface RestModel {
public static final String CORE = "core";
public static final String EPERSON = "eperson";
public static final String DISCOVER = "discover";
@JsonIgnore
public String getCategory();
public String getType(); public String getType();
@JsonIgnore
public Class getController(); public Class getController();
} }

View File

@@ -15,7 +15,13 @@ package org.dspace.app.rest.model;
*/ */
public class SiteRest extends DSpaceObjectRest { public class SiteRest extends DSpaceObjectRest {
public static final String NAME = "site"; public static final String NAME = "site";
public static final String CATEGORY = RestModel.CORE;
@Override
public String getCategory() {
return CATEGORY;
}
@Override @Override
public String getType() { public String getType() {
return NAME; return NAME;

View File

@@ -55,11 +55,12 @@ public abstract class DSpaceResource<T extends RestModel> extends ResourceSuppor
RestModel linkedObject = (RestModel) readMethod.invoke(data); RestModel linkedObject = (RestModel) readMethod.invoke(data);
if (linkedObject != null) { if (linkedObject != null) {
embedded.put(pd.getName(), embedded.put(pd.getName(),
utils.getResourceRepository(linkedObject.getType()).wrapResource(linkedObject)); utils.getResourceRepository(linkedObject.getCategory(), linkedObject.getType())
.wrapResource(linkedObject));
} else { } else {
embedded.put(pd.getName(), null); embedded.put(pd.getName(), null);
} }
Method writeMethod = pd.getWriteMethod(); Method writeMethod = pd.getWriteMethod();
writeMethod.invoke(data, new Object[] { null }); writeMethod.invoke(data, new Object[] { null });
} }

View File

@@ -28,7 +28,7 @@ import org.springframework.stereotype.Component;
* @author Andrea Bollini (andrea.bollini at 4science.it) * @author Andrea Bollini (andrea.bollini at 4science.it)
* *
*/ */
@Component(BitstreamFormatRest.NAME) @Component(BitstreamFormatRest.CATEGORY + "." + BitstreamFormatRest.NAME)
public class BitstreamFormatRestRepository extends DSpaceRestRepository<BitstreamFormatRest, Integer> { public class BitstreamFormatRestRepository extends DSpaceRestRepository<BitstreamFormatRest, Integer> {
BitstreamFormatService bfs = ContentServiceFactory.getInstance().getBitstreamFormatService(); BitstreamFormatService bfs = ContentServiceFactory.getInstance().getBitstreamFormatService();
@Autowired @Autowired

View File

@@ -33,7 +33,7 @@ import org.springframework.stereotype.Component;
* *
*/ */
@Component(BitstreamRest.NAME) @Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME)
public class BitstreamRestRepository extends DSpaceRestRepository<BitstreamRest, UUID> { public class BitstreamRestRepository extends DSpaceRestRepository<BitstreamRest, UUID> {
BitstreamService bs = ContentServiceFactory.getInstance().getBitstreamService(); BitstreamService bs = ContentServiceFactory.getInstance().getBitstreamService();
@Autowired @Autowired

View File

@@ -32,7 +32,7 @@ import org.springframework.stereotype.Component;
* *
*/ */
@Component(CollectionRest.NAME) @Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME)
public class CollectionRestRepository extends DSpaceRestRepository<CollectionRest, UUID> { public class CollectionRestRepository extends DSpaceRestRepository<CollectionRest, UUID> {
CollectionService cs = ContentServiceFactory.getInstance().getCollectionService(); CollectionService cs = ContentServiceFactory.getInstance().getCollectionService();
@Autowired @Autowired

View File

@@ -32,7 +32,7 @@ import org.springframework.stereotype.Component;
* *
*/ */
@Component(CommunityRest.NAME) @Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME)
public class CommunityRestRepository extends DSpaceRestRepository<CommunityRest, UUID> { public class CommunityRestRepository extends DSpaceRestRepository<CommunityRest, UUID> {
CommunityService cs = ContentServiceFactory.getInstance().getCommunityService(); CommunityService cs = ContentServiceFactory.getInstance().getCommunityService();
@Autowired @Autowired

View File

@@ -31,7 +31,7 @@ import org.springframework.stereotype.Component;
* *
*/ */
@Component(EPersonRest.NAME) @Component(EPersonRest.CATEGORY + "." + EPersonRest.NAME)
public class EPersonRestRepository extends DSpaceRestRepository<EPersonRest, UUID> { public class EPersonRestRepository extends DSpaceRestRepository<EPersonRest, UUID> {
EPersonService es = EPersonServiceFactory.getInstance().getEPersonService(); EPersonService es = EPersonServiceFactory.getInstance().getEPersonService();

View File

@@ -31,7 +31,7 @@ import org.springframework.stereotype.Component;
* *
*/ */
@Component(GroupRest.NAME) @Component(GroupRest.CATEGORY + "." + GroupRest.NAME)
public class GroupRestRepository extends DSpaceRestRepository<GroupRest, UUID> { public class GroupRestRepository extends DSpaceRestRepository<GroupRest, UUID> {
GroupService gs = EPersonServiceFactory.getInstance().getGroupService(); GroupService gs = EPersonServiceFactory.getInstance().getGroupService();

View File

@@ -33,7 +33,7 @@ import org.springframework.stereotype.Component;
* *
*/ */
@Component(ItemRest.NAME) @Component(ItemRest.CATEGORY + "." + ItemRest.NAME)
public class ItemRestRepository extends DSpaceRestRepository<ItemRest, UUID> { public class ItemRestRepository extends DSpaceRestRepository<ItemRest, UUID> {
ItemService is = ContentServiceFactory.getInstance().getItemService(); ItemService is = ContentServiceFactory.getInstance().getItemService();
@Autowired @Autowired

View File

@@ -32,7 +32,7 @@ import org.springframework.stereotype.Component;
* *
*/ */
@Component(SiteRest.NAME) @Component(SiteRest.CATEGORY + "." + SiteRest.NAME)
public class SiteRestRepository extends DSpaceRestRepository<SiteRest, UUID> { public class SiteRestRepository extends DSpaceRestRepository<SiteRest, UUID> {
SiteService sitesv = ContentServiceFactory.getInstance().getSiteService(); SiteService sitesv = ContentServiceFactory.getInstance().getSiteService();
@Autowired @Autowired

View File

@@ -59,19 +59,19 @@ public class Utils {
} }
public Link linkToSingleResource(RestModel data, String rel) { public Link linkToSingleResource(RestModel data, String rel) {
return linkTo(data.getController(), data.getType()).slash(data).withRel(rel); return linkTo(data.getController(), data.getCategory(), data.getType()).slash(data).withRel(rel);
} }
public Link linkToSubResource(RestModel data, String rel) { public Link linkToSubResource(RestModel data, String rel) {
return linkTo(data.getController(), data.getType()).slash(data).slash(rel).withRel(rel); return linkTo(data.getController(), data.getCategory(), data.getType()).slash(data).slash(rel).withRel(rel);
} }
public DSpaceRestRepository getResourceRepository(String modelPlural) { public DSpaceRestRepository getResourceRepository(String apiCategory, String modelPlural) {
String model = makeSingular(modelPlural); String model = makeSingular(modelPlural);
try { try {
return applicationContext.getBean(model, DSpaceRestRepository.class); return applicationContext.getBean(apiCategory + "." + model, DSpaceRestRepository.class);
} catch (NoSuchBeanDefinitionException e) { } catch (NoSuchBeanDefinitionException e) {
throw new RepositoryNotFoundException(model); throw new RepositoryNotFoundException(apiCategory, model);
} }
} }