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.portlet.multipart;
018
019import java.util.List;
020import javax.portlet.ActionRequest;
021import javax.portlet.PortletContext;
022
023import org.apache.commons.fileupload.FileItem;
024import org.apache.commons.fileupload.FileItemFactory;
025import org.apache.commons.fileupload.FileUpload;
026import org.apache.commons.fileupload.FileUploadBase;
027import org.apache.commons.fileupload.FileUploadException;
028import org.apache.commons.fileupload.portlet.PortletFileUpload;
029
030import org.springframework.util.Assert;
031import org.springframework.web.multipart.MaxUploadSizeExceededException;
032import org.springframework.web.multipart.MultipartException;
033import org.springframework.web.multipart.commons.CommonsFileUploadSupport;
034import org.springframework.web.portlet.context.PortletContextAware;
035import org.springframework.web.portlet.util.PortletUtils;
036
037/**
038 * {@link PortletMultipartResolver} implementation for
039 * <a href="https://commons.apache.org/proper/commons-fileupload">Apache Commons FileUpload</a>
040 * 1.2 or above.
041 *
042 * <p>Provides "maxUploadSize", "maxInMemorySize" and "defaultEncoding" settings as
043 * bean properties (inherited from {@link CommonsFileUploadSupport}). See corresponding
044 * PortletFileUpload / DiskFileItemFactory properties ("sizeMax", "sizeThreshold",
045 * "headerEncoding") for details in terms of defaults and accepted values.
046 *
047 * <p>Saves temporary files to the portlet container's temporary directory.
048 * Needs to be initialized <i>either</i> by an application context <i>or</i>
049 * via the constructor that takes a PortletContext (for standalone usage).
050 *
051 * @author Juergen Hoeller
052 * @since 2.0
053 * @see #CommonsPortletMultipartResolver(javax.portlet.PortletContext)
054 * @see #setResolveLazily
055 * @see org.springframework.web.multipart.commons.CommonsMultipartResolver
056 * @see org.apache.commons.fileupload.portlet.PortletFileUpload
057 * @see org.apache.commons.fileupload.disk.DiskFileItemFactory
058 */
059public class CommonsPortletMultipartResolver extends CommonsFileUploadSupport
060                implements PortletMultipartResolver, PortletContextAware {
061
062        private boolean resolveLazily = false;
063
064
065        /**
066         * Constructor for use as bean. Determines the portlet container's
067         * temporary directory via the PortletContext passed in as through the
068         * PortletContextAware interface (typically by an ApplicationContext).
069         * @see #setPortletContext
070         * @see org.springframework.web.portlet.context.PortletContextAware
071         */
072        public CommonsPortletMultipartResolver() {
073                super();
074        }
075
076        /**
077         * Constructor for standalone usage. Determines the portlet container's
078         * temporary directory via the given PortletContext.
079         * @param portletContext the PortletContext to use
080         */
081        public CommonsPortletMultipartResolver(PortletContext portletContext) {
082                this();
083                setPortletContext(portletContext);
084        }
085
086
087        /**
088         * Set whether to resolve the multipart request lazily at the time of
089         * file or parameter access.
090         * <p>Default is "false", resolving the multipart elements immediately, throwing
091         * corresponding exceptions at the time of the {@link #resolveMultipart} call.
092         * Switch this to "true" for lazy multipart parsing, throwing parse exceptions
093         * once the application attempts to obtain multipart files or parameters.
094         */
095        public void setResolveLazily(boolean resolveLazily) {
096                this.resolveLazily = resolveLazily;
097        }
098
099        /**
100         * Initialize the underlying {@code org.apache.commons.fileupload.portlet.PortletFileUpload}
101         * instance. Can be overridden to use a custom subclass, e.g. for testing purposes.
102         * @return the new PortletFileUpload instance
103         */
104        @Override
105        protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
106                return new PortletFileUpload(fileItemFactory);
107        }
108
109        @Override
110        public void setPortletContext(PortletContext portletContext) {
111                if (!isUploadTempDirSpecified()) {
112                        getFileItemFactory().setRepository(PortletUtils.getTempDir(portletContext));
113                }
114        }
115
116
117        @Override
118        public boolean isMultipart(ActionRequest request) {
119                return (request != null && PortletFileUpload.isMultipartContent(request));
120        }
121
122        @Override
123        public MultipartActionRequest resolveMultipart(final ActionRequest request) throws MultipartException {
124                Assert.notNull(request, "Request must not be null");
125                if (this.resolveLazily) {
126                        return new DefaultMultipartActionRequest(request) {
127                                @Override
128                                protected void initializeMultipart() {
129                                        MultipartParsingResult parsingResult = parseRequest(request);
130                                        setMultipartFiles(parsingResult.getMultipartFiles());
131                                        setMultipartParameters(parsingResult.getMultipartParameters());
132                                        setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
133                                }
134                        };
135                }
136                else {
137                        MultipartParsingResult parsingResult = parseRequest(request);
138                        return new DefaultMultipartActionRequest(request, parsingResult.getMultipartFiles(),
139                                        parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
140                }
141        }
142
143        /**
144         * Parse the given portlet request, resolving its multipart elements.
145         * @param request the request to parse
146         * @return the parsing result
147         * @throws MultipartException if multipart resolution failed.
148         */
149        protected MultipartParsingResult parseRequest(ActionRequest request) throws MultipartException {
150                String encoding = determineEncoding(request);
151                FileUpload fileUpload = prepareFileUpload(encoding);
152                try {
153                        List<FileItem> fileItems = ((PortletFileUpload) fileUpload).parseRequest(request);
154                        return parseFileItems(fileItems, encoding);
155                }
156                catch (FileUploadBase.SizeLimitExceededException ex) {
157                        throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
158                }
159                catch (FileUploadBase.FileSizeLimitExceededException ex) {
160                        throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
161                }
162                catch (FileUploadException ex) {
163                        throw new MultipartException("Failed to parse multipart portlet request", ex);
164                }
165        }
166
167        /**
168         * Determine the encoding for the given request.
169         * Can be overridden in subclasses.
170         * <p>The default implementation checks the request encoding,
171         * falling back to the default encoding specified for this resolver.
172         * @param request current portlet request
173         * @return the encoding for the request (never {@code null})
174         * @see javax.portlet.ActionRequest#getCharacterEncoding
175         * @see #setDefaultEncoding
176         */
177        protected String determineEncoding(ActionRequest request) {
178                String encoding = request.getCharacterEncoding();
179                if (encoding == null) {
180                        encoding = getDefaultEncoding();
181                }
182                return encoding;
183        }
184
185        @Override
186        public void cleanupMultipart(MultipartActionRequest request) {
187                if (request != null) {
188                        try {
189                                cleanupFileItems(request.getMultiFileMap());
190                        }
191                        catch (Throwable ex) {
192                                logger.warn("Failed to perform multipart cleanup for portlet request", ex);
193                        }
194                }
195        }
196
197}