/* * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.rest.core.config; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy; import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies; import org.springframework.data.rest.core.support.EntityLookup; import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.servlet.config.annotation.CorsRegistration; /** * Spring Data REST configuration options. * * @author Jon Brisbin * @author Oliver Gierke * @author Jeremy Rickard * @author Greg Turnquist * @author Mark Paluch */ @SuppressWarnings("deprecation") public class RepositoryRestConfiguration { private static final URI NO_URI = URI.create(""); private URI baseUri = NO_URI; private URI basePath = NO_URI; private int defaultPageSize = 20; private int maxPageSize = 1000; private String pageParamName = "page"; private String limitParamName = "size"; private String sortParamName = "sort"; private MediaType defaultMediaType = MediaTypes.HAL_JSON; private boolean useHalAsDefaultJsonMediaType = true; private Boolean returnBodyOnCreate = null; private Boolean returnBodyOnUpdate = null; private List<Class<?>> exposeIdsFor = new ArrayList<Class<?>>(); private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration(); private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration(); private RepositoryDetectionStrategy repositoryDetectionStrategy = RepositoryDetectionStrategies.DEFAULT; private final RepositoryCorsRegistry corsRegistry = new RepositoryCorsRegistry(); private final ProjectionDefinitionConfiguration projectionConfiguration; private final MetadataConfiguration metadataConfiguration; private final EntityLookupConfiguration entityLookupConfiguration; private final EnumTranslationConfiguration enumTranslationConfiguration; private boolean enableEnumTranslation = false; /** * Creates a new {@link RepositoryRestConfiguration} with the given {@link ProjectionDefinitionConfiguration}. * * @param projectionConfiguration must not be {@literal null}. * @param metadataConfiguration must not be {@literal null}. * @param enumTranslationConfiguration must not be {@literal null}. */ public RepositoryRestConfiguration(ProjectionDefinitionConfiguration projectionConfiguration, MetadataConfiguration metadataConfiguration, EnumTranslationConfiguration enumTranslationConfiguration) { Assert.notNull(projectionConfiguration, "ProjectionDefinitionConfiguration must not be null!"); Assert.notNull(metadataConfiguration, "MetadataConfiguration must not be null!"); Assert.notNull(enumTranslationConfiguration, "EnumTranslationConfiguration must not be null!"); this.projectionConfiguration = projectionConfiguration; this.metadataConfiguration = metadataConfiguration; this.enumTranslationConfiguration = enumTranslationConfiguration; this.entityLookupConfiguration = new EntityLookupConfiguration(); } /** * The base URI against which the exporter should calculate its links. * * @return The base URI. */ public URI getBaseUri() { return basePath != NO_URI ? basePath : baseUri; } /** * The base path to expose repository resources under. * * @return the basePath */ public URI getBasePath() { return basePath; } /** * Configures the base path to be used by Spring Data REST to expose repository resources. * * @param basePath the basePath to set */ public RepositoryRestConfiguration setBasePath(String basePath) { Assert.isTrue(!basePath.startsWith("http"), "Use a path not a URI"); basePath = StringUtils.trimTrailingCharacter(basePath, '/'); this.basePath = URI.create(basePath.startsWith("/") ? basePath : "/".concat(basePath)); Assert.isTrue(!this.basePath.isAbsolute(), "Absolute URIs are not supported as base path!"); return this; } /** * Get the default size of {@link org.springframework.data.domain.Pageable}s. Default is 20. * * @return The default page size. */ public int getDefaultPageSize() { return defaultPageSize; } /** * Set the default size of {@link org.springframework.data.domain.Pageable}s. * * @param defaultPageSize The default page size. * @return {@literal this} */ public RepositoryRestConfiguration setDefaultPageSize(int defaultPageSize) { Assert.isTrue(defaultPageSize > 0, "Page size must be greater than 0."); this.defaultPageSize = defaultPageSize; return this; } /** * Get the maximum size of pages. * * @return Maximum page size. */ public int getMaxPageSize() { return maxPageSize; } /** * Set the maximum size of pages. * * @param maxPageSize Maximum page size. * @return {@literal this} */ public RepositoryRestConfiguration setMaxPageSize(int maxPageSize) { Assert.isTrue(defaultPageSize > 0, "Maximum page size must be greater than 0."); this.maxPageSize = maxPageSize; return this; } /** * Get the name of the URL query string parameter that indicates what page to return. Default is 'page'. * * @return Name of the query parameter used to indicate the page number to return. */ public String getPageParamName() { return pageParamName; } /** * Set the name of the URL query string parameter that indicates what page to return. * * @param pageParamName Name of the query parameter used to indicate the page number to return. * @return {@literal this} */ public RepositoryRestConfiguration setPageParamName(String pageParamName) { Assert.notNull(pageParamName, "Page param name cannot be null."); this.pageParamName = pageParamName; return this; } /** * Get the name of the URL query string parameter that indicates how many results to return at once. Default is * 'limit'. * * @return Name of the query parameter used to indicate the maximum number of entries to return at a time. */ public String getLimitParamName() { return limitParamName; } /** * Set the name of the URL query string parameter that indicates how many results to return at once. * * @param limitParamName Name of the query parameter used to indicate the maximum number of entries to return at a * time. * @return {@literal this} */ public RepositoryRestConfiguration setLimitParamName(String limitParamName) { Assert.notNull(limitParamName, "Limit param name cannot be null."); this.limitParamName = limitParamName; return this; } /** * Get the name of the URL query string parameter that indicates what direction to sort results. Default is 'sort'. * * @return Name of the query string parameter used to indicate what field to sort on. */ public String getSortParamName() { return sortParamName; } /** * Set the name of the URL query string parameter that indicates what direction to sort results. * * @param sortParamName Name of the query string parameter used to indicate what field to sort on. * @return {@literal this} */ public RepositoryRestConfiguration setSortParamName(String sortParamName) { Assert.notNull(sortParamName, "Sort param name cannot be null."); this.sortParamName = sortParamName; return this; } /** * Get the {@link MediaType} to use as a default when none is specified. * * @return Default content type if none has been specified. */ public MediaType getDefaultMediaType() { return defaultMediaType; } /** * Set the {@link MediaType} to use as a default when none is specified. * * @param defaultMediaType default content type if none has been specified. * @return {@literal this} */ public RepositoryRestConfiguration setDefaultMediaType(MediaType defaultMediaType) { this.defaultMediaType = defaultMediaType; return this; } /** * Returns whether HAL will be served as primary representation in case on {@code application/json} is requested. This * defaults to {@literal true}. If configured to {@literal false} the legacy Spring Data representation will be * rendered. * * @return */ public boolean useHalAsDefaultJsonMediaType() { return this.useHalAsDefaultJsonMediaType; } /** * Configures whether HAL will be served as primary representation in case on {@code application/json} is requested. * This defaults to {@literal true}. If configured to {@literal false} the legacy Spring Data representation will be * rendered. * * @param useHalAsDefaultJsonMediaType * @return */ public RepositoryRestConfiguration useHalAsDefaultJsonMediaType(boolean useHalAsDefaultJsonMediaType) { this.useHalAsDefaultJsonMediaType = useHalAsDefaultJsonMediaType; return this; } /** * Convenience method to activate returning response bodies for all {@code PUT} and {@code POST} requests, i.e. both * creating and updating entities. * * @param returnBody can be {@literal null}, expressing the decision shall be derived from the presence of an * {@code Accept} header in the request. * @return */ public RepositoryRestConfiguration setReturnBodyForPutAndPost(Boolean returnBody) { setReturnBodyOnCreate(returnBody); setReturnBodyOnUpdate(returnBody); return this; } /** * Whether to return a response body after creating an entity. * * @return {@link java.lang.Boolean#TRUE} to enforce returning a body on create, {@link java.lang.Boolean#FALSE} * otherwise. If {@literal null} and an {@code Accept} header present in the request will cause a body being * returned. If the {@code Accept} header is not present, no body will be rendered. * @deprecated use {@link #returnBodyOnCreate(String)} */ @Deprecated public Boolean isReturnBodyOnCreate() { return returnBodyOnCreate; } /** * Whether to return a response body after creating an entity considering the given accept header. * * @param acceptHeader can be {@literal null} or empty. * @return */ public boolean returnBodyOnCreate(String acceptHeader) { return returnBodyOnCreate == null ? StringUtils.hasText(acceptHeader) : returnBodyOnCreate; } /** * Set whether to return a response body after creating an entity. * * @param returnBody can be {@literal null}, expressing the decision shall be derived from the presence of an * {@code Accept} header in the request. * @return {@literal this} */ public RepositoryRestConfiguration setReturnBodyOnCreate(Boolean returnBody) { this.returnBodyOnCreate = returnBody; return this; } /** * Whether to return a response body after updating an entity. * * @return {@link java.lang.Boolean#TRUE} to enforce returning a body on create, {@link java.lang.Boolean#FALSE} * otherwise. If {@literal null} and an {@code Accept} header present in the request will cause a body being * returned. If the {@code Accept} header is not present, no body will be rendered. * @deprecated use {@link #returnBodyOnUpdate(String)} */ @Deprecated public Boolean isReturnBodyOnUpdate() { return returnBodyOnUpdate; } /** * Whether to return a response body after updating an entity considering the given accept header. * * @param acceptHeader can be {@literal null} or empty. * @return */ public boolean returnBodyOnUpdate(String acceptHeader) { return returnBodyOnUpdate == null ? StringUtils.hasText(acceptHeader) : returnBodyOnUpdate; } /** * Set whether to return a response body after updating an entity. * * @param returnBody can be {@literal null}, expressing the decision shall be derived from the presence of an * {@code Accept} header in the request. * @return {@literal this} */ public RepositoryRestConfiguration setReturnBodyOnUpdate(Boolean returnBodyOnUpdate) { this.returnBodyOnUpdate = returnBodyOnUpdate; return this; } /** * Start configuration a {@link ResourceMapping} for a specific domain type. * * @param domainType The {@link Class} of the domain type to configure a mapping for. * @return A new {@link ResourceMapping} for configuring how a domain type is mapped. */ public ResourceMapping setResourceMappingForDomainType(Class<?> domainType) { return domainMappings.setResourceMappingFor(domainType); } /** * Get the {@link ResourceMapping} for a specific domain type. * * @param domainType The {@link Class} of the domain type. * @return A {@link ResourceMapping} for that domain type or {@literal null} if none exists. */ public ResourceMapping getResourceMappingForDomainType(Class<?> domainType) { return domainMappings.getResourceMappingFor(domainType); } /** * Whether there is a {@link ResourceMapping} for the given domain type. * * @param domainType The domain type to find a {@link ResourceMapping} for. * @return {@literal true} if a {@link ResourceMapping} exists for this domain class, {@literal false} otherwise. */ public boolean hasResourceMappingForDomainType(Class<?> domainType) { return domainMappings.hasResourceMappingFor(domainType); } /** * Get the {@link ResourceMappingConfiguration} that is currently configured. * * @return */ public ResourceMappingConfiguration getDomainTypesResourceMappingConfiguration() { return domainMappings; } /** * Start configuration a {@link ResourceMapping} for a specific repository interface. * * @param repositoryInterface The {@link Class} of the repository interface to configure a mapping for. * @return A new {@link ResourceMapping} for configuring how a repository interface is mapped. */ public ResourceMapping setResourceMappingForRepository(Class<?> repositoryInterface) { return repoMappings.setResourceMappingFor(repositoryInterface); } /** * Get the {@link ResourceMapping} for a specific repository interface. * * @param repositoryInterface The {@link Class} of the repository interface. * @return A {@link ResourceMapping} for that repository interface or {@literal null} if none exists. */ public ResourceMapping getResourceMappingForRepository(Class<?> repositoryInterface) { return repoMappings.getResourceMappingFor(repositoryInterface); } /** * Whether there is a {@link ResourceMapping} configured for this {@literal Repository} class. * * @param repositoryInterface * @return */ public boolean hasResourceMappingForRepository(Class<?> repositoryInterface) { return repoMappings.hasResourceMappingFor(repositoryInterface); } public ResourceMapping findRepositoryMappingForPath(String path) { Class<?> type = repoMappings.findTypeForPath(path); if (null == type) { return null; } return repoMappings.getResourceMappingFor(type); } /** * Should we expose the ID property for this domain type? * * @param domainType The domain type we may need to expose the ID for. * @return {@literal true} is the ID is to be exposed, {@literal false} otherwise. */ public boolean isIdExposedFor(Class<?> domainType) { return exposeIdsFor.contains(domainType); } /** * Set the list of domain types for which we will expose the ID value as a normal property. * * @param domainTypes Array of types to expose IDs for. * @return {@literal this} */ public RepositoryRestConfiguration exposeIdsFor(Class<?>... domainTypes) { Collections.addAll(exposeIdsFor, domainTypes); return this; } /** * Returns the {@link ProjectionDefinitionConfiguration} to register addition projections. * * @return * @deprecated since 2.4, use {@link #getProjectionConfiguration()} instead. */ @Deprecated public ProjectionDefinitionConfiguration projectionConfiguration() { return getProjectionConfiguration(); } /** * Returns the {@link ProjectionDefinitionConfiguration} to register addition projections. * * @return */ public ProjectionDefinitionConfiguration getProjectionConfiguration() { return projectionConfiguration; } /** * Returns the {@link MetadataConfiguration} to customize metadata exposure. * * @return * @deprecated since 2.4, use {@link #getMetadataConfiguration()} instead. */ @Deprecated public MetadataConfiguration metadataConfiguration() { return metadataConfiguration; } /** * Returns the {@link MetadataConfiguration} to customize metadata exposure. * * @return */ public MetadataConfiguration getMetadataConfiguration() { return metadataConfiguration; } /** * Configures whether to enable enum value translation via the Spring Data REST default resource bundle. Defaults to * {@literal false} for backwards compatibility reasons. Will use the fully qualified enum name as key. For further * details see {@link EnumTranslator}. * * @param enableEnumTranslation * @see #getEnumTranslationConfiguration() */ public RepositoryRestConfiguration setEnableEnumTranslation(boolean enableEnumTranslation) { this.enableEnumTranslation = enableEnumTranslation; return this; } /** * Returns whether enum value translation is enabled. * * @return * @since 2.4 */ public boolean isEnableEnumTranslation() { return this.enableEnumTranslation; } /** * Returns the {@link EnumTranslationConfiguration} to be used. * * @return must not be {@literal null}. * @since 2.4 */ public EnumTranslationConfiguration getEnumTranslationConfiguration() { return this.enumTranslationConfiguration; } /** * Returns the {@link RepositoryDetectionStrategy} to be used to decide which repositories get exposed. Will be * {@link RepositoryDetectionStrategies#DEFAULT} by default. * * @return will never be {@literal null}. * @see RepositoryDetectionStrategies * @since 2.5 */ public RepositoryDetectionStrategy getRepositoryDetectionStrategy() { return repositoryDetectionStrategy; } /** * Configures the {@link RepositoryDetectionStrategy} to be used to determine which repositories get exposed. Defaults * to {@link RepositoryDetectionStrategies#DEFAULT}. * * @param repositoryDetectionStrategy can be {@literal null}. * @since 2.5 */ public RepositoryRestConfiguration setRepositoryDetectionStrategy( RepositoryDetectionStrategy repositoryDetectionStrategy) { this.repositoryDetectionStrategy = repositoryDetectionStrategy == null ? RepositoryDetectionStrategies.DEFAULT : repositoryDetectionStrategy; return this; } /** * Returns the {@link RepositoryCorsRegistry} to configure Cross-origin resource sharing. * * @return the {@link RepositoryCorsRegistry}. * @since 2.6 * @see RepositoryCorsRegistry * @see CorsRegistration */ public RepositoryCorsRegistry getCorsRegistry() { return corsRegistry; } /** * Returns the {@link EntityLookupRegistrar} to create custom {@link EntityLookup} instances registered in the * configuration. * * @return the {@link EntityLookupRegistrar} to build custom {@link EntityLookup}s. * @since 2.5 */ public EntityLookupRegistrar withEntityLookup() { return entityLookupConfiguration; } /** * Returns all {@link EntityLookup}s considering the customizations made to the configuration. * * @param repositories must not be {@literal null}. * @return */ public List<EntityLookup<?>> getEntityLookups(Repositories repositories) { Assert.notNull(repositories, "Repositories must not be null!"); return entityLookupConfiguration.getEntityLookups(repositories); } public boolean isLookupType(Class<?> type) { return this.entityLookupConfiguration.isLookupType(type); } }