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