File Upload on Google App Engine using struts2

Few months back, i wrote a blog on Creating Struts2 application on Google App Engine and some developers asked me how to upload a file using struts2 on google app engine. At that point of time, i was playing with google app engine and was not very clear about the limitation google app engine imposes. Google App Engine does not allow your application to write a file to their application server. This was a very big limitation as most of the application require some sort of file upload. So, i decided to find some way by which i can achieve the upload functionality and i found this link. But, i didn’t wanted to use servelt in my code because i was trying to build a application using struts 2.I wanted to work with actions and use FileUploadInterceptor. With the current implementation of struts 2 FileUploadInterceptor, i can’t do fileupload because it writes file to server.So, after spending some time with struts2 code, i wrote my own extension for Struts 2.This post will discuss how you can use small struts2 wrapper framework, i created for google app engine in your application to do fileupload and more.

Prerequisites for starting Struts2 Application on Google App Engine

Before you start building your sample application on google app engine using struts 2 you will need the following:-

  1. Google App Engine runs on java 5 and above so if necessary, download and install the Java SE Development Kit (JDK) for your platform and for mac users download and install the latest version.
  2. In this example we will be using Eclipse as our ide. So if necessary, download eclipse and google app engine plugin for eclipse.You will also need to download the google java app engine SDK. For more information you can refer to installing the java SDK for google app engine.
  3. Download the latest release of Struts2 framework.If you want to learn struts 2 a very good reference is struts 2 in action book.Please buy Struts 2 in Action.
  4. Download the latest release of struts2-gae framework.It is an wrapper around struts 2 for google app engine.

Step by Step procedure to create Struts2 File Upload application on Google App Engine.

Step 1: Create a new project by clicking the New Web Application Project button in the toolbar.

Step 2 : Give the project name say struts2-fileupload as we are going to create a simple file upload application. Enter package name as com.login and uncheck “Use Google Web Toolkit,” and ensure “Use Google App Engine” is checked and click the finish button.

Step3 : When you click the finish button you will get a sample HelloWorld application, which you can run going in the Run menu, select Run As > Web Application.By default application will run at port 8080, you can view the sample application at http://locahost:8080. For more information on the sample google web application created by the plugin you can refer to Google java app engine documentation .Please keep in mind that intent of this document is not to provide developers the overview of Google App engine for Java.

Step4 : By now you are ready with the google app engine infrastructure and we can move to the next step of creating a file upload application in Struts 2.

  • for creating a struts 2 application you will need to first add the required dependencies to the struts2-fileupload project. The required struts 2 jars are below mentioned and you can find these jars in struts2 package you downloaded inside the lib folder :-
    • commons-fileupload-1.2.1.jar
    • commons-io-1.3.2.jar
    • commons-logging-1.1.jar
    • freemarker-2.3.13.jar
    • ognl-2.6.11.jar
    • struts2-core-2.1.6.jar
    • struts2-gae-0.1.jar
    • xwork-2.1.2.jar
  • Add these dependencies in your eclipse java build path.

  • Add these dependencies in the war/WEB-INF/lib folder so that these jars gets deployed along with your application.
  • First step in creating a struts 2 application is configuring the web.xml (deployment descriptor) which is located in WEB-INF folder.You can remove the servlet declaration from web.xml as we will not be needing this.In the last post,we configured FilterDispatcher(this is theFilterDispatcher which comes with struts2) but in this application we need to add the GaeFilterDispatcher(this is provided by struts 2 extension framework for GAE). We will declare GaeFilterDispatcher in web.xml, because in struts2 every request goes pass through a FilterDispatcher, which will invoke the appropriate action corresponding to the URL mapping.So our web.xml will look like :-
