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:-
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.
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-gae filter-name > |
09 |
< filter-class >com.struts2.gae.dispatcher.GaeFilterDispatcher filter-class > |
10 |
filter > |
11 |
< filter-mapping > |
12 |
< filter-name >struts2-gae filter-name > |
13 |
< url-pattern >/* url-pattern > |
14 |
filter-mapping > |
15 |
< welcome-file-list > |
16 |
< welcome-file >index.html welcome-file > |
17 |
welcome-file-list > |
18 |
web-app > |
index.html
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 Engine title > |
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 Photo td > |
16 |
tr > |
17 |
table > |
18 |
body > |
19 |
html > |
upload.jsp
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 Photo title > |
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:-
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.jsp result > |
10 |
action > |
11 |
package > |
12 |
struts > |
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
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-gae filter-name > |
11 |
< filter-class >com.struts2.gae.dispatcher.GaeFilterDispatcher filter-class > |
12 |
filter > |
13 |
< filter-mapping > |
14 |
< filter-name >struts2-gae filter-name > |
15 |
< url-pattern >/* url-pattern > |
16 |
filter-mapping > |
17 |
< listener > |
18 |
< listener-class >com.struts2.gae.listener.OgnlListener listener-class > |
19 |
listener > |
20 |
< welcome-file-list > |
21 |
< welcome-file >index.html welcome-file > |
22 |
welcome-file-list > |
23 |
web-app > |
24 |
25 |
1 |
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
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 + "#noparse>"; |
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 |
} |
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.jsp result > |
21 |
action > |
22 |
package > |
23 |
24 |
struts > |
25 |
< pre > |
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
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.
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.jsp result > |
20 |
action > |
21 |
< action name = "upload" method = "upload" > |
22 |
< result name = "success" type = "stream" > |
23 |
< param name = "contentType" >image/jpeg param > |
24 |
< param name = "inputName" >photoStream param > |
25 |
< param name = "contentDisposition" >filename="photo.jpg" param > |
26 |
< param name = "bufferSize" >1024 param > |
27 |
result > |
28 |
action > |
29 |
package > |
30 |
31 |
struts > |
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/