diff --git a/src/main/src/main/java/applicationContext.xml b/src/main/src/main/java/applicationContext.xml
index e294a9a..d5e5926 100644
--- a/src/main/src/main/java/applicationContext.xml
+++ b/src/main/src/main/java/applicationContext.xml
@@ -87,6 +87,9 @@
       <constructor-arg ref="resourceLoader" />
     </bean>
     
+    <!-- classpath publisher, allows resources in the classpath to be published as static files -->
+    <bean id="classpathPublisher" class="org.geoserver.ows.ClasspathPublisher"/>
+    
   <!-- jai initializer -->
   <bean id="jaiInitializer" class="org.geoserver.jai.JAIInitializer"/>
   
@@ -132,6 +135,16 @@
      </property>
   </bean>
   
+  <bean id="classpathPublisherMapping"
+    class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
+     <property name="alwaysUseFullPath" value="true"/>
+     <property name="mappings">
+       <props>
+        <prop key="/schemas/**">classpathPublisher</prop>
+       </props>
+     </property>
+  </bean>
+  
   <!-- geotools repository adapter for catalog -->
   <bean id="catalogRepository" class="org.geoserver.catalog.CatalogRepository">
     <constructor-arg ref="catalog"/>
diff --git a/src/ows/src/main/java/org/geoserver/ows/AbstractURLPublisher.java b/src/ows/src/main/java/org/geoserver/ows/AbstractURLPublisher.java
new file mode 100644
index 0000000..ae03ef4
--- /dev/null
+++ b/src/ows/src/main/java/org/geoserver/ows/AbstractURLPublisher.java
@@ -0,0 +1,143 @@
+/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
+ * This code is licensed under the GPL 2.0 license, availible at the root
+ * application directory.
+ */
+package org.geoserver.ows;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.geoserver.ows.util.EncodingInfo;
+import org.geoserver.ows.util.XmlCharsetDetector;
+import org.geotools.data.DataUtilities;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.AbstractController;
+
+/**
+ * Controller which publishes files through a web interface from the classpath
+ * <p>
+ * To use this controller, it should be mapped to a particular url in the url mapping of the spring
+ * dispatcher servlet. Example:
+ * 
+ * <pre>
+ * <code>
+ *   &lt;bean id="filePublisher" class="org.geoserver.ows.FilePublisher"/&gt;
+ *   &lt;bean id="dispatcherMappings"
+ *      &lt;property name="alwaysUseFullPath" value="true"/&gt;
+ *      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"&gt;
+ *      &lt;property name="mappings"&gt;
+ *        &lt;prop key="/schemas/** /*.xsd"&gt;filePublisher&lt;/prop&gt;
+ *        &lt;prop key="/schemas/** /*.dtd"&gt;filePublisher&lt;/prop&gt;
+ *        &lt;prop key="/styles/*"&gt;filePublisher&lt;/prop&gt;
+ *      &lt;/property&gt;
+ *   &lt;/bean&gt;
+ * </code>
+ * </pre>
+ * 
+ * </p>
+ * 
+ * @author Justin Deoliveira, The Open Planning Project
+ * @author Andrea Aime - GeoSolutions
+ */
+public abstract class AbstractURLPublisher extends AbstractController {
+
+    protected ModelAndView handleRequestInternal(HttpServletRequest request,
+            HttpServletResponse response) throws Exception {
+        URL url = getUrl(request);
+
+        // if not found return a 404
+        if (url == null) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return null;
+        }
+        
+        File file = DataUtilities.urlToFile(url);
+        if(file != null && file.exists() && file.isDirectory()) {
+            String uri = request.getRequestURI().toString();
+            uri += uri.endsWith("/") ? "index.html" : "/index.html";
+            
+            response.addHeader("Location", uri);
+            response.sendError(HttpServletResponse.SC_MOVED_TEMPORARILY);
+            
+            return null;
+        }
+        
+        
+
+        // set the mime if known by the servlet container, set nothing otherwise
+        // (Tomcat behaves like this when it does not recognize the file format)
+        String mime = getServletContext().getMimeType(new File(url.getFile()).getName());
+        if (mime != null) {
+            response.setContentType(mime);
+        }
+
+        // set the content length and content type
+        URLConnection connection = null;
+        InputStream input = null;
+        try {
+            connection = url.openConnection();
+            long length = connection.getContentLength();
+            if (length > 0 && length <= Integer.MAX_VALUE) {
+                response.setContentLength((int) length);
+            }
+
+            long lastModified = connection.getLastModified();
+            if (lastModified > 0) {
+                SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss",
+                        Locale.ENGLISH);
+                format.setTimeZone(TimeZone.getTimeZone("GMT"));
+                String formatted = format.format(new Date(lastModified)) + " GMT";
+                response.setHeader("Last-Modified", formatted);
+            }
+
+            // Guessing the charset (and closing the stream)
+            EncodingInfo encInfo = null;
+            OutputStream output = null;
+            final byte[] b4 = new byte[4];
+            int count = 0;
+            // open the output
+            input = connection.getInputStream();
+
+            // Read the first four bytes, and determine charset encoding
+            count = input.read(b4);
+            encInfo = XmlCharsetDetector.getEncodingName(b4, count);
+            response.setCharacterEncoding(encInfo.getEncoding() != null ? encInfo.getEncoding()
+                    : "UTF-8");
+
+            // send out the first four bytes read
+            output = response.getOutputStream();
+            output.write(b4, 0, count);
+
+            // copy the content to the output
+            byte[] buffer = new byte[8192];
+            int n = -1;
+            while ((n = input.read(buffer)) != -1) {
+                output.write(buffer, 0, n);
+            }
+        } finally {
+            if (input != null)
+                input.close();
+        }
+
+        return null;
+    }
+
+    /**
+     * Retrieves the resource URL from the specified request
+     * @param request
+     * @return
+     * @throws IOException 
+     */
+    protected abstract URL getUrl(HttpServletRequest request) throws IOException;
+}
diff --git a/src/ows/src/main/java/org/geoserver/ows/ClasspathPublisher.java b/src/ows/src/main/java/org/geoserver/ows/ClasspathPublisher.java
new file mode 100644
index 0000000..df7a4ea
--- /dev/null
+++ b/src/ows/src/main/java/org/geoserver/ows/ClasspathPublisher.java
@@ -0,0 +1,85 @@
+/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
+ * This code is licensed under the GPL 2.0 license, availible at the root
+ * application directory.
+ */
+package org.geoserver.ows;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.geoserver.ows.util.EncodingInfo;
+import org.geoserver.ows.util.XmlCharsetDetector;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.AbstractController;
+
+/**
+ * Controller which publishes files through a web interface from the classpath
+ * <p>
+ * To use this controller, it should be mapped to a particular url in the url mapping of the spring
+ * dispatcher servlet. Example:
+ * 
+ * <pre>
+ * <code>
+ *   &lt;bean id="filePublisher" class="org.geoserver.ows.FilePublisher"/&gt;
+ *   &lt;bean id="dispatcherMappings"
+ *      &lt;property name="alwaysUseFullPath" value="true"/&gt;
+ *      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"&gt;
+ *      &lt;property name="mappings"&gt;
+ *        &lt;prop key="/schemas/** /*.xsd"&gt;filePublisher&lt;/prop&gt;
+ *        &lt;prop key="/schemas/** /*.dtd"&gt;filePublisher&lt;/prop&gt;
+ *        &lt;prop key="/styles/*"&gt;filePublisher&lt;/prop&gt;
+ *      &lt;/property&gt;
+ *   &lt;/bean&gt;
+ * </code>
+ * </pre>
+ * 
+ * </p>
+ * 
+ * @author Justin Deoliveira, The Open Planning Project
+ * @author Andrea Aime - GeoSolutions
+ */
+public class ClasspathPublisher extends AbstractURLPublisher {
+    Class clazz;
+
+    /**
+     * Creates the new classpath publisher.
+     * 
+     * @param clazz the class used to perform classpath lookups with
+     *        {@link Class#getResource(String)}
+     */
+    public ClasspathPublisher(Class clazz) {
+        this.clazz = clazz;
+    }
+
+    public ClasspathPublisher() {
+        this.clazz = ClasspathPublisher.class;
+    }
+
+    @Override
+    protected URL getUrl(HttpServletRequest request) {
+        String ctxPath = request.getContextPath();
+        String reqPath = request.getRequestURI();
+        reqPath = reqPath.substring(ctxPath.length());
+
+        // try a few lookups
+        URL url = clazz.getResource(reqPath);
+        if (url == null && !reqPath.startsWith("/")) {
+            url = clazz.getResource("/");
+        }
+        if (url == null && (reqPath.length() > 1) && reqPath.startsWith("/")) {
+            url = clazz.getResource(reqPath.substring(1));
+        }
+        return url;
+    }
+
+}
diff --git a/src/ows/src/main/java/org/geoserver/ows/FilePublisher.java b/src/ows/src/main/java/org/geoserver/ows/FilePublisher.java
index 848ac6a..d0ef206 100644
--- a/src/ows/src/main/java/org/geoserver/ows/FilePublisher.java
+++ b/src/ows/src/main/java/org/geoserver/ows/FilePublisher.java
@@ -6,7 +6,9 @@ package org.geoserver.ows;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.OutputStream;
+import java.net.URL;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Locale;
@@ -19,17 +21,18 @@ import javax.servlet.http.HttpServletResponse;
 import org.geoserver.ows.util.EncodingInfo;
 import org.geoserver.ows.util.XmlCharsetDetector;
 import org.geoserver.platform.GeoServerResourceLoader;
