diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/PostProcessor.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/PostProcessor.java index a472bf6dd..e986b1bc2 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/PostProcessor.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/PostProcessor.java @@ -1,5 +1,7 @@ package org.springframework.data.rest.core; +import org.springframework.http.server.ServerHttpRequest; + /** * Implementations of this interface will post-process objects to mutate them in ways meaningful to the context in * which they are called. @@ -13,6 +15,6 @@ public interface PostProcessor { * * @param obj */ - T postProcess(T obj); + T postProcess(ServerHttpRequest request, T obj); } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java index 5feb1c981..a58e4ab48 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java @@ -20,7 +20,7 @@ import org.springframework.util.StringUtils; * * @author Jon Brisbin */ -public abstract class RepositoryExporter, E extends EntityMetadata> +public abstract class RepositoryExporter, M extends RepositoryMetadata, E extends EntityMetadata> implements ApplicationContextAware, InitializingBean { @@ -49,9 +49,9 @@ public abstract class RepositoryExporter, E exte * @return @this */ @SuppressWarnings({"unchecked"}) - public M setExportOnlyTheseClasses(List exportOnlyTheseClasses) { + public R setExportOnlyTheseClasses(List exportOnlyTheseClasses) { this.exportOnlyTheseClasses = exportOnlyTheseClasses; - return (M)this; + return (R)this; } public Map, Class> getDomainTypeMappings() { @@ -59,13 +59,12 @@ public abstract class RepositoryExporter, E exte } @SuppressWarnings({"unchecked"}) - public M setDomainTypeMappings(Map, Class> domainTypeMappings) { + public R setDomainTypeMappings(Map, Class> domainTypeMappings) { this.domainTypeMappings = domainTypeMappings; - return (M)this; + return (R)this; } - @Override public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java index 1c74ef4a4..89270fbd3 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java @@ -13,7 +13,7 @@ import org.springframework.data.rest.repository.RepositoryExporter; * @author Jon Brisbin */ public class JpaRepositoryExporter - extends RepositoryExporter { + extends RepositoryExporter { protected EntityManager entityManager; diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java index 01117e9fe..4aa54919f 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java @@ -3,9 +3,11 @@ package org.springframework.data.rest.webmvc; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.Assert; @@ -20,17 +22,18 @@ public class RepositoryRestConfiguration { public static final RepositoryRestConfiguration DEFAULT = new RepositoryRestConfiguration(); - private int defaultPageSize = 20; - private String pageParamName = "page"; - private String limitParamName = "limit"; - private String sortParamName = "sort"; - private String jsonpParamName = "callback"; - private String jsonpOnErrParamName = null; - private List> customConverters = Collections.emptyList(); - private Multimap, ResourcePostProcessor> resourcePostProcessors = ArrayListMultimap.create(); - private List responsePostProcessors = Collections.emptyList(); - private MediaType defaultMediaType = MediaType.APPLICATION_JSON; - private boolean dumpErrors = true; + private int defaultPageSize = 20; + private String pageParamName = "page"; + private String limitParamName = "limit"; + private String sortParamName = "sort"; + private String jsonpParamName = "callback"; + private String jsonpOnErrParamName = null; + private List> customConverters = Collections.emptyList(); + private Multimap, ResourcePostProcessor> resourcePostProcessors = ArrayListMultimap.create(); + private List resourceSetPostProcessors = Collections.emptyList(); + private Map, Class> typeMappings = Collections.emptyMap(); + private MediaType defaultMediaType = MediaType.APPLICATION_JSON; + private boolean dumpErrors = true; /** * Get the default size of {@link org.springframework.data.domain.Pageable}s. Default is 20. @@ -143,6 +146,29 @@ public class RepositoryRestConfiguration { return this; } + /** + * Get the list of domain type to repository implementation mappings that will help the exporters narrow down the + * correct {@link org.springframework.data.repository.Repository} to return for a given domain type. + * + * @return + */ + public Map, Class> getDomainTypeToRepositoryMappings() { + return typeMappings; + } + + /** + * Set the list of domain type to repository implementation mappings that will help the exporters narrow down the + * correct {@link org.springframework.data.repository.Repository} to return for a given domain type. + * + * @param typeMappings + * + * @return + */ + public RepositoryRestConfiguration setDomainTypeToRepositoryMappings(Map, Class> typeMappings) { + this.typeMappings = typeMappings; + return this; + } + /** * Get the name of the URL query string parameter that indicates the name of the javascript function to use as the * JSONP wrapper for results. @@ -232,22 +258,25 @@ public class RepositoryRestConfiguration { } /** - * Get the list of {@link ResponsePostProcessor}s that will potentially alter the responses going back to the + * Get the list of {@link ResourceSetPostProcessor}s that will potentially alter the responses going back to the * client. * * @return */ - public List getResponsePostProcessors() { - return responsePostProcessors; + public List getResourceSetPostProcessors() { + return resourceSetPostProcessors; } /** - * Set the list of {@link ResponsePostProcessor}s that will potentially alter the responses going back to the + * Set the list of {@link ResourceSetPostProcessor}s that will potentially alter the responses going back to the * - * @param responsePostProcessors + * @param resourceSetPostProcessors */ - public void setResponsePostProcessors(List responsePostProcessors) { - this.responsePostProcessors = responsePostProcessors; + @Autowired(required = false) + public RepositoryRestConfiguration setResourceSetPostProcessors(List resourceSetPostProcessors) { + Assert.notNull(resourceSetPostProcessors, "ResourceSetPostProcessors cannot be null."); + this.resourceSetPostProcessors = resourceSetPostProcessors; + return this; } /** @@ -259,10 +288,29 @@ public class RepositoryRestConfiguration { * @return */ public RepositoryRestConfiguration addResourcePostProcessor(Class type, ResourcePostProcessor postProcessor) { + Assert.notNull(type, "Type for ResourcePostProcessor cannot be null."); + Assert.notNull(postProcessor, "ResourcePostProcessor for type " + type.getName() + " cannot be null."); resourcePostProcessors.put(type, postProcessor); return this; } + /** + * Set tje {@link ResourcePostProcessor} map used to determine what post-processors to run for which domain type. + * + * @param postProcessors + * + * @return + */ + public RepositoryRestConfiguration setResourcePostProcessors(Map, ResourcePostProcessor> postProcessors) { + if(null == postProcessors) { + return this; + } + for(Map.Entry, ResourcePostProcessor> entry : postProcessors.entrySet()) { + addResourcePostProcessor(entry.getKey(), entry.getValue()); + } + return this; + } + /** * Get the {@link ResourcePostProcessor}s assigned to a particular domain type. * diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java index 50a913f59..093c55258 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java @@ -363,7 +363,12 @@ public class RepositoryRestController } } + // Publish an event that we're about to publish this ResourceSet publishEvent(new BeforeRenderResourcesEvent(request, null, resources)); + // Run any configured post processors + for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { + resources = pp.postProcess(request, resources); + } return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), resources); } @@ -401,7 +406,7 @@ public class RepositoryRestController } Iterator allEntities = Collections.emptyList().iterator(); - final ResourceSet resources; + ResourceSet resources; if(repoMeta.repository() instanceof PagingAndSortingRepository) { PageableResourceSet pr = new PageableResourceSet(); @@ -480,6 +485,10 @@ public class RepositoryRestController } publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, resources)); + // Run any configured post processors + for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { + resources = pp.postProcess(request, resources); + } return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), resources); } @@ -535,6 +544,10 @@ public class RepositoryRestController } publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, resources)); + // Run any configured post processors + for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { + resources = pp.postProcess(request, resources); + } return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), resources); } @@ -715,6 +728,10 @@ public class RepositoryRestController } publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, resources)); + // Run any configured post processors + for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { + resources = pp.postProcess(request, resources); + } return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), resources); } @@ -780,6 +797,10 @@ public class RepositoryRestController body = resource; publishEvent(new BeforeRenderResourceEvent(request, repoMeta, body)); + // Run any post-processors for this domain type + for(ResourcePostProcessor pp : config.getResourcePostProcessors(repoMeta.domainType())) { + body = pp.postProcess(request, body); + } } return negotiateResponse(request, HttpStatus.CREATED, headers, body); @@ -839,13 +860,17 @@ public class RepositoryRestController } URI selfUri = buildUri(baseUri, repository, id); - MapResource res = createResource(repoMeta.rel(), - entity, - repoMeta.entityMetadata(), - selfUri); + Resource res = createResource(repoMeta.rel(), + entity, + repoMeta.entityMetadata(), + selfUri); res.addLink(new ResourceLink(SELF, selfUri)); publishEvent(new BeforeRenderResourceEvent(request, repoMeta, res)); + // Run any post-processors for this domain type + for(ResourcePostProcessor pp : config.getResourcePostProcessors(repoMeta.domainType())) { + res = pp.postProcess(request, res); + } return negotiateResponse(request, HttpStatus.OK, headers, res); } @@ -926,15 +951,19 @@ public class RepositoryRestController Object body = null; if(returnBody(request)) { - MapResource res = createResource(repoMeta.rel(), - savedEntity, - repoMeta.entityMetadata(), - selfUri); + Resource res = createResource(repoMeta.rel(), + savedEntity, + repoMeta.entityMetadata(), + selfUri); res.addLink(new ResourceLink(SELF, selfUri)); body = res; publishEvent(new BeforeRenderResourceEvent(request, repoMeta, body)); + // Run any post-processors for this domain type + for(ResourcePostProcessor pp : config.getResourcePostProcessors(repoMeta.domainType())) { + res = pp.postProcess(request, res); + } } if(!isUpdate) { @@ -1084,6 +1113,10 @@ public class RepositoryRestController } } body = resources; + // Run any post-processors for this domain type + for(ResourceSetPostProcessor pp : config.getResourceSetPostProcessors()) { + resources = pp.postProcess(request, resources); + } } else if(propVal instanceof Map) { propertyRel += "." + propRepoMeta.entityMetadata().type().getSimpleName(); Map resource = new HashMap(); @@ -1098,13 +1131,17 @@ public class RepositoryRestController resource.put(sKey, new ResourceLink(propertyRel, path)); } else { URI selfUri = buildUri(baseUri, propRepoMeta.name(), propValId); - MapResource res = createResource(propRepoMeta.rel(), - entry.getValue(), - propRepoMeta.entityMetadata(), - selfUri); + Resource res = createResource(propRepoMeta.rel(), + entry.getValue(), + propRepoMeta.entityMetadata(), + selfUri); res.addLink(new ResourceLink(SELF, selfUri)); res.addLink(new ResourceLink(propertyRel, path)); resource.put(sKey, res); + // Run any post-processors for this domain type + for(ResourcePostProcessor pp : config.getResourcePostProcessors(propRepoMeta.domainType())) { + res = pp.postProcess(request, res); + } } } body = new MapResource(resource); @@ -1125,6 +1162,10 @@ public class RepositoryRestController res.addLink(new ResourceLink(SELF, selfUri)); body = res; } + // Run any post-processors for this domain type + for(ResourcePostProcessor pp : config.getResourcePostProcessors(propRepoMeta.domainType())) { + body = pp.postProcess(request, (Resource)body); + } } publishEvent(new BeforeRenderResourceEvent(request, propRepoMeta, body)); @@ -1388,14 +1429,18 @@ public class RepositoryRestController String propertyRel = repository + "." + repoMeta.entityMetadata().type().getSimpleName() + "." + property; URI propertyPath = buildUri(baseUri, repository, id, property, linkedId); URI selfUri = buildUri(baseUri, linkedRepoMeta.name(), linkedId); - MapResource res = createResource(linkedRepoMeta.rel(), - linkedEntity, - linkedRepoMeta.entityMetadata(), - selfUri); + Resource res = createResource(linkedRepoMeta.rel(), + linkedEntity, + linkedRepoMeta.entityMetadata(), + selfUri); res.addLink(new ResourceLink(propertyRel, propertyPath)); res.addLink(new ResourceLink(SELF, selfUri)); publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, res)); + // Run any post-processors for this domain type + for(ResourcePostProcessor pp : config.getResourcePostProcessors(linkedRepoMeta.domainType())) { + res = pp.postProcess(request, res); + } HttpHeaders headers = new HttpHeaders(); headers.add("Content-Location", selfUri.toString()); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java index 0acaf2cbe..f688659e0 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java @@ -31,20 +31,20 @@ public class RepositoryRestMvcConfiguration { * {@link org.springframework.data.rest.repository.RepositoryExporter} implementation for exporting JPA repositories. */ @Autowired(required = false) - JpaRepositoryExporter customJpaRepositoryExporter; + protected JpaRepositoryExporter customJpaRepositoryExporter; /** * {@link org.springframework.context.ApplicationListener} implementation for invoking {@link * org.springframework.validation.Validator} instances assigned to specific domain types. */ @Autowired(required = false) - ValidatingRepositoryEventListener validatingRepositoryEventListener; + protected ValidatingRepositoryEventListener validatingRepositoryEventListener; /** * Main configuration for the REST exporter. */ @Autowired(required = false) - RepositoryRestConfiguration repositoryRestConfig = RepositoryRestConfiguration.DEFAULT; + protected RepositoryRestConfiguration repositoryRestConfig; /** * For getting access to the {@link javax.persistence.EntityManagerFactory}. @@ -61,11 +61,9 @@ public class RepositoryRestMvcConfiguration { * @return */ @Bean public JpaRepositoryExporter jpaRepositoryExporter() { - if(null == customJpaRepositoryExporter) { - return new JpaRepositoryExporter(); - } else { - return customJpaRepositoryExporter; - } + return (null == customJpaRepositoryExporter + ? new JpaRepositoryExporter().setDomainTypeMappings(repositoryRestConfiguration().getDomainTypeToRepositoryMappings()) + : customJpaRepositoryExporter); } /** @@ -74,11 +72,9 @@ public class RepositoryRestMvcConfiguration { * @return */ @Bean public ValidatingRepositoryEventListener validatingRepositoryEventListener() { - if(null == validatingRepositoryEventListener) { - return new ValidatingRepositoryEventListener(); - } else { - return validatingRepositoryEventListener; - } + return (null == validatingRepositoryEventListener + ? new ValidatingRepositoryEventListener() + : validatingRepositoryEventListener); } /** @@ -92,7 +88,8 @@ public class RepositoryRestMvcConfiguration { } /** - * A {@link org.springframework.data.rest.core.Resolver} implementation that takes a {@link java.net.URI} and turns it + * A {@link org.springframework.data.rest.core.Resolver} implementation that takes a {@link java.net.URI} and turns + * it * into a top-level domain object. * * @return @@ -101,6 +98,12 @@ public class RepositoryRestMvcConfiguration { return new UriToDomainObjectResolver(); } + @Bean public RepositoryRestConfiguration repositoryRestConfiguration() { + return (null == repositoryRestConfig + ? RepositoryRestConfiguration.DEFAULT + : repositoryRestConfig); + } + /** * The main REST exporter Spring MVC controller. * @@ -113,7 +116,7 @@ public class RepositoryRestMvcConfiguration { } @Bean ResourcesReturnValueHandler resourcesReturnValueHandler() { - return new ResourcesReturnValueHandler(repositoryRestConfig); + return new ResourcesReturnValueHandler(repositoryRestConfiguration()); } /** @@ -123,7 +126,7 @@ public class RepositoryRestMvcConfiguration { * @return */ @Bean public RepositoryRestHandlerAdapter repositoryExporterHandlerAdapter() { - return new RepositoryRestHandlerAdapter(repositoryRestConfig); + return new RepositoryRestHandlerAdapter(repositoryRestConfiguration()); } /** diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResponsePostProcessor.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceSetPostProcessor.java similarity index 82% rename from spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResponsePostProcessor.java rename to spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceSetPostProcessor.java index 541177a78..98e8da043 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResponsePostProcessor.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceSetPostProcessor.java @@ -9,5 +9,5 @@ import org.springframework.data.rest.core.ResourceSet; * * @author Jon Brisbin */ -public interface ResponsePostProcessor extends PostProcessor { +public interface ResourceSetPostProcessor extends PostProcessor { } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ApplicationConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ApplicationConfig.java index 5a2ec6b21..986e87a59 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ApplicationConfig.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/ApplicationConfig.java @@ -1,7 +1,9 @@ package org.springframework.data.rest.test.webmvc; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; @@ -12,9 +14,11 @@ import org.springframework.context.annotation.Import; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.data.rest.webmvc.RepositoryRestConfiguration; +import org.springframework.data.rest.core.Resource; import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration; +import org.springframework.data.rest.webmvc.ResourcePostProcessor; import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.http.server.ServerHttpRequest; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.orm.jpa.JpaDialect; @@ -66,11 +70,6 @@ public class ApplicationConfig { return txManager; } - @Bean public RepositoryRestConfiguration repositoryRestConfiguration() { - return new RepositoryRestConfiguration() - .setJsonpOnErrParamName("errback"); - } - @Bean public TestRepositoryEventListener testRepositoryEventListener() { return new TestRepositoryEventListener(); } @@ -89,4 +88,15 @@ public class ApplicationConfig { return cs; } + @Bean public Map, ResourcePostProcessor> resourcePostProcessors() { + Map, ResourcePostProcessor> resourcePostProcessors = new HashMap, ResourcePostProcessor>(); + resourcePostProcessors.put(Person.class, new ResourcePostProcessor() { + @Override public Resource postProcess(ServerHttpRequest request, Resource r) { + System.out.println(" **** post-processing request: " + request + " with resource: " + r); + return r; + } + }); + return resourcePostProcessors; + } + } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/LoggingResourcePostProcessor.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/LoggingResourcePostProcessor.java new file mode 100644 index 000000000..6e6666f12 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/LoggingResourcePostProcessor.java @@ -0,0 +1,21 @@ +package org.springframework.data.rest.test.webmvc; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.rest.core.Resource; +import org.springframework.data.rest.webmvc.ResourcePostProcessor; +import org.springframework.http.server.ServerHttpRequest; + +/** + * @author Jon Brisbin + */ +public class LoggingResourcePostProcessor implements ResourcePostProcessor { + + private Logger logger = LoggerFactory.getLogger(LoggingResourcePostProcessor.class); + + @Override public Resource postProcess(ServerHttpRequest request, Resource r) { + logger.info(" **** post-processing request: " + request + " with resource: " + r); + return r; + } + +} diff --git a/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml b/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml index 910313d5b..58e676a8c 100644 --- a/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml +++ b/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml @@ -8,7 +8,21 @@ + p:jsonpOnErrParamName="errback"> + + + + + + + + + + + + +