view source
print ?
01 xml version="1.0" encoding="utf-8"?>
02 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
03 xmlns="http://java.sun.com/xml/ns/javaee"
04 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
05 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
06 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
07 <filter>
08 <filter-name>struts2-gaefilter-name>
09 <filter-class>com.struts2.gae.dispatcher.GaeFilterDispatcherfilter-class>
10 filter>
11 <filter-mapping>
12 <filter-name>struts2-gaefilter-name>
13 <url-pattern>/*url-pattern>
14 filter-mapping>
15 <welcome-file-list>
16 <welcome-file>index.htmlwelcome-file>
17 welcome-file-list>
18 web-app>
  • To start we will be creating a jsp fileupload page using struts 2 tag library and we will call file upload page from index.html.To call the fileupload page there are two ways first, we can directly call the upload.jsp page from link second, we can calling it through struts. We will be taking the second step as this will show you how to configure actions when you dont need to invoke any action.Lets first see how our login page and index.html will look like :-

index.html

view source
print ?
01
02 <html>
03 <head>
04 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
05 <title>Struts2 File upload on Google App Enginetitle>
06 head>
07  
08 <body>
09 <h1>Struts2 File upload on Google App Engine!h1>
10 <table>
11 <tr>
12 <td colspan="2" style="font-weight: bold;">Available Application:td>
13 tr>
14 <tr>
15 <td><a href="/add" />Upload my Phototd>
16 tr>
17 table>
18 body>
19 html>

upload.jsp

view source
print ?
01 <%@ page language="java" contentType="text/html; charset=ISO-8859-1"pre>
02 pageEncoding="ISO-8859-1"%>
03 <%@ taglib prefix="s" uri="/struts-tags"%>
04 http://www.w3.org/TR/html4/loose.dtd">
05 <html>
06 <head>
07 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
08 <title>Upload my Phototitle>
09 head>
10 <body>
11 <s:form action="upload" method="post" enctype="multipart/form-data">
12 <s:file name="photo" label="Upload new Photo">s:file>
13 <s:submit value="Upload">s:submit>
14 s:form>
15  
16 body>
17 html>

After creating the upload.jsp we need to configure this as action in the struts.xml file which you be should put inside source folder parallel to log4j.properties file.We can configure action as mentioned below:-

view source
print ?
01 xml version="1.0" encoding="UTF-8" ?>
02
03 "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
04 "http://struts.apache.org/dtds/struts-2.0.dtd">
05 <struts>
06 <include file="struts-default.xml">include>
07 <package name="" namespace="/" extends="struts-default">
08 <action name="add">
09 <result>/upload.jspresult>
10 action>
11 package>
12 struts>
  • Now try running this application by right click on project run as > web application and click http://localhost:8080. You will see index.html and when you click on upload my photo you will get this exception :-

SEVERE: Unable to set parameter [location] in result of type [org.apache.struts2.dispatcher.ServletDispatcherResult]

Caught OgnlException while setting property ‘location’ on type ‘org.apache.struts2.dispatcher.ServletDispatcherResult’. – Class: ognl.OgnlRuntime

File: OgnlRuntime.java

Method: invokeMethod

Line: 508 – ognl/OgnlRuntime.java:508:-1

at com.opensymphony.xwork2.ognl.OgnlUtil.internalSetProperty(OgnlUtil.java:392)

Caused by: java.lang.IllegalAccessException: Method [public void org.apache.struts2.dispatcher.StrutsResultSupport.setLocation(java.lang.String)] cannot be accessed.

at ognl.OgnlRuntime.invokeMethod(OgnlRuntime.java:508)

at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:812)

at ognl.OgnlRuntime.setMethodValue(OgnlRuntime.java:964)

at ognl.ObjectPropertyAccessor.setPossibleProperty(ObjectPropertyAccessor.java:75)

at ognl.ObjectPropertyAccessor.setProperty(ObjectPropertyAccessor.java:131)

at com.opensymphony.xwork2.ognl.accessor.ObjectAccessor.setProperty(ObjectAccessor.java:28)

at ognl.OgnlRuntime.setProperty(OgnlRuntime.java:1656)

at ognl.ASTProperty.setValueBody(ASTProperty.java:101)

at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:177)

at ognl.SimpleNode.setValue(SimpleNode.java:246)

at ognl.Ognl.setValue(Ognl.java:476)

at com.opensymphony.xwork2.ognl.OgnlUtil.setValue(OgnlUtil.java:192)

at com.opensymphony.xwork2.ognl.OgnlUtil.internalSetProperty(OgnlUtil.java:385)

… 73 more

  • In order to resolve this problem we need to make entry in web.xml file also for OgnlListener.This OgnlListener is also provided by struts2-gae framework.(A struts2 extension framework for GAE).
view source
print ?
01 [/sourcecode]
02  
03 xml version="1.0" encoding="utf-8"?>
04 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
05 xmlns="http://java.sun.com/xml/ns/javaee"
06 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
07 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
08 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
09 <filter>
10 <filter-name>struts2-gaefilter-name>
11 <filter-class>com.struts2.gae.dispatcher.GaeFilterDispatcherfilter-class>
12 filter>
13 <filter-mapping>
14 <filter-name>struts2-gaefilter-name>
15 <url-pattern>/*url-pattern>
16 filter-mapping>
17 <listener>
18 <listener-class>com.struts2.gae.listener.OgnlListenerlistener-class>
19 listener>
20 <welcome-file-list>
21 <welcome-file>index.htmlwelcome-file>
22 welcome-file-list>
23 web-app>
24  
25 1
  • You need to add this step if you are using Google App Engine 1.2.6 because when you run struts2 application on google app engine 1.2.6 you will get the following error:-

javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class. Please see the Google App Engine developer’s guide for more details.
at org.apache.jasper.runtime.PageContextImpl.doHandlePageException(PageContextImpl.java:825)
at org.apache.jasper.runtime.PageContextImpl.access$1100(PageContextImpl.java:64)
at org.apache.jasper.runtime.PageContextImpl$12.run(PageContextImpl.java:745)
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.jasper.runtime.PageContextImpl.handlePageException(PageContextImpl.java:743)
at org.apache.jsp.login_jsp._jspService(login_jsp.java:86)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:94)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:324)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:292)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:236)
at com.google.appengine.tools.development.PrivilegedJspServlet.access$101(PrivilegedJspServlet.java:23)
at com.google.appengine.tools.development.PrivilegedJspServlet$2.run(PrivilegedJspServlet.java:59)
at java.security.AccessController.doPrivileged(Native Method)
at com.google.appengine.tools.development.PrivilegedJspServlet.service(PrivilegedJspServlet.java)

To avoid this error you need to create a new package “freemarker.core” in your source folder and add the following class

view source
print ?
001
002  
003 /*
004 * Copyright (c) 2003 The Visigoth Software Society. All rights
005 * reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met:
010 *
011 * 1. Redistributions of source code must retain the above copyright
012 * notice, this list of conditions and the following disclaimer.
013 *
014 * 2. Redistributions in binary form must reproduce the above
015 copyright
016 * notice, this list of conditions and the following disclaimer in
017 * the documentation and/or other materials provided with the
018 * distribution.
019 *
020 * 3. The end-user documentation included with the redistribution, if
021 * any, must include the following acknowledgement:
022 * "This product includes software developed by the
023 * Visigoth Software Society (http://www.visigoths.org/)."
024 * Alternately, this acknowledgement may appear in the software
025 itself,
026 * if and wherever such third-party acknowledgements normally
027 appear.
028 *
029 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names
030 of the
031 * project contributors may be used to endorse or promote products
032 derived
033 * from this software without prior written permission. For written
034 * permission, please contact [email protected].
035 *
036 * 5. Products derived from this software may not be called
037 "FreeMarker" or "Visigoth"
038 * nor may "FreeMarker" or "Visigoth" appear in their names
039 * without prior written permission of the Visigoth Software
040 Society.
041 *
042 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
043 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
044 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
045 * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
046 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
047 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
048 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
049 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
050 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
051 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
052 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
053 * SUCH DAMAGE.
054 *
055 ====================================================================
056 *
057 * This software consists of voluntary contributions made by many
058 * individuals on behalf of the Visigoth Software Society. For more
059 * information on the Visigoth Software Society, please see
060 * http://www.visigoths.org/
061 */
062  
063 package freemarker.core;
064  
065 import java.io.IOException;
066  
067 /**
068 * A TemplateElement representing a block of plain text.
069 *
070 * @version $Id: TextBlock.java,v 1.17 2004/01/06 17:06:42 szegedia Exp $
071 */
072 public final class TextBlock extends TemplateElement {
073 private static final char[] EMPTY_CHAR_ARRAY = new char[0];
074 static final TextBlock EMPTY_BLOCK = new TextBlock(EMPTY_CHAR_ARRAY, false);
075 // We're using char[] instead of String for storing the text block because
076 // Writer.write(String) involves copying the String contents to a char[]
077 // using String.getChars(), and then calling Writer.write(char[]).By
078 // using Writer.write(char[]) directly, we avoid array copying on each
079 // write.
080 private char[] text;
081 private final boolean unparsed;
082  
083 public TextBlock(String text) {
084 this(text, false);
085 }
086  
087 public TextBlock(String text, boolean unparsed) {
088 this(text.toCharArray(), unparsed);
089 }
090  
091 private TextBlock(char[] text, boolean unparsed) {
092 this.text = text;
093 this.unparsed = unparsed;
094 }
095  
096 /**
097 * Simply outputs the text.
098 */
099 public void accept(Environment env) throws IOException {
100 env.getOut().write(text);
101 }
102  
103 public String getCanonicalForm() {
104 String text = new String(this.text);
105 if (unparsed) {
106 return "<#noparse>" + text + "";
107 }
108 return text;
109 }
110  
111 public String getDescription() {
112 String s = new String(text).trim();
113 if (s.length() == 0) {
114 return "whitespace";
115 }
116 if (s.length() > 20) {
117 s = s.substring(0, 20) + "...";
118 s = s.replace('/n', ' ');
119 s = s.replace('/r', ' ');
120 }
121 return "text block (" + s + ")";
122 }
123  
124 TemplateElement postParseCleanup(boolean stripWhitespace) {
125 if (text.length == 0)
126 return this;
127 int openingCharsToStrip = 0, trailingCharsToStrip = 0;
128 boolean deliberateLeftTrim = deliberateLeftTrim();
129 boolean deliberateRightTrim = deliberateRightTrim();
130 if (!stripWhitespace || text.length == 0) {
131 return this;
132 }
133 if (parent.parent == null && previousSibling() == null)
134 return this;
135 if (!deliberateLeftTrim) {
136 trailingCharsToStrip = trailingCharsToStrip();
137 }
138 if (!deliberateRightTrim) {
139 openingCharsToStrip = openingCharsToStrip();
140 }
141 if (openingCharsToStrip == 0 && trailingCharsToStrip == 0) {
142 return this;
143 }
144 this.text = substring(text, openingCharsToStrip, text.length
145 - trailingCharsToStrip);
146 if (openingCharsToStrip > 0) {
147 this.beginLine++;
148 this.beginColumn = 1;
149 }
150 if (trailingCharsToStrip > 0) {
151 this.endColumn = 0;
152 }
153 return this;
154 }
155  
156 /**
157 * Scans forward the nodes on the same line to see whether there is a
158 * deliberate left trim in effect. Returns true if the left trim was
159 * present.
160 */
161 private boolean deliberateLeftTrim() {
162 boolean result = false;
163 for (TemplateElement elem = this.nextTerminalNode(); elem != null
164 && elem.beginLine == this.endLine; elem = elem
165 .nextTerminalNode()) {
166 if (elem instanceof TrimInstruction) {
167 TrimInstruction ti = (TrimInstruction) elem;
168 if (!ti.left && !ti.right) {
169 result = true;
170 }
171 if (ti.left) {
172 result = true;
173 int lastNewLineIndex = lastNewLineIndex();
174 if (lastNewLineIndex >= 0 || beginColumn == 1) {
175 char[] firstPart = substring(text, 0,
176 lastNewLineIndex + 1);
177 char[] lastLine = substring(text, 1 + lastNewLineIndex);
178 if (trim(lastLine).length == 0) {
179 this.text = firstPart;
180 this.endColumn = 0;
181 } else {
182 int i = 0;
183 while (Character.isWhitespace(lastLine[i])) {
184 i++;
185 }
186 char[] printablePart = substring(lastLine, i);
187 this.text = concat(firstPart, printablePart);
188 }
189 }
190 }
191 }
192 }
193 if (result) {
194 }
195 return result;
196 }
197  
198 /**
199 * Checks for the presence of a t or rt directive on the same line. Returns
200 * true if the right trim directive was present.
201 */
202 private boolean deliberateRightTrim() {
203 boolean result = false;
204 for (TemplateElement elem = this.prevTerminalNode(); elem != null
205 && elem.endLine == this.beginLine; elem = elem
206 .prevTerminalNode()) {
207 if (elem instanceof TrimInstruction) {
208 TrimInstruction ti = (TrimInstruction) elem;
209 if (!ti.left && !ti.right) {
210 result = true;
211 }
212 if (ti.right) {
213 result = true;
214 int firstLineIndex = firstNewLineIndex() + 1;
215 if (firstLineIndex == 0) {
216 return false;
217 }
218 if (text.length > firstLineIndex
219 && text[firstLineIndex - 1] == '/r'
220 && text[firstLineIndex] == '/n') {
221 firstLineIndex++;
222 }
223 char[] trailingPart = substring(text, firstLineIndex);
224 char[] openingPart = substring(text, 0, firstLineIndex);
225 if (trim(openingPart).length == 0) {
226 this.text = trailingPart;
227 this.beginLine++;
228 this.beginColumn = 1;
229 } else {
230 int lastNonWS = openingPart.length - 1;
231 while (Character.isWhitespace(text[lastNonWS])) {
232 lastNonWS--;
233 }
234 char[] printablePart = substring(text, 0, lastNonWS + 1);
235 if (trim(trailingPart).length == 0) {
236 // THIS BLOCK IS HEINOUS! THERE MUST BE A BETTER
237 // WAY! REVISIT (JR)
238 boolean trimTrailingPart = true;
239 for (TemplateElement te = this.nextTerminalNode(); te != null
240 && te.beginLine == this.endLine; te = te
241 .nextTerminalNode()) {
242 if (te.heedsOpeningWhitespace()) {
243 trimTrailingPart = false;
244 }
245 if (te instanceof TrimInstruction
246 && ((TrimInstruction) te).left) {
247 trimTrailingPart = true;
248 break;
249 }
250 }
251 if (trimTrailingPart)
252 trailingPart = EMPTY_CHAR_ARRAY;
253 }
254 this.text = concat(printablePart, trailingPart);
255 }
256 }
257 }
258 }
259 return result;
260 }
261  
262 /*
263 * private String leftTrim(String s) { int i =0; while (i
264 * (!Character.isWhitespace(s.charAt(i))) break; ++i; } return
265 * s.substring(i); }
266 */
267 private int firstNewLineIndex() {
268 String content = new String(text);
269 int newlineIndex1 = content.indexOf('/n');
270 int newlineIndex2 = content.indexOf('/r');
271 int result = newlineIndex1 >= 0 ? newlineIndex1 : newlineIndex2;
272 if (newlineIndex1 >= 0 && newlineIndex2 >= 0) {
273 result = Math.min(newlineIndex1, newlineIndex2);
274 }
275 return result;
276 }
277  
278 private int lastNewLineIndex() {
279 String content = new String(text);
280 return Math.max(content.lastIndexOf('/r'), content.lastIndexOf('/n'));
281 }
282  
283 /**
284 * figures out how many opening whitespace characters to strip in the
285 * post-parse cleanup phase.
286 */
287 private int openingCharsToStrip() {
288 int newlineIndex = firstNewLineIndex();
289 if (newlineIndex == -1 && beginColumn != 1) {
290 return 0;
291 }
292 ++newlineIndex;
293 if (text.length > newlineIndex) {
294 if (newlineIndex > 0 && text[newlineIndex - 1] == '/r'
295 && text[newlineIndex] == '/n') {
296 ++newlineIndex;
297 }
298 }
299 if (new String(text).substring(0, newlineIndex).trim().length() > 0) {
300 return 0;
301 }
302 // We look at the preceding elements on the line to see if we should
303 // strip the opening newline and any whitespace preceding it.
304 for (TemplateElement elem = this.prevTerminalNode(); elem != null
305 && elem.endLine == this.beginLine; elem = elem
306 .prevTerminalNode()) {
307 if (elem.heedsOpeningWhitespace()) {
308 return 0;
309 }
310 }
311 return newlineIndex;
312 }
313  
314 /**
315 * figures out how many trailing whitespace characters to strip in the
316 * post-parse cleanup phase.
317 */
318 private int trailingCharsToStrip() {
319 String content = new String(text);
320 int lastNewlineIndex = lastNewLineIndex();
321 if (lastNewlineIndex == -1 && beginColumn != 1) {
322 return 0;
323 }
324 String substring = content.substring(lastNewlineIndex + 1);
325 if (substring.trim().length() > 0) {
326 return 0;
327 }
328 // We look at the elements afterward on the same line to see if we
329 // should strip any whitespace after the last newline
330 for (TemplateElement elem = this.nextTerminalNode(); elem != null
331 && elem.beginLine == this.endLine; elem = elem
332 .nextTerminalNode()) {
333 if (elem.heedsTrailingWhitespace()) {
334 return 0;
335 }
336 }
337 return substring.length();
338 }
339  
340 boolean heedsTrailingWhitespace() {
341 if (isIgnorable()) {
342 return false;
343 }
344 for (int i = 0; i < text.length; i++) {
345 char c = text[i];
346 if (c == '/n' || c == '/r') {
347 return false;
348 }
349 if (!Character.isWhitespace(c)) {
350 return true;
351 }
352 }
353 return true;
354 }
355  
356 boolean heedsOpeningWhitespace() {
357 if (isIgnorable()) {
358 return false;
359 }
360 for (int i = text.length - 1; i >= 0; i--) {
361 char c = text[i];
362 if (c == '/n' || c == '/r') {
363 return false;
364 }
365 if (!Character.isWhitespace(c)) {
366 return true;
367 }
368 }
369 return true;
370 }
371  
372 boolean isIgnorable() {
373 if (text == null || text.length == 0) {
374 return true;
375 }
376 if (!isWhitespace()) {
377 return false;
378 }
379 // trick here
380 boolean atTopLevel = true;
381 TemplateElement prevSibling = previousSibling();
382 TemplateElement nextSibling = nextSibling();
383 return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
384 && ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling));
385 }
386  
387 private boolean nonOutputtingType(TemplateElement element) {
388 return (element instanceof Macro || element instanceof Assignment
389 || element instanceof AssignmentInstruction
390 || element instanceof PropertySetting
391 || element instanceof LibraryLoad || element instanceof Comment);
392 }
393  
394 private static char[] substring(char[] c, int from, int to) {
395 char[] c2 = new char[to - from];
396 System.arraycopy(c, from, c2, 0, c2.length);
397 return c2;
398 }
399  
400 private static char[] substring(char[] c, int from) {
401 return substring(c, from, c.length);
402 }
403  
404 private static char[] trim(char[] c) {
405 if (c.length == 0) {
406 return c;
407 }
408 return new String(c).trim().toCharArray();
409 }
410  
411 private static char[] concat(char[] c1, char[] c2) {
412 char[] c = new char[c1.length + c2.length];
413 System.arraycopy(c1, 0, c, 0, c1.length);
414 System.arraycopy(c2, 0, c, c1.length, c2.length);
415 return c;
416 }
417  
418 boolean isWhitespace() {
419 return text == null || trim(text).length == 0;
420 }
421  
422 }

 

  • Now if you run the web application you will see the upload page.
  • Next step is to configure the GaeFileUploadInterceptor in struts.xml file.In struts 2, file upload is done by FileUploadInterceptor which intercept all the MultiPartRequest and provides the File object to the action. Then action does what ever it wants to do with the File object.But, on google app engine you can’t get the File object because file writes and many other file related operations are not allowed on GAE.So, we will use GaeFileUploadInterceptor which provides a String object which contains all the file content.You can save this string as a blob into the datastore or convert this string object into InputStream and return to the user.You need to add an entry in struts.xml for GaeFileUploadInterceptor.
