diff --git a/src/main/java/org/gbif/ipt/action/portal/ResourceAction.java b/src/main/java/org/gbif/ipt/action/portal/ResourceAction.java index cf90102da1..034a53d2c9 100644 --- a/src/main/java/org/gbif/ipt/action/portal/ResourceAction.java +++ b/src/main/java/org/gbif/ipt/action/portal/ResourceAction.java @@ -267,6 +267,14 @@ public String rss() { return SUCCESS; } + public String apiResources() { + resources = resourceManager.list(PublicationStatus.PUBLIC); + return SUCCESS; + } + + public String apiResource() { + return detail(); + } /** * Finish loading all details shown on resource homepage. * diff --git a/src/main/java/org/gbif/ipt/config/AppConfig.java b/src/main/java/org/gbif/ipt/config/AppConfig.java index 649adf1738..08942c0c6a 100644 --- a/src/main/java/org/gbif/ipt/config/AppConfig.java +++ b/src/main/java/org/gbif/ipt/config/AppConfig.java @@ -259,6 +259,18 @@ public URI getResourceUri(@NotNull String shortname) { return UriBuilder.fromPath(getBaseUrl()).path(Constants.REQ_PATH_RESOURCE) .queryParam(Constants.REQ_PARAM_RESOURCE, shortname).build(); } + /** + * @return URI to resource default homepage (no version number) used in DOI registration + */ + @NotNull + public URI getResourceApiUrl(@NotNull String shortname) { + Preconditions.checkNotNull(getBaseUrl()); + + return UriBuilder.fromPath(getBaseUrl()) + .path(Constants.REQ_PATH_API) + .path("resource") + .queryParam(Constants.REQ_PARAM_RESOURCE, shortname).build(); + } /** * @return String URI used as resource EML GUID, similar to resource homepage URI but with id param versus r param diff --git a/src/main/java/org/gbif/ipt/config/Constants.java b/src/main/java/org/gbif/ipt/config/Constants.java index ba39acde69..e340480c54 100644 --- a/src/main/java/org/gbif/ipt/config/Constants.java +++ b/src/main/java/org/gbif/ipt/config/Constants.java @@ -15,6 +15,7 @@ public final class Constants { public static final String SESSION_FILE_NAME = "fileName"; public static final String SESSION_FILE_CONTENT_TYPE = "contentType"; public static final String REQ_PATH_RESOURCE = "resource"; + public static final String REQ_PATH_API = "api"; public static final String REQ_PATH_EML = "eml.do"; public static final String REQ_PATH_DWCA = "archive.do"; public static final String REQ_PATH_LOGO = "logo.do"; diff --git a/src/main/java/org/gbif/ipt/model/Resource.java b/src/main/java/org/gbif/ipt/model/Resource.java index d3e3e29e2a..53962bdeb4 100644 --- a/src/main/java/org/gbif/ipt/model/Resource.java +++ b/src/main/java/org/gbif/ipt/model/Resource.java @@ -1,33 +1,27 @@ package org.gbif.ipt.model; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; import org.gbif.api.model.common.DOI; -import org.gbif.dwc.terms.Term; -import org.gbif.dwc.terms.TermFactory; +import org.gbif.dwc.terms.*; +import org.gbif.dwca.io.ArchiveField; import org.gbif.ipt.config.Constants; import org.gbif.ipt.model.voc.IdentifierStatus; import org.gbif.ipt.model.voc.PublicationMode; import org.gbif.ipt.model.voc.PublicationStatus; import org.gbif.ipt.service.AlreadyExistingException; import org.gbif.ipt.utils.ResourceUtils; -import org.gbif.metadata.eml.Agent; -import org.gbif.metadata.eml.Citation; -import org.gbif.metadata.eml.Eml; -import org.gbif.metadata.eml.MaintenanceUpdateFrequency; +import org.gbif.metadata.eml.*; +import java.io.File; import java.io.Serializable; import java.math.BigDecimal; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; import javax.annotation.Nullable; import javax.validation.constraints.NotNull; @@ -41,6 +35,7 @@ import com.google.common.collect.Sets; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; +import org.gbif.metadata.eml.Collection; import static com.google.common.base.Objects.equal; @@ -1258,4 +1253,112 @@ public synchronized void updateCitationIdentifierForDOI() { public boolean hasOccurrenceMapping() { return !getMappings(Constants.DWC_ROWTYPE_OCCURRENCE).isEmpty(); } + + /** + * Return JSON representation of object + * @return string + */ + public String toJSONSimple() { + //return (new GsonBuilder().create()).toJson(this); + // Using a allow first approach to avoid recursive classes + Class[] classes = new Class[]{ + String.class + ,Date.class + ,BigDecimal.class + ,Resource.class + ,Collection.class + }; + final Set allowedClasses = new HashSet(Arrays.asList(classes)); + return (new GsonBuilder().addSerializationExclusionStrategy(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return fieldAttributes.getName().equals("resource"); + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return !allowedClasses.contains(aClass); + } + }).create()).toJson(this); + } + + /** + * Return JSON representation of object + * @return string + */ + public String toJSON() { + //return (new GsonBuilder().create()).toJson(this); + // Using a allow first approach to avoid recursive classes + Class[] classes = new Class[]{ + String.class + ,Date.class + ,BigDecimal.class + ,Resource.class + ,Eml.class + ,KeywordSet.class + ,Agent.class + ,UserId.class + ,UUID.class + ,User.class + ,BibliographicCitationSet.class + ,Citation.class + ,Collection.class + ,Project.class + ,PhysicalData.class + ,MaintenanceUpdateFrequency.class + ,PublicationMode.class + ,PublicationStatus.class + ,TemporalCoverage.class + ,TaxonomicCoverage.class + ,GeospatialCoverage.class + ,TaxonKeyword.class + ,Organisation.class + ,IdentifierStatus.class + ,User.class + ,List.class + ,Set.class + ,Source.class + ,SourceBase.class + ,SqlSource.class + ,FileSource.class + ,TextFileSource.class + ,ExcelFileSource.class + ,File.class + ,Source.class + ,ExtensionMapping.class + ,VersionHistory.class + ,PropertyMapping.class + ,Map.class + ,ArchiveField.class + ,Integer.class + ,Boolean.class + ,Term.class + ,VocabularyTerm.class + ,AcTerm.class + ,EolReferenceTerm.class + ,IucnTerm.class + ,GbifInternalTerm.class + ,GbifTerm.class + ,XmpTerm.class + ,XmpRightsTerm.class + ,UnknownTerm.class + ,DcTerm.class + ,DwcTerm.class + ,DcElement.class + ,UnknownTerm.class + ,ArchiveField.DataType.class + }; + final Set allowedClasses = new HashSet(Arrays.asList(classes)); + return (new GsonBuilder().addSerializationExclusionStrategy(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return fieldAttributes.getName().equals("resource"); + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return !allowedClasses.contains(aClass); + } + }).create()).toJson(this); + } } \ No newline at end of file diff --git a/src/main/resources/struts-api.xml b/src/main/resources/struts-api.xml new file mode 100644 index 0000000000..cdef8b67cf --- /dev/null +++ b/src/main/resources/struts-api.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + /WEB-INF/pages/api/404.ftl + + + /WEB-INF/pages/api/410.ftl + + + + + + /WEB-INF/pages/api/resources.ftl + application/json + + + + + + /WEB-INF/pages/api/resource.ftl + application/json + + + + diff --git a/src/main/resources/struts.xml b/src/main/resources/struts.xml index 4ccebf0fcd..f1750e9798 100644 --- a/src/main/resources/struts.xml +++ b/src/main/resources/struts.xml @@ -120,7 +120,7 @@ /setup.do ${baseURL}/login.do ${baseURL}/home.do - + ${baseURL}/api/resources @@ -188,4 +188,5 @@ + diff --git a/src/main/webapp/WEB-INF/pages/api/404.ftl b/src/main/webapp/WEB-INF/pages/api/404.ftl new file mode 100644 index 0000000000..d6b91ded49 --- /dev/null +++ b/src/main/webapp/WEB-INF/pages/api/404.ftl @@ -0,0 +1,6 @@ +{"ipt":{ +"name":"${ipt.name!}", +"link":"${baseURL!}", +"root":"${baseURL!}/${REQ_PATH_API!"api"}" +} +,"error":"not found"} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/pages/api/410.ftl b/src/main/webapp/WEB-INF/pages/api/410.ftl new file mode 100644 index 0000000000..cf1a0aebee --- /dev/null +++ b/src/main/webapp/WEB-INF/pages/api/410.ftl @@ -0,0 +1,6 @@ +{"ipt":{ +"name":"${ipt.name!}", +"link":"${baseURL!}", +"root":"${baseURL!}/${REQ_PATH_API!"api"}" +} +,"error":"not authorized"} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/pages/api/resource.ftl b/src/main/webapp/WEB-INF/pages/api/resource.ftl new file mode 100644 index 0000000000..176301a166 --- /dev/null +++ b/src/main/webapp/WEB-INF/pages/api/resource.ftl @@ -0,0 +1,13 @@ +{"ipt":{ + "name":"${ipt.name!}", + "link":"${baseURL!}", + "root":"${baseURL!}/${REQ_PATH_API!"api"}/resources" +}, +"resource": {"data":${resource.toJSON()} + ,"links":{ + "self":"${cfg.getResourceApiUrl(resource.shortname)}" + ,"dwca":"${cfg.getResourceArchiveUrl(resource.shortname)}" + ,"eml":"${cfg.getResourceEmlUrl(resource.shortname)}" + ,"resource":"${cfg.getResourceUrl(resource.shortname)}" + }} +} diff --git a/src/main/webapp/WEB-INF/pages/api/resources.ftl b/src/main/webapp/WEB-INF/pages/api/resources.ftl new file mode 100644 index 0000000000..e37f0abc98 --- /dev/null +++ b/src/main/webapp/WEB-INF/pages/api/resources.ftl @@ -0,0 +1,17 @@ +{"ipt":{ +"name":"${ipt.name!}", +"link":"${baseURL!}", +"root":"${baseURL!}/${REQ_PATH_API!"api"}/resources" +}, +"resources": [ + <#list resources as resource> + {"data":${resource.toJSONSimple()} + ,"links":{ + "self":"${cfg.getResourceApiUrl(resource.shortname)}" + ,"dwca":"${cfg.getResourceArchiveUrl(resource.shortname)}" + ,"eml":"${cfg.getResourceEmlUrl(resource.shortname)}" + ,"resource":"${cfg.getResourceUrl(resource.shortname)}" + }}<#sep>, + + ] +}