com.sap.cloud.yaas.servicesdk.springboot.ramlrewriter.RamlResourcesAutoConfiguration Maven / Gradle / Ivy
/*
* © 2017 SAP SE or an SAP affiliate company.
* All rights reserved.
* Please see http://www.sap.com/corporate-en/legal/copyright/index.epx for additional trademark information and
* notices.
*/
package com.sap.cloud.yaas.servicesdk.springboot.ramlrewriter;
import javax.servlet.http.HttpServletRequest;
import com.github.ulisesbocchio.jar.resources.JarResourceLoader;
import com.sap.cloud.yaas.servicesdk.ramlrewriter.filter.RequestBasedRamlRewriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.MimeMappings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.resource.ResourceTransformer;
import org.springframework.web.servlet.resource.ResourceTransformerChain;
import org.springframework.web.servlet.resource.TransformedResource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
/**
* Auto Configuration class that auto-registers the functionality of raml-rewriter library of the YaaS Service SDK.
*
* It registers a RAML rewriting {@link ResourceTransformer} that uses {@link RequestBasedRamlRewriter} internally.
*/
@Configuration
@ConditionalOnClass({ WebMvcConfigurerAdapter.class, RequestBasedRamlRewriter.class })
@ConditionalOnWebApplication
public class RamlResourcesAutoConfiguration
{
private static final Logger LOG = LoggerFactory.getLogger(RamlResourcesAutoConfiguration.class);
private static final String PUBLIC_PATH = "/public/";
private static final String METADATA_PATH = PUBLIC_PATH + "meta-data/";
/**
* Registers an instance of {@link RequestBasedRamlRewriter}.
*
* @return an instance of {@link RequestBasedRamlRewriter}
* @throws IOException in case of problems retrieving the raml resources root path
*/
@Bean
@Lazy
@ConditionalOnClass(RequestBasedRamlRewriter.class)
@ConditionalOnMissingBean(RequestBasedRamlRewriter.class)
public RequestBasedRamlRewriter ramlRewriter() throws IOException
{
return new RequestBasedRamlRewriter();
}
/**
* Registers an instance of {@link ResourceTransformer}.
*
* @return an instance of {@link ResourceTransformer}
*/
@Bean
@Lazy
@ConditionalOnBean(RequestBasedRamlRewriter.class)
public ResourceTransformer ramlRewritingTransformer()
{
return new RamlRewritingTransformer();
}
/**
* Registers a {@link WebMvcConfigurerAdapter} that adds resource handlers for static RAML serving and rewriting.
*
* @return serving static RAML configuration
* @throws IOException in case of streaming problems during transformation
*/
@Bean
public WebMvcConfigurerAdapter ramlResourcesConfiguration() throws IOException
{
return new RamlResourcesConfiguration();
}
private class RamlRewritingTransformer implements ResourceTransformer
{
@Override
public Resource transform(final HttpServletRequest request, final Resource resource,
final ResourceTransformerChain transformerChain) throws IOException
{
try (InputStream input = resource.getInputStream())
{
final byte[] rawContent = StreamUtils.copyToByteArray(input);
final byte[] rewrittenContent = ramlRewriter().rewrite(rawContent, request);
final Resource rewrittenResource = new TransformedResource(resource, rewrittenContent);
return transformerChain.transform(request, rewrittenResource);
}
}
}
private class RamlResourcesConfiguration extends WebMvcConfigurerAdapter
{
private static final String BASE_PATH = "/meta-data/**";
@Override
@SuppressWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
public void addResourceHandlers(final ResourceHandlerRegistry registry)
{
final Resource resourceLocation = new ClassPathResource(METADATA_PATH);
if (resourceLocation.exists())
{
LOG.info("Serving RAML resources from classpath at : '" + METADATA_PATH + "'.");
final String location;
try
{
location = resourceLocation.getURL().toString();
}
catch (final IOException e)
{
LOG.warn("The path '" + METADATA_PATH + "' for RAML resources does not seem to be valid."
+ " Will ignore this, and not attempt to serve static resources for RAML.", e);
return;
}
final ResourceHandlerRegistration registration = registry.addResourceHandler(BASE_PATH);
registration.addResourceLocations(location);
LOG.info("Activating RAML rewriting for all RAML resources being served.");
registration.resourceChain(false).addTransformer(ramlRewritingTransformer());
}
else
{
LOG.warn("The path '" + METADATA_PATH + "' does not exist on the classpath."
+ " Will ignore this, and not attempt to serve static resources for RAML.");
}
}
}
/**
* Adds the Content-type mapping for *.raml extension, so that it is served as text/plain MIME type
* instead of application/octet-stream. Additionally, provide a proper document root to serve
* resources from the ServletContext to allow for RAML expansion and add a
* servlet mapping for /meta-data/*
*/
@Component
public static class ServletContainerCustomizer implements EmbeddedServletContainerCustomizer
{
private static final String DOC_ROOT_PREFIX = "tempDocRoot";
@Override
public void customize(final ConfigurableEmbeddedServletContainer container)
{
customizeMimeMappings(container);
customizeDocumentRoot(container);
}
private void customizeMimeMappings(final ConfigurableEmbeddedServletContainer container)
{
final MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
mappings.add("raml", "text/plain");
container.setMimeMappings(mappings);
}
private void customizeDocumentRoot(final ConfigurableEmbeddedServletContainer container)
{
// we wrap the actual resource in a JarResource. If contained in a JAR file, the resource will be unpacked
// to a temp directory. This allows us to get a File object to set the document root directory.
try
{
final String docRootDir = Files.createTempDirectory(DOC_ROOT_PREFIX).toAbsolutePath().toString();
final Resource unpackedDocumentRootResource = new JarResourceLoader(docRootDir).getResource(PUBLIC_PATH);
if (unpackedDocumentRootResource.exists())
{
final File documentRoot = unpackedDocumentRootResource.getFile();
container.setDocumentRoot(documentRoot);
LOG.info("Using custom document root for our servlet context: " + documentRoot);
}
}
catch (final IOException e)
{
LOG.warn("Failed to set document root to " + PUBLIC_PATH + ", falling back to default.", e);
}
}
}
}