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:-
- 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.
- 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.
- 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
. - 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
.
![1 1](http://whyjava.files.wordpress.com/2009/08/11.jpg?w=1024&h=624)
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.
![s1 s1](http://whyjava.files.wordpress.com/2009/10/s1.jpg?w=1024&h=640)
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.
![s2 s2](http://whyjava.files.wordpress.com/2009/10/s2.jpg?w=1024&h=518)
- 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 :-
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
08 | < filter-name >struts2-gae</ filter-name > |
09 | < filter-class >com.struts2.gae.dispatcher.GaeFilterDispatcher</ filter-class > |
12 | < filter-name >struts2-gae</ filter-name > |
13 | < url-pattern >/*</ url-pattern > |
16 | < welcome-file >index.html</ welcome-file > |
- 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
01 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
04 | < meta http-equiv = "content-type" content = "text/html; charset=UTF-8" > |
05 | < title >Struts2 File upload on Google App Engine</ title > |
09 | < h1 >Struts2 File upload on Google App Engine!</ h1 > |
12 | < td colspan = "2" style = "font-weight: bold;" >Available Application:</ td > |
15 | < td >< a href = "/add" />Upload my Photo</ td > |
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"%> |
07 | < meta http-equiv = "Content-Type" content = "text/html; charset=ISO-8859-1" > |
08 | < title >Upload my Photo</ title > |
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 > |
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 | <!DOCTYPE struts PUBLIC |
03 | "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" |
06 | < include file = "struts-default.xml" ></ include > |
07 | < package name = "" namespace = "/" extends = "struts-default" > |
09 | < result >/upload.jsp</ result > |
- 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).
03 | <? xml version = "1.0" encoding = "utf-8" ?> |
10 | < filter-name >struts2-gae</ filter-name > |
11 | < filter-class >com.struts2.gae.dispatcher.GaeFilterDispatcher</ filter-class > |
14 | < filter-name >struts2-gae</ filter-name > |
15 | < url-pattern >/*</ url-pattern > |
18 | < listener-class >com.struts2.gae.listener.OgnlListener</ listener-class > |
21 | < welcome-file >index.html</ welcome-file > |
- 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
318 | private int trailingCharsToStrip() { |
319 | String content = new String(text); |
320 | int lastNewlineIndex = lastNewLineIndex(); |
321 | if (lastNewlineIndex == - 1 && beginColumn != 1 ) { |
324 | String substring = content.substring(lastNewlineIndex + 1 ); |
325 | if (substring.trim().length() > 0 ) { |
330 | for (TemplateElement elem = this .nextTerminalNode(); elem != null |
331 | && elem.beginLine == this .endLine; elem = elem |
332 | .nextTerminalNode()) { |
333 | if (elem.heedsTrailingWhitespace()) { |
337 | return substring.length(); |
340 | boolean heedsTrailingWhitespace() { |
344 | for ( int i = 0 ; i < text.length; i++) { |
346 | if (c == '/n' || c == '/r' ) { |
349 | if (!Character.isWhitespace(c)) { |
356 | boolean heedsOpeningWhitespace() { |
360 | for ( int i = text.length - 1 ; i >= 0 ; i--) { |
362 | if (c == '/n' || c == '/r' ) { |
365 | if (!Character.isWhitespace(c)) { |
372 | boolean isIgnorable() { |
373 | if (text == null || text.length == 0 ) { |
376 | if (!isWhitespace()) { |
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)); |
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); |
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); |
400 | private static char [] substring( char [] c, int from) { |
401 | return substring(c, from, c.length); |
404 | private static char [] trim( char [] c) { |
408 | return new String(c).trim().toCharArray(); |
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); |
418 | boolean isWhitespace() { |
419 | return text == null || trim(text).length == 0 ; |
- 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.
02 | <? xml version = "1.0" encoding = "UTF-8" ?> |
03 | <!DOCTYPE struts PUBLIC |
04 | "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" |
07 | < include file = "struts-default.xml" ></ include > |
08 | < package name = "" namespace = "/" extends = "struts-default" > |
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 > |
18 | < default-interceptor-ref name = "fileUploadStack" /> |
20 | < result >/upload.jsp</ result > |
- Next step is to create the UploadAction which will handle the upload request.
01 | package com.fileupload; |
03 | import java.io.InputStream; |
05 | import org.apache.commons.io.IOUtils; |
07 | import com.opensymphony.xwork2.ActionSupport; |
09 | public class UploadAction extends ActionSupport { |
11 | private static final long serialVersionUID = -300329750248730163L; |
13 | private String photoContentType; |
14 | private String photoFileName; |
15 | private InputStream photoStream; |
17 | public String upload() throws Exception { |
18 | photoStream = IOUtils.toInputStream(photo, "ISO-8859-1" ); |
22 | public String getPhoto() { |
26 | public void setPhoto(String photo) { |
30 | public String getPhotoContentType() { |
31 | return photoContentType; |
34 | public void setPhotoContentType(String photoContentType) { |
35 | this .photoContentType = photoContentType; |
38 | public String getPhotoFileName() { |
42 | public void setPhotoFileName(String photoFileName) { |
43 | this .photoFileName = photoFileName; |
46 | public InputStream getPhotoStream() { |
50 | public void setPhotoStream(InputStream photoStream) { |
51 | this .photoStream = photoStream; |
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 <s:file name=”photo” label=”Upload new Photo”></s:file>. 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
01 | <? xml version = "1.0" encoding = "UTF-8" ?> |
02 | <!DOCTYPE struts PUBLIC |
03 | "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" |
06 | < include file = "struts-default.xml" ></ include > |
07 | < package name = "" namespace = "/" extends = "struts-default" > |
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 > |
17 | < default-interceptor-ref name = "fileUploadStack" /> |
19 | < result >/upload.jsp</ result > |
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 > |
- 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/