+import org.geotools.data.DataUtilities;
 import org.springframework.web.context.support.ServletContextResource;
 import org.springframework.web.context.support.ServletContextResourceLoader;
 import org.springframework.web.servlet.ModelAndView;
 import org.springframework.web.servlet.mvc.AbstractController;
 
-
 /**
  * Controller which publishes files through a web interface.
  * <p>
- * To use this controller, it should be mapped to a particular url in the url
- * mapping of the spring dispatcher servlet. Example:
+ * To use this controller, it should be mapped to a particular url in the url mapping of the spring
+ * dispatcher servlet. Example:
+ * 
  * <pre>
  * <code>
  *   &lt;bean id="filePublisher" class="org.geoserver.ows.FilePublisher"/&gt;
@@ -44,25 +47,26 @@ import org.springframework.web.servlet.mvc.AbstractController;
  *   &lt;/bean&gt;
  * </code>
  * </pre>
+ * 
  * </p>
- *
+ * 
  * @author Justin Deoliveira, The Open Planning Project
- *
+ * 
  */
-public class FilePublisher extends AbstractController {
+public class FilePublisher extends AbstractURLPublisher {
     /**
      * Resource loader
      */
     protected GeoServerResourceLoader loader;
-    
+
     /**
      * Servlet resource loader
      */
     protected ServletContextResourceLoader scloader;
-    
+
     /**
      * Creates the new file publisher.
-     *
+     * 
      * @param loader The loader used to locate files.
      */
     public FilePublisher(GeoServerResourceLoader loader) {
@@ -73,9 +77,9 @@ public class FilePublisher extends AbstractController {
     protected void initServletContext(ServletContext servletContext) {
         this.scloader = new ServletContextResourceLoader(servletContext);
     }
-    
-    protected ModelAndView handleRequestInternal(HttpServletRequest request,
-        HttpServletResponse response) throws Exception {
+
+    @Override
+    protected URL getUrl(HttpServletRequest request) throws IOException {
         String ctxPath = request.getContextPath();
         String reqPath = request.getRequestURI();
         reqPath = reqPath.substring(ctxPath.length());
@@ -83,91 +87,30 @@ public class FilePublisher extends AbstractController {
         if ((reqPath.length() > 1) && reqPath.startsWith("/")) {
             reqPath = reqPath.substring(1);
         }
-        
+
         // sigh, in order to serve the file we have to open it 2 times
         // 1) to determine its mime type
         // 2) to determine its encoding and really serve it
-        // we can't coalish 1) because we don't have a way to give jmimemagic the bytes at the 
+        // we can't coalish 1) because we don't have a way to give jmimemagic the bytes at the
         // beginning of the file without disabling extension quick matching
 
         // load the file
         File file = loader.find(reqPath);
 
         if (file == null && scloader != null) {
-            //try loading as a servlet resource
-            ServletContextResource resource = (ServletContextResource) scloader.getResource(reqPath);
+            // try loading as a servlet resource
+            ServletContextResource resource = (ServletContextResource) scloader
+                    .getResource(reqPath);
             if (resource != null && resource.exists()) {
                 file = resource.getFile();
             }
         }
         
-        if (file == null) {
-            //return a 404
-            response.sendError(HttpServletResponse.SC_NOT_FOUND);
-    
-            return null;
-        }
-
-        if (file.isDirectory()) {
-            String uri = request.getRequestURI().toString();
-            uri += uri.endsWith("/") ? "index.html" : "/index.html";
-            
-            response.addHeader("Location", uri);
-            response.sendError(HttpServletResponse.SC_MOVED_TEMPORARILY);
-            
+        if(file != null) {
+            return DataUtilities.fileToURL(file);
+        } else {
             return null;
         }
-
-        // set the mime if known by the servlet container, set nothing otherwise
-        // (Tomcat behaves like this when it does not recognize the file format)
-        String mime = getServletContext().getMimeType(file.getName());
-        if (mime != null)
-            response.setContentType(mime);
-        
-        // set the content length
-        long length = file.length();
-        if(length > 0 && length <= Integer.MAX_VALUE)
-            response.setContentLength((int) length);
-        
-        // set the last modified header
-        long lastModified = file.lastModified();
-        if(lastModified > 0) {
-            SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss", Locale.ENGLISH);
-            format.setTimeZone(TimeZone.getTimeZone("GMT"));
-            String formatted = format.format(new Date(lastModified)) + " GMT";
-            response.setHeader("Last-Modified", formatted);
-        }
-
-        // Guessing the charset (and closing the stream)
-        EncodingInfo encInfo = null;
-        FileInputStream input = null;
-        OutputStream output = null;
-        final byte[] b4 = new byte[4];
-        int count = 0;
-        try {
-            // open the output
-            input = new FileInputStream(file);
-           
-            // Read the first four bytes, and determine charset encoding
-            count = input.read(b4);
-            encInfo = XmlCharsetDetector.getEncodingName(b4, count);
-            response.setCharacterEncoding(encInfo.getEncoding() != null ? encInfo.getEncoding() : "UTF-8");
-            
-            // send out the first four bytes read
-            output = response.getOutputStream();
-            output.write(b4, 0, count);
-        
-            // copy the content to the output
-            byte[] buffer = new byte[8192];
-            int n = -1;
-            while ((n = input.read(buffer)) != -1) {
-                output.write(buffer, 0, n);
-            }
-        } finally {
-            if(output != null) output.flush();
-            if(input != null) input.close();
-        }
-
-        return null;
     }
+
 }
diff --git a/src/wms/src/main/java/applicationContext.xml b/src/wms/src/main/java/applicationContext.xml
index ce5ac94..1f74249 100644
--- a/src/wms/src/main/java/applicationContext.xml
+++ b/src/wms/src/main/java/applicationContext.xml
@@ -700,4 +700,15 @@
     <bean id="envVariableCleaner" class="org.geoserver.wms.EnvVariableCleaner"/>
     
     <bean id="rasterCleaner" class="org.geoserver.wms.RasterCleaner"/>
+    
+    <bean id="wmsClasspathPublisherMapping"
+    class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
+     <property name="alwaysUseFullPath" value="true"/>
+     <property name="mappings">
+       <props>
+        <prop key="/openlayers/**">classpathPublisher</prop>
+        <prop key="/options.png">classpathPublisher</prop>
+       </props>
+     </property>
+  </bean>
 </beans>