view source
print ?
01 pre>
02 xml version="1.0" encoding="UTF-8" ?>
03
04 "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
05 "http://struts.apache.org/dtds/struts-2.0.dtd">
06 <struts>
07 <include file="struts-default.xml">include>
08 <package name="" namespace="/" extends="struts-default">
09 <interceptors>
10 <interceptor name="gaeFileUploadInterceptor"
11 class="com.struts2.gae.interceptor.GaeFileUploadInterceptor" />
12 <interceptor-stack name="fileUploadStack">
13 <interceptor-ref name="gaeFileUploadInterceptor">interceptor-ref>
14 <interceptor-ref name="basicStack">interceptor-ref>
15 interceptor-stack>
16 interceptors>
17  
18 <default-interceptor-ref name="fileUploadStack" />
19 <action name="add">
20 <result>/upload.jspresult>
21 action>
22 package>
23  
24 struts>
25 <pre>
  • Next step is to create the UploadAction which will handle the upload request.
view source
print ?
01 package com.fileupload;
02  
03 import java.io.InputStream;
04  
05 import org.apache.commons.io.IOUtils;
06  
07 import com.opensymphony.xwork2.ActionSupport;
08  
09 public class UploadAction extends ActionSupport {
10  
11 private static final long serialVersionUID = -300329750248730163L;
12 private String photo;
13 private String photoContentType;
14 private String photoFileName;
15 private InputStream photoStream;
16  
17 public String upload() throws Exception {
18 photoStream = IOUtils.toInputStream(photo,"ISO-8859-1");
19 return "success";
20 }
21  
22 public String getPhoto() {
23 return photo;
24 }
25  
26 public void setPhoto(String photo) {
27 this.photo = photo;
28 }
29  
30 public String getPhotoContentType() {
31 return photoContentType;
32 }
33  
34 public void setPhotoContentType(String photoContentType) {
35 this.photoContentType = photoContentType;
36 }
37  
38 public String getPhotoFileName() {
39 return photoFileName;
40 }
41  
42 public void setPhotoFileName(String photoFileName) {
43 this.photoFileName = photoFileName;
44 }
45  
46 public InputStream getPhotoStream() {
47 return photoStream;
48 }
49  
50 public void setPhotoStream(InputStream photoStream) {
51 this.photoStream = photoStream;
52 }
53 }

If you notice there are three properties in the action photo,photoContentType,photoFileName.These properties get their values from the GaeFileUploadInterceptor. These properties have to start with name you gave the file in the upload.jsp . In this, name is “photo” so you will get your file content in a property called photo and the two other properties will start with photo.

The second thing to notice is that result of action is “success” which is not SUCCESS you normally use. In this we are using a different result type called org.apache.struts2.dispatcher.StreamResult. Stream result is a custom Result type for sending raw data (via an InputStream) directly to the HttpServletResponse.

  • Now we will configure UploadAction in struts.xml
view source
print ?
01 xml version="1.0" encoding="UTF-8" ?>
02
03 "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
04 "http://struts.apache.org/dtds/struts-2.0.dtd">
05 <struts>
06 <include file="struts-default.xml">include>
07 <package name="" namespace="/" extends="struts-default">
08 <interceptors>
09 <interceptor name="gaeFileUploadInterceptor"
10 class="com.struts2.gae.interceptor.GaeFileUploadInterceptor" />
11 <interceptor-stack name="fileUploadStack">
12 <interceptor-ref name="gaeFileUploadInterceptor">interceptor-ref>
13 <interceptor-ref name="basicStack">interceptor-ref>
14 interceptor-stack>
15 interceptors>
16  
17 <default-interceptor-ref name="fileUploadStack" />
18 <action name="add">
19 <result>/upload.jspresult>
20 action>
21 <action name="upload" method="upload">
22 <result name="success" type = "stream">
23 <param name="contentType">image/jpegparam>
24 <param name="inputName">photoStreamparam>
25 <param name="contentDisposition">filename="photo.jpg"param>
26 <param name="bufferSize">1024param>
27 result>
28 action>
29 package>
30  
31 struts>
  • Finally, Run this application you will be able to upload the photo and then view it in your browser.

In this blog, i have tried to explain you how you can do file upload using struts 2 on google app engine.Please make sure you download the struts2-gae jar.Hope this helps you all.

You can dowload the sample project from here.

 

http://whyjava.wordpress.com/2009/10/04/file-upload-on-google-app-engine-using-struts2/

你可能感兴趣的:(File Upload on Google App Engine using struts2)