diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactory.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactory.java index 2729b4a98c..f4940b298e 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactory.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactory.java @@ -63,6 +63,8 @@ public abstract class AbstractEmbeddedServletContainerFactory implements private Set errorPages = new LinkedHashSet(); + private MimeMappings mimeMappings = new MimeMappings(MimeMappings.DEFAULT); + private InetAddress address; /** @@ -94,13 +96,6 @@ public abstract class AbstractEmbeddedServletContainerFactory implements this.port = port; } - /** - * Sets the context path for the embedded servlet container. The context should start - * with a "/" character but not end with a "/" character. The default context path can - * be specified using an empty string. - * @param contextPath the contextPath to set - * @see #getContextPath - */ @Override public void setContextPath(String contextPath) { checkContextPath(contextPath); @@ -125,17 +120,10 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * Returns the context path for the embedded servlet container. The path will start * with "/" and not end with "/". The root context is represented by an empty string. */ - @Override public String getContextPath() { return this.contextPath; } - /** - * Sets the port that the embedded servlet container should listen on. If not - * specified port '8080' will be used. Use port 0 to switch off the server completely. - * - * @param port the port to set - */ @Override public void setPort(int port) { checkPort(port); @@ -151,16 +139,10 @@ public abstract class AbstractEmbeddedServletContainerFactory implements /** * Returns the port that the embedded servlet container should listen on. */ - @Override public int getPort() { return this.port; } - /** - * If you need the server to bind to a specific network address, provide one here. - * - * @param address the address to set (defaults to null) - */ @Override public void setAddress(InetAddress address) { this.address = address; @@ -169,32 +151,16 @@ public abstract class AbstractEmbeddedServletContainerFactory implements /** * @return the address the embedded container binds to */ - @Override public InetAddress getAddress() { return this.address; } - /** - * Sets {@link ServletContextInitializer} that should be applied in addition to - * {@link #getEmbeddedServletContainer(ServletContextInitializer...)} parameters. This - * method will replace any previously set or added initializers. - * @param initializers the initializers to set - * @see #addInitializers - * @see #getInitializers - */ @Override public void setInitializers(List initializers) { Assert.notNull(initializers, "Initializers must not be null"); this.initializers = new ArrayList(initializers); } - /** - * Add {@link ServletContextInitializer}s to those that should be applied in addition - * to {@link #getEmbeddedServletContainer(ServletContextInitializer...)} parameters. - * @param initializers the initializers to add - * @see #setInitializers - * @see #getInitializers - */ @Override public void addInitializers(ServletContextInitializer... initializers) { Assert.notNull(initializers, "Initializers must not be null"); @@ -207,16 +173,10 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * parameters. * @return the initializers */ - @Override public List getInitializers() { return this.initializers; } - /** - * Sets the document root folder which will be used by the web context to serve static - * files. - * @param documentRoot the document root or {@code null} if not required - */ @Override public void setDocumentRoot(File documentRoot) { this.documentRoot = documentRoot; @@ -226,25 +186,16 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * Returns the document root which will be used by the web context to serve static * files. */ - @Override public File getDocumentRoot() { return this.documentRoot; } - /** - * Sets the error pages that will be used when handling exceptions. - * @param errorPages the error pages - */ @Override public void setErrorPages(Set errorPages) { Assert.notNull(errorPages, "ErrorPages must not be null"); this.errorPages = new LinkedHashSet(errorPages); } - /** - * Adds error pages that will be used when handling exceptions. - * @param errorPages the error pages - */ @Override public void addErrorPages(ErrorPage... errorPages) { Assert.notNull(this.initializers, "ErrorPages must not be null"); @@ -255,16 +206,23 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * Returns a mutable set of {@link ErrorPage}s that will be used when handling * exceptions. */ - @Override public Set getErrorPages() { return this.errorPages; } + @Override + public void setMimeMappings(MimeMappings mimeMappings) { + this.mimeMappings = new MimeMappings(mimeMappings); + } + /** - * Set if the DefaultServlet should be registered. Defaults to {@code true} so that - * files from the {@link #setDocumentRoot(File) document root} will be served. - * @param registerDefaultServlet if the default servlet should be registered + * Returns the mime-type mappings. + * @return the mimeMappings the mime-type mappings. */ + public MimeMappings getMimeMappings() { + return this.mimeMappings; + } + @Override public void setRegisterDefaultServlet(boolean registerDefaultServlet) { this.registerDefaultServlet = registerDefaultServlet; @@ -273,20 +231,12 @@ public abstract class AbstractEmbeddedServletContainerFactory implements /** * Flag to indicate that the JSP servlet should be registered if available on the * classpath. - * * @return true if the JSP servlet is to be registered */ - @Override public boolean isRegisterJspServlet() { return this.registerJspServlet; } - /** - * Set if the JspServlet should be registered if it is on the classpath. Defaults to - * {@code true} so that files from the {@link #setDocumentRoot(File) document root} - * will be served. - * @param registerJspServlet if the JSP servlet should be registered - */ @Override public void setRegisterJspServlet(boolean registerJspServlet) { this.registerJspServlet = registerJspServlet; @@ -294,23 +244,12 @@ public abstract class AbstractEmbeddedServletContainerFactory implements /** * Flag to indicate that the default servlet should be registered. - * * @return true if the default servlet is to be registered */ - @Override public boolean isRegisterDefaultServlet() { return this.registerDefaultServlet; } - /** - * The class name for the jsp servlet if used. If - * {@link #setRegisterJspServlet(boolean) registerJspServlet} is true - * and this class is on the classpath then it will be registered. Since both - * Tomcat and Jetty use Jasper for their JSP implementation the default is - * org.apache.jasper.servlet.JspServlet. - * - * @param jspServletClassName the class name for the JSP servlet if used - */ @Override public void setJspServletClassName(String jspServletClassName) { this.jspServletClassName = jspServletClassName; diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/ConfigurableEmbeddedServletContainerFactory.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/ConfigurableEmbeddedServletContainerFactory.java index 6be6ba538a..978ea351c7 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/ConfigurableEmbeddedServletContainerFactory.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/ConfigurableEmbeddedServletContainerFactory.java @@ -31,42 +31,93 @@ import java.util.Set; public interface ConfigurableEmbeddedServletContainerFactory extends EmbeddedServletContainerFactory { + /** + * Sets the context path for the embedded servlet container. The context should start + * with a "/" character but not end with a "/" character. The default context path can + * be specified using an empty string. + * @param contextPath the contextPath to set + */ void setContextPath(String contextPath); - String getContextPath(); - + /** + * Sets the port that the embedded servlet container should listen on. If not + * specified port '8080' will be used. Use port 0 to switch off the server completely. + * @param port the port to set + */ void setPort(int port); - int getPort(); - + /** + * Sets the specific network address that the server should bind to. + * @param address the address to set (defaults to {@code null}) + */ void setAddress(InetAddress address); - InetAddress getAddress(); - - void setInitializers(List initializers); - + /** + * The class name for the jsp servlet if used. If + * {@link #setRegisterJspServlet(boolean) registerJspServlet} is true + * and this class is on the classpath then it will be registered. Since both + * Tomcat and Jetty use Jasper for their JSP implementation the default is + * org.apache.jasper.servlet.JspServlet. + * @param jspServletClassName the class name for the JSP servlet if used + */ void setJspServletClassName(String jspServletClassName); - boolean isRegisterDefaultServlet(); - + /** + * Set if the JspServlet should be registered if it is on the classpath. Defaults to + * {@code true} so that files from the {@link #setDocumentRoot(File) document root} + * will be served. + * @param registerJspServlet if the JSP servlet should be registered + */ void setRegisterJspServlet(boolean registerJspServlet); - boolean isRegisterJspServlet(); - + /** + * Set if the DefaultServlet should be registered. Defaults to {@code true} so that + * files from the {@link #setDocumentRoot(File) document root} will be served. + * @param registerDefaultServlet if the default servlet should be registered + */ void setRegisterDefaultServlet(boolean registerDefaultServlet); - Set getErrorPages(); - + /** + * Adds error pages that will be used when handling exceptions. + * @param errorPages the error pages + */ void addErrorPages(ErrorPage... errorPages); + /** + * Sets the error pages that will be used when handling exceptions. + * @param errorPages the error pages + */ void setErrorPages(Set errorPages); - File getDocumentRoot(); - + /** + * Sets the mime-type mappings. + * @param mimeMappings the mime type mappings (defaults to + * {@link MimeMappings#DEFAULT}) + */ + void setMimeMappings(MimeMappings mimeMappings); + + /** + * Sets the document root folder which will be used by the web context to serve static + * files. + * @param documentRoot the document root or {@code null} if not required + */ void setDocumentRoot(File documentRoot); - List getInitializers(); + /** + * Sets {@link ServletContextInitializer} that should be applied in addition to + * {@link #getEmbeddedServletContainer(ServletContextInitializer...)} parameters. This + * method will replace any previously set or added initializers. + * @param initializers the initializers to set + * @see #addInitializers + */ + void setInitializers(List initializers); + /** + * Add {@link ServletContextInitializer}s to those that should be applied in addition + * to {@link #getEmbeddedServletContainer(ServletContextInitializer...)} parameters. + * @param initializers the initializers to add + * @see #setInitializers + */ void addInitializers(ServletContextInitializer... initializers); } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/MimeMappings.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/MimeMappings.java new file mode 100644 index 0000000000..78d8fb7996 --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/MimeMappings.java @@ -0,0 +1,376 @@ +/* + * Copyright 2012-2013 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.bootstrap.context.embedded; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.bootstrap.context.embedded.MimeMappings.Mapping; +import org.springframework.util.Assert; + +/** + * Simple container-independent abstraction for servlet mime mappings. Roughly equivalent + * to the {@literal <mime-mapping>} element traditionally found in web.xml. + * + * @author Phillip Webb + */ +public final class MimeMappings implements Iterable { + + /** + * Default mime mapping commonly used. + */ + public static final MimeMappings DEFAULT; + static { + MimeMappings mappings = new MimeMappings(); + mappings.add("abs", "audio/x-mpeg"); + mappings.add("ai", "application/postscript"); + mappings.add("aif", "audio/x-aiff"); + mappings.add("aifc", "audio/x-aiff"); + mappings.add("aiff", "audio/x-aiff"); + mappings.add("aim", "application/x-aim"); + mappings.add("art", "image/x-jg"); + mappings.add("asf", "video/x-ms-asf"); + mappings.add("asx", "video/x-ms-asf"); + mappings.add("au", "audio/basic"); + mappings.add("avi", "video/x-msvideo"); + mappings.add("avx", "video/x-rad-screenplay"); + mappings.add("bcpio", "application/x-bcpio"); + mappings.add("bin", "application/octet-stream"); + mappings.add("bmp", "image/bmp"); + mappings.add("body", "text/html"); + mappings.add("cdf", "application/x-cdf"); + mappings.add("cer", "application/pkix-cert"); + mappings.add("class", "application/java"); + mappings.add("cpio", "application/x-cpio"); + mappings.add("csh", "application/x-csh"); + mappings.add("css", "text/css"); + mappings.add("dib", "image/bmp"); + mappings.add("doc", "application/msword"); + mappings.add("dtd", "application/xml-dtd"); + mappings.add("dv", "video/x-dv"); + mappings.add("dvi", "application/x-dvi"); + mappings.add("eps", "application/postscript"); + mappings.add("etx", "text/x-setext"); + mappings.add("exe", "application/octet-stream"); + mappings.add("gif", "image/gif"); + mappings.add("gtar", "application/x-gtar"); + mappings.add("gz", "application/x-gzip"); + mappings.add("hdf", "application/x-hdf"); + mappings.add("hqx", "application/mac-binhex40"); + mappings.add("htc", "text/x-component"); + mappings.add("htm", "text/html"); + mappings.add("html", "text/html"); + mappings.add("ief", "image/ief"); + mappings.add("jad", "text/vnd.sun.j2me.app-descriptor"); + mappings.add("jar", "application/java-archive"); + mappings.add("java", "text/x-java-source"); + mappings.add("jnlp", "application/x-java-jnlp-file"); + mappings.add("jpe", "image/jpeg"); + mappings.add("jpeg", "image/jpeg"); + mappings.add("jpg", "image/jpeg"); + mappings.add("js", "application/javascript"); + mappings.add("jsf", "text/plain"); + mappings.add("jspf", "text/plain"); + mappings.add("kar", "audio/midi"); + mappings.add("latex", "application/x-latex"); + mappings.add("m3u", "audio/x-mpegurl"); + mappings.add("mac", "image/x-macpaint"); + mappings.add("man", "text/troff"); + mappings.add("mathml", "application/mathml+xml"); + mappings.add("me", "text/troff"); + mappings.add("mid", "audio/midi"); + mappings.add("midi", "audio/midi"); + mappings.add("mif", "application/x-mif"); + mappings.add("mov", "video/quicktime"); + mappings.add("movie", "video/x-sgi-movie"); + mappings.add("mp1", "audio/mpeg"); + mappings.add("mp2", "audio/mpeg"); + mappings.add("mp3", "audio/mpeg"); + mappings.add("mp4", "video/mp4"); + mappings.add("mpa", "audio/mpeg"); + mappings.add("mpe", "video/mpeg"); + mappings.add("mpeg", "video/mpeg"); + mappings.add("mpega", "audio/x-mpeg"); + mappings.add("mpg", "video/mpeg"); + mappings.add("mpv2", "video/mpeg2"); + mappings.add("nc", "application/x-netcdf"); + mappings.add("oda", "application/oda"); + mappings.add("odb", "application/vnd.oasis.opendocument.database"); + mappings.add("odc", "application/vnd.oasis.opendocument.chart"); + mappings.add("odf", "application/vnd.oasis.opendocument.formula"); + mappings.add("odg", "application/vnd.oasis.opendocument.graphics"); + mappings.add("odi", "application/vnd.oasis.opendocument.image"); + mappings.add("odm", "application/vnd.oasis.opendocument.text-master"); + mappings.add("odp", "application/vnd.oasis.opendocument.presentation"); + mappings.add("ods", "application/vnd.oasis.opendocument.spreadsheet"); + mappings.add("odt", "application/vnd.oasis.opendocument.text"); + mappings.add("otg", "application/vnd.oasis.opendocument.graphics-template"); + mappings.add("oth", "application/vnd.oasis.opendocument.text-web"); + mappings.add("otp", "application/vnd.oasis.opendocument.presentation-template"); + mappings.add("ots", "application/vnd.oasis.opendocument.spreadsheet-template "); + mappings.add("ott", "application/vnd.oasis.opendocument.text-template"); + mappings.add("ogx", "application/ogg"); + mappings.add("ogv", "video/ogg"); + mappings.add("oga", "audio/ogg"); + mappings.add("ogg", "audio/ogg"); + mappings.add("spx", "audio/ogg"); + mappings.add("flac", "audio/flac"); + mappings.add("anx", "application/annodex"); + mappings.add("axa", "audio/annodex"); + mappings.add("axv", "video/annodex"); + mappings.add("xspf", "application/xspf+xml"); + mappings.add("pbm", "image/x-portable-bitmap"); + mappings.add("pct", "image/pict"); + mappings.add("pdf", "application/pdf"); + mappings.add("pgm", "image/x-portable-graymap"); + mappings.add("pic", "image/pict"); + mappings.add("pict", "image/pict"); + mappings.add("pls", "audio/x-scpls"); + mappings.add("png", "image/png"); + mappings.add("pnm", "image/x-portable-anymap"); + mappings.add("pnt", "image/x-macpaint"); + mappings.add("ppm", "image/x-portable-pixmap"); + mappings.add("ppt", "application/vnd.ms-powerpoint"); + mappings.add("pps", "application/vnd.ms-powerpoint"); + mappings.add("ps", "application/postscript"); + mappings.add("psd", "image/vnd.adobe.photoshop"); + mappings.add("qt", "video/quicktime"); + mappings.add("qti", "image/x-quicktime"); + mappings.add("qtif", "image/x-quicktime"); + mappings.add("ras", "image/x-cmu-raster"); + mappings.add("rdf", "application/rdf+xml"); + mappings.add("rgb", "image/x-rgb"); + mappings.add("rm", "application/vnd.rn-realmedia"); + mappings.add("roff", "text/troff"); + mappings.add("rtf", "application/rtf"); + mappings.add("rtx", "text/richtext"); + mappings.add("sh", "application/x-sh"); + mappings.add("shar", "application/x-shar"); + mappings.add("sit", "application/x-stuffit"); + mappings.add("snd", "audio/basic"); + mappings.add("src", "application/x-wais-source"); + mappings.add("sv4cpio", "application/x-sv4cpio"); + mappings.add("sv4crc", "application/x-sv4crc"); + mappings.add("svg", "image/svg+xml"); + mappings.add("svgz", "image/svg+xml"); + mappings.add("swf", "application/x-shockwave-flash"); + mappings.add("t", "text/troff"); + mappings.add("tar", "application/x-tar"); + mappings.add("tcl", "application/x-tcl"); + mappings.add("tex", "application/x-tex"); + mappings.add("texi", "application/x-texinfo"); + mappings.add("texinfo", "application/x-texinfo"); + mappings.add("tif", "image/tiff"); + mappings.add("tiff", "image/tiff"); + mappings.add("tr", "text/troff"); + mappings.add("tsv", "text/tab-separated-values"); + mappings.add("txt", "text/plain"); + mappings.add("ulw", "audio/basic"); + mappings.add("ustar", "application/x-ustar"); + mappings.add("vxml", "application/voicexml+xml"); + mappings.add("xbm", "image/x-xbitmap"); + mappings.add("xht", "application/xhtml+xml"); + mappings.add("xhtml", "application/xhtml+xml"); + mappings.add("xls", "application/vnd.ms-excel"); + mappings.add("xml", "application/xml"); + mappings.add("xpm", "image/x-xpixmap"); + mappings.add("xsl", "application/xml"); + mappings.add("xslt", "application/xslt+xml"); + mappings.add("xul", "application/vnd.mozilla.xul+xml"); + mappings.add("xwd", "image/x-xwindowdump"); + mappings.add("vsd", "application/vnd.visio"); + mappings.add("wav", "audio/x-wav"); + mappings.add("wbmp", "image/vnd.wap.wbmp"); + mappings.add("wml", "text/vnd.wap.wml"); + mappings.add("wmlc", "application/vnd.wap.wmlc"); + mappings.add("wmls", "text/vnd.wap.wmlsc"); + mappings.add("wmlscriptc", "application/vnd.wap.wmlscriptc"); + mappings.add("wmv", "video/x-ms-wmv"); + mappings.add("wrl", "model/vrml"); + mappings.add("wspolicy", "application/wspolicy+xml"); + mappings.add("Z", "application/x-compress"); + mappings.add("z", "application/x-compress"); + mappings.add("zip", "application/zip"); + DEFAULT = unmodifiableMappings(mappings); + } + + private final Map map; + + /** + * Create a new empty {@link MimeMappings} instance. + */ + public MimeMappings() { + this.map = new LinkedHashMap(); + } + + /** + * Create a new {@link MimeMappings} instance from the specified mappings. + * @param mappings the source mappings + */ + public MimeMappings(MimeMappings mappings) { + this(mappings, true); + } + + /** + * Create a new {@link MimeMappings} from the specified mappings. + * @param mappings the source mappings with extension as the key and mime-type as the + * value + */ + public MimeMappings(Map mappings) { + Assert.notNull(mappings, "Mappings must not be null"); + this.map = new LinkedHashMap(); + for (Map.Entry entry : mappings.entrySet()) { + add(entry.getKey(), entry.getValue()); + } + } + + /** + * Internal constructor. + * @param mappings source mappings + * @param mutable if the new object should be mutable. + */ + private MimeMappings(MimeMappings mappings, boolean mutable) { + Assert.notNull(mappings, "Mappings must not be null"); + this.map = (mutable ? new LinkedHashMap( + mappings.map) : Collections.unmodifiableMap(mappings.map)); + } + + @Override + public Iterator iterator() { + return getAll().iterator(); + } + + /** + * Returns all defined mappings. + * @return the mappings. + */ + public Collection getAll() { + return this.map.values(); + } + + /** + * Add a new mime mapping. + * @param extension the file extension (excluding '.') + * @param mimeType the mime type to map + * @return any previous mapping or {@code null} + */ + public String add(String extension, String mimeType) { + Mapping previous = this.map.put(extension, new Mapping(extension, mimeType)); + return (previous == null ? null : previous.getMimeType()); + } + + /** + * Get a mime mapping for the given extension. + * @param extension the file extension (excluding '.') + * @return a mime mapping or {@code null} + */ + public String get(String extension) { + Mapping mapping = this.map.get(extension); + return (mapping == null ? null : mapping.getMimeType()); + } + + /** + * Remove an existing mapping. + * @param extension the file extension (excluding '.') + * @return the removed mime mapping or {@code null} if no item was removed + */ + public String remove(String extension) { + Mapping previous = this.map.remove(extension); + return (previous == null ? null : previous.getMimeType()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj instanceof MimeMappings) { + MimeMappings other = (MimeMappings) obj; + return this.map.equals(other.map); + } + return false; + } + + @Override + public int hashCode() { + return this.map.hashCode(); + } + + /** + * Create a new unmodifiable view of the specified mapping. Methods that attempt to + * modify the returned map will throw {@link UnsupportedOperationException}s. + * @param mappings the mappings + * @return an unmodifiable view of the specified mappings. + */ + public static MimeMappings unmodifiableMappings(MimeMappings mappings) { + return new MimeMappings(mappings, false); + } + + /** + * A single mime mapping. + */ + public static final class Mapping { + + private final String extension; + + private final String mimeType; + + public Mapping(String extension, String mimeType) { + Assert.notNull(extension, "Extension must not be null"); + Assert.notNull(mimeType, "MimeType must not be null"); + this.extension = extension; + this.mimeType = mimeType; + } + + public String getExtension() { + return this.extension; + } + + public String getMimeType() { + return this.mimeType; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj instanceof Mapping) { + Mapping other = (Mapping) obj; + return this.extension.equals(other.extension) + && this.mimeType.equals(other.mimeType); + } + return false; + } + + @Override + public int hashCode() { + return this.extension.hashCode(); + } + + } + +} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java index 7f71a5c33b..1661f5de12 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.servlet.ErrorPageErrorHandler; @@ -36,6 +37,7 @@ import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletCon import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer; import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory; import org.springframework.bootstrap.context.embedded.ErrorPage; +import org.springframework.bootstrap.context.embedded.MimeMappings; import org.springframework.bootstrap.context.embedded.ServletContextInitializer; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; @@ -173,12 +175,12 @@ public class JettyEmbeddedServletContainerFactory extends initializers)); configurations.addAll(getConfigurations()); configurations.add(getErrorPageConfiguration()); + configurations.add(getMimeTypeConfiguration()); return configurations.toArray(new Configuration[configurations.size()]); } /** * Create a configuration object that adds error handlers - * * @return a configuration object for adding error pages */ private Configuration getErrorPageConfiguration() { @@ -191,6 +193,23 @@ public class JettyEmbeddedServletContainerFactory extends }; } + /** + * Create a configuration object that adds mime type mappings + * @return a configuration object for adding mime type mappings + */ + private Configuration getMimeTypeConfiguration() { + return new AbstractConfiguration() { + @Override + public void configure(WebAppContext context) throws Exception { + MimeTypes mimeTypes = context.getMimeTypes(); + for (MimeMappings.Mapping mapping : getMimeMappings()) { + mimeTypes.addMimeMapping(mapping.getExtension(), + mapping.getMimeType()); + } + } + }; + } + /** * Return a Jetty {@link Configuration} that will invoke the specified * {@link ServletContextInitializer}s. By default this method will return a diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java index cff702b56a..c3e96116c2 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java @@ -39,6 +39,7 @@ import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer; import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerException; import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory; import org.springframework.bootstrap.context.embedded.ErrorPage; +import org.springframework.bootstrap.context.embedded.MimeMappings; import org.springframework.bootstrap.context.embedded.ServletContextInitializer; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; @@ -147,7 +148,6 @@ public class TomcatEmbeddedServletContainerFactory extends WebappLoader loader = new WebappLoader(context.getParentClassLoader()); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); context.setLoader(loader); - if (isRegisterDefaultServlet()) { addDefaultServlet(context); } @@ -217,6 +217,9 @@ public class TomcatEmbeddedServletContainerFactory extends tomcatPage.setErrorCode(errorPage.getStatusCode()); context.addErrorPage(tomcatPage); } + for (MimeMappings.Mapping mapping : getMimeMappings()) { + context.addMimeMapping(mapping.getExtension(), mapping.getMimeType()); + } } /** diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java index d83f788ddd..a9b0ed05bf 100644 --- a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java @@ -85,8 +85,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { this.container = factory .getEmbeddedServletContainer(exampleServletRegistration()); this.container.start(); - assertThat(getResponse(factory, "http://localhost:8080/hello"), - equalTo("Hello World")); + assertThat(getResponse("http://localhost:8080/hello"), equalTo("Hello World")); } @Test @@ -97,7 +96,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { .getEmbeddedServletContainer(exampleServletRegistration()); this.container.start(); this.thrown.expect(SocketException.class); - getResponse(factory, "http://localhost:8080/hello"); + getResponse("http://localhost:8080/hello"); } @Test @@ -108,7 +107,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { this.container.start(); this.container.stop(); this.thrown.expect(SocketException.class); - getResponse(null, "http://localhost:8080/hello"); + getResponse("http://localhost:8080/hello"); } @Test @@ -140,8 +139,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { exampleServletRegistration(), new FilterRegistrationBean( new ExampleFilter())); this.container.start(); - assertThat(getResponse(factory, "http://localhost:8080/hello"), - equalTo("[Hello World]")); + assertThat(getResponse("http://localhost:8080/hello"), equalTo("[Hello World]")); } @Test @@ -173,8 +171,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { this.container = factory .getEmbeddedServletContainer(exampleServletRegistration()); this.container.start(); - assertThat(getResponse(factory, "http://localhost:8081/hello"), - equalTo("Hello World")); + assertThat(getResponse("http://localhost:8081/hello"), equalTo("Hello World")); } @Test @@ -184,8 +181,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { this.container = factory .getEmbeddedServletContainer(exampleServletRegistration()); this.container.start(); - assertThat(getResponse(factory, "http://localhost:8080/say/hello"), - equalTo("Hello World")); + assertThat(getResponse("http://localhost:8080/say/hello"), equalTo("Hello World")); } @Test @@ -246,18 +242,29 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { factory.setDocumentRoot(this.temporaryFolder.getRoot()); this.container = factory.getEmbeddedServletContainer(); this.container.start(); - assertThat(getResponse(factory, "http://localhost:8080/test.txt"), - equalTo("test")); + assertThat(getResponse("http://localhost:8080/test.txt"), equalTo("test")); + } + + @Test + public void mimeType() throws Exception { + FileCopyUtils.copy("test", + new FileWriter(this.temporaryFolder.newFile("test.xxcss"))); + AbstractEmbeddedServletContainerFactory factory = getFactory(); + factory.setDocumentRoot(this.temporaryFolder.getRoot()); + MimeMappings mimeMappings = new MimeMappings(); + mimeMappings.add("xxcss", "text/css"); + factory.setMimeMappings(mimeMappings); + this.container = factory.getEmbeddedServletContainer(); + this.container.start(); + ClientHttpResponse response = getClientResponse("http://localhost:8080/test.xxcss"); + assertThat(response.getHeaders().getContentType().toString(), equalTo("text/css")); + response.close(); } // FIXME test error page - protected String getResponse(EmbeddedServletContainerFactory factory, String url) - throws IOException, URISyntaxException { - SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory(); - ClientHttpRequest request = clientHttpRequestFactory.createRequest(new URI(url), - HttpMethod.GET); - ClientHttpResponse response = request.execute(); + protected String getResponse(String url) throws IOException, URISyntaxException { + ClientHttpResponse response = getClientResponse(url); try { return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8")); } @@ -266,6 +273,15 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { } } + protected ClientHttpResponse getClientResponse(String url) throws IOException, + URISyntaxException { + SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory(); + ClientHttpRequest request = clientHttpRequestFactory.createRequest(new URI(url), + HttpMethod.GET); + ClientHttpResponse response = request.execute(); + return response; + } + protected abstract AbstractEmbeddedServletContainerFactory getFactory(); private ServletContextInitializer exampleServletRegistration() { diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/MimeMappingsTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/MimeMappingsTests.java new file mode 100644 index 0000000000..18893b471b --- /dev/null +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/MimeMappingsTests.java @@ -0,0 +1,175 @@ +/* + * Copyright 2012-2013 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.bootstrap.context.embedded; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.catalina.Context; +import org.apache.catalina.Wrapper; +import org.apache.catalina.startup.Tomcat; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willAnswer; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link MimeMappings}. + * + * @author Phillip Webb + */ +public class MimeMappingsTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void defaults() throws Exception { + assertThat(MimeMappings.DEFAULT, equalTo(getTomatDefaults())); + } + + @Test(expected = UnsupportedOperationException.class) + public void defaultsCannotBeModified() throws Exception { + MimeMappings.DEFAULT.add("foo", "foo/bar"); + } + + @Test + public void createFromExisting() throws Exception { + MimeMappings mappings = new MimeMappings(); + mappings.add("foo", "bar"); + MimeMappings clone = new MimeMappings(mappings); + mappings.add("baz", "bar"); + assertThat(clone.get("foo"), equalTo("bar")); + assertThat(clone.get("baz"), nullValue()); + } + + @Test + public void createFromMap() throws Exception { + Map mappings = new HashMap(); + mappings.put("foo", "bar"); + MimeMappings clone = new MimeMappings(mappings); + mappings.put("baz", "bar"); + assertThat(clone.get("foo"), equalTo("bar")); + assertThat(clone.get("baz"), nullValue()); + } + + @Test + public void iterate() throws Exception { + MimeMappings mappings = new MimeMappings(); + mappings.add("foo", "bar"); + mappings.add("baz", "boo"); + List mappingList = new ArrayList(); + for (MimeMappings.Mapping mapping : mappings) { + mappingList.add(mapping); + } + assertThat(mappingList.get(0).getExtension(), equalTo("foo")); + assertThat(mappingList.get(0).getMimeType(), equalTo("bar")); + assertThat(mappingList.get(1).getExtension(), equalTo("baz")); + assertThat(mappingList.get(1).getMimeType(), equalTo("boo")); + } + + @Test + public void getAll() throws Exception { + MimeMappings mappings = new MimeMappings(); + mappings.add("foo", "bar"); + mappings.add("baz", "boo"); + List mappingList = new ArrayList(); + mappingList.addAll(mappings.getAll()); + assertThat(mappingList.get(0).getExtension(), equalTo("foo")); + assertThat(mappingList.get(0).getMimeType(), equalTo("bar")); + assertThat(mappingList.get(1).getExtension(), equalTo("baz")); + assertThat(mappingList.get(1).getMimeType(), equalTo("boo")); + } + + @Test + public void addNew() throws Exception { + MimeMappings mappings = new MimeMappings(); + assertThat(mappings.add("foo", "bar"), nullValue()); + } + + @Test + public void addReplacesExisting() throws Exception { + MimeMappings mappings = new MimeMappings(); + mappings.add("foo", "bar"); + assertThat(mappings.add("foo", "baz"), equalTo("bar")); + } + + @Test + public void remove() throws Exception { + MimeMappings mappings = new MimeMappings(); + mappings.add("foo", "bar"); + assertThat(mappings.remove("foo"), equalTo("bar")); + assertThat(mappings.remove("foo"), nullValue()); + } + + @Test + public void get() throws Exception { + MimeMappings mappings = new MimeMappings(); + mappings.add("foo", "bar"); + assertThat(mappings.get("foo"), equalTo("bar")); + } + + @Test + public void getMissing() throws Exception { + MimeMappings mappings = new MimeMappings(); + assertThat(mappings.get("foo"), nullValue()); + } + + @Test + public void makeUnmodifiable() throws Exception { + MimeMappings mappings = new MimeMappings(); + mappings.add("foo", "bar"); + MimeMappings unmodifiable = MimeMappings.unmodifiableMappings(mappings); + try { + unmodifiable.remove("foo"); + } + catch (UnsupportedOperationException ex) { + // Expected + } + mappings.remove("foo"); + assertThat(unmodifiable.get("foo"), nullValue()); + } + + private MimeMappings getTomatDefaults() { + final MimeMappings mappings = new MimeMappings(); + Context ctx = mock(Context.class); + Wrapper wrapper = mock(Wrapper.class); + given(ctx.createWrapper()).willReturn(wrapper); + willAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + mappings.add((String) args[0], (String) args[1]); + return null; + } + }).given(ctx).addMimeMapping(anyString(), anyString()); + Tomcat.initWebappDefaults(ctx); + return mappings; + } + +} diff --git a/spring-zero-samples/spring-zero-sample-tomcat/src/main/resources/public/test.css b/spring-zero-samples/spring-zero-sample-tomcat/src/main/resources/public/test.css new file mode 100644 index 0000000000..a6e538f4a4 --- /dev/null +++ b/spring-zero-samples/spring-zero-sample-tomcat/src/main/resources/public/test.css @@ -0,0 +1 @@ +p.{}