001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.context.support;
018
019import java.io.File;
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.io.InputStream;
023import java.net.MalformedURLException;
024import java.net.URL;
025import javax.servlet.ServletContext;
026
027import org.springframework.core.io.AbstractFileResolvingResource;
028import org.springframework.core.io.ContextResource;
029import org.springframework.core.io.Resource;
030import org.springframework.util.Assert;
031import org.springframework.util.ResourceUtils;
032import org.springframework.util.StringUtils;
033import org.springframework.web.util.WebUtils;
034
035/**
036 * {@link org.springframework.core.io.Resource} implementation for
037 * {@link javax.servlet.ServletContext} resources, interpreting
038 * relative paths within the web application root directory.
039 *
040 * <p>Always supports stream access and URL access, but only allows
041 * {@code java.io.File} access when the web application archive
042 * is expanded.
043 *
044 * @author Juergen Hoeller
045 * @since 28.12.2003
046 * @see javax.servlet.ServletContext#getResourceAsStream
047 * @see javax.servlet.ServletContext#getResource
048 * @see javax.servlet.ServletContext#getRealPath
049 */
050public class ServletContextResource extends AbstractFileResolvingResource implements ContextResource {
051
052        private final ServletContext servletContext;
053
054        private final String path;
055
056
057        /**
058         * Create a new ServletContextResource.
059         * <p>The Servlet spec requires that resource paths start with a slash,
060         * even if many containers accept paths without leading slash too.
061         * Consequently, the given path will be prepended with a slash if it
062         * doesn't already start with one.
063         * @param servletContext the ServletContext to load from
064         * @param path the path of the resource
065         */
066        public ServletContextResource(ServletContext servletContext, String path) {
067                // check ServletContext
068                Assert.notNull(servletContext, "Cannot resolve ServletContextResource without ServletContext");
069                this.servletContext = servletContext;
070
071                // check path
072                Assert.notNull(path, "Path is required");
073                String pathToUse = StringUtils.cleanPath(path);
074                if (!pathToUse.startsWith("/")) {
075                        pathToUse = "/" + pathToUse;
076                }
077                this.path = pathToUse;
078        }
079
080
081        /**
082         * Return the ServletContext for this resource.
083         */
084        public final ServletContext getServletContext() {
085                return this.servletContext;
086        }
087
088        /**
089         * Return the path for this resource.
090         */
091        public final String getPath() {
092                return this.path;
093        }
094
095        /**
096         * This implementation checks {@code ServletContext.getResource}.
097         * @see javax.servlet.ServletContext#getResource(String)
098         */
099        @Override
100        public boolean exists() {
101                try {
102                        URL url = this.servletContext.getResource(this.path);
103                        return (url != null);
104                }
105                catch (MalformedURLException ex) {
106                        return false;
107                }
108        }
109
110        /**
111         * This implementation delegates to {@code ServletContext.getResourceAsStream},
112         * which returns {@code null} in case of a non-readable resource (e.g. a directory).
113         * @see javax.servlet.ServletContext#getResourceAsStream(String)
114         */
115        @Override
116        public boolean isReadable() {
117                InputStream is = this.servletContext.getResourceAsStream(this.path);
118                if (is != null) {
119                        try {
120                                is.close();
121                        }
122                        catch (IOException ex) {
123                                // ignore
124                        }
125                        return true;
126                }
127                else {
128                        return false;
129                }
130        }
131
132        /**
133         * This implementation delegates to {@code ServletContext.getResourceAsStream},
134         * but throws a FileNotFoundException if no resource found.
135         * @see javax.servlet.ServletContext#getResourceAsStream(String)
136         */
137        @Override
138        public InputStream getInputStream() throws IOException {
139                InputStream is = this.servletContext.getResourceAsStream(this.path);
140                if (is == null) {
141                        throw new FileNotFoundException("Could not open " + getDescription());
142                }
143                return is;
144        }
145
146        /**
147         * This implementation delegates to {@code ServletContext.getResource},
148         * but throws a FileNotFoundException if no resource found.
149         * @see javax.servlet.ServletContext#getResource(String)
150         */
151        @Override
152        public URL getURL() throws IOException {
153                URL url = this.servletContext.getResource(this.path);
154                if (url == null) {
155                        throw new FileNotFoundException(
156                                        getDescription() + " cannot be resolved to URL because it does not exist");
157                }
158                return url;
159        }
160
161        /**
162         * This implementation resolves "file:" URLs or alternatively delegates to
163         * {@code ServletContext.getRealPath}, throwing a FileNotFoundException
164         * if not found or not resolvable.
165         * @see javax.servlet.ServletContext#getResource(String)
166         * @see javax.servlet.ServletContext#getRealPath(String)
167         */
168        @Override
169        public File getFile() throws IOException {
170                URL url = this.servletContext.getResource(this.path);
171                if (url != null && ResourceUtils.isFileURL(url)) {
172                        // Proceed with file system resolution...
173                        return super.getFile();
174                }
175                else {
176                        String realPath = WebUtils.getRealPath(this.servletContext, this.path);
177                        return new File(realPath);
178                }
179        }
180
181        /**
182         * This implementation creates a ServletContextResource, applying the given path
183         * relative to the path of the underlying file of this resource descriptor.
184         * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
185         */
186        @Override
187        public Resource createRelative(String relativePath) {
188                String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
189                return new ServletContextResource(this.servletContext, pathToUse);
190        }
191
192        /**
193         * This implementation returns the name of the file that this ServletContext
194         * resource refers to.
195         * @see org.springframework.util.StringUtils#getFilename(String)
196         */
197        @Override
198        public String getFilename() {
199                return StringUtils.getFilename(this.path);
200        }
201
202        /**
203         * This implementation returns a description that includes the ServletContext
204         * resource location.
205         */
206        @Override
207        public String getDescription() {
208                return "ServletContext resource [" + this.path + "]";
209        }
210
211        @Override
212        public String getPathWithinContext() {
213                return this.path;
214        }
215
216
217        /**
218         * This implementation compares the underlying ServletContext resource locations.
219         */
220        @Override
221        public boolean equals(Object obj) {
222                if (obj == this) {
223                        return true;
224                }
225                if (obj instanceof ServletContextResource) {
226                        ServletContextResource otherRes = (ServletContextResource) obj;
227                        return (this.servletContext.equals(otherRes.servletContext) && this.path.equals(otherRes.path));
228                }
229                return false;
230        }
231
232        /**
233         * This implementation returns the hash code of the underlying
234         * ServletContext resource location.
235         */
236        @Override
237        public int hashCode() {
238                return this.path.hashCode();
239        }
240
241}