Version 70.1 by smmccraw on 2007/09/29 23:54

Show last authors
1 == Introduction ==
2
3 WO developers have been able to deploy their applications as a WAR bundle in a J2EE container since WO 5.1, and as an independent SSDD (Servlet Single Directory Deployment) bundle since WO 5.2. There is some documentation out there already for preparing your apps for this if you use XCode, but more and more developers are ditching that in favor of the vastly superior (in my opinion) Eclipse/WOLips IDE. For more information on XCode, [[start here>>WO:Web Applications-Deployment-Tomcat Deployment]]. This article is all about how to do things in Eclipse, and then as a bonus, how to create a deploy a project wonder application so that it runs in tomcat. I had a fairly miserable time figuring all of this out, but there's really not that much too it once you have all of the information in one place. Read on...
4
5 == Prerequisites ==
6
7 You really ought to know how to create a regular standalone webobjects application before you dive into this article. It helps if you understand the basic servlet container file structure, and you should also have the container of your choice installed. I'm using Tomcat 5.5.9.
8
9 == Getting Started ==
10
11 For an example of how this works, we're going to do the most boring little hello world app imaginable from scratch. Don't fret though, you can apply the same set of changes to any existing app and it should work. After getting Hello World working, I applied the changes described here to a fairly complex existing project full of Ajax and project wonder fanciness, and it worked just as expected.
12
13 == Creating Hello World ==
14
15 First create your hello world just like you would [[In this tutorial>>WOL:Create a new WO Application]]. I called my project JSPHelloWorld, and all I did after the WOLips wizard finished guiding me through the project was to edit the Main.html component to say HELLO, WORLD! About as boring as it gets, but go ahead and run your application as a WOLips WOApplication run configuration (the beauty of all this, as you'll see, is that you can develop your applications as you normally would, and in the end, you only have to think about the tomcat stuff when you're actually ready to deploy):
16
17 [[image:Picture 1.png]]
18
19 == Now for the fun part... ==
20
21 There are really just a few steps you need to go through to make a plain WebObjects application deploy on Tomcat from here:
22
23 1) Open up the build.xml file for editing. It should be at the project level. If you don't see it, it's probably just hidden. To show it, click the down triangle at the top of the Package Explorer, and select the "Filters" menu:
24
25 [[image:Picture 2.png]]
26
27 Uncheck the item labeled build.xml (WOLips). Now you should see it in package explorer.
28
29 Note: If you are converting an existing project to deploy in a servlet container, you may have an older version of the WOLips build.xml file (this happened to me when I started playing with things, and it was all kinds of confusing). Lots of work has been done by the community in the last year to bring this up to speed. To get the recent good stuff, you can right click on the build.xml file and choose WOLips Ant Tools->Replace with latest build.xml. Take care to back up your original version somewhere first though, if you've made any modifications, because you will lose those changes.
30
31 If you are using the ant editor to edit the file, you will see a nice summary of your targets in the Outline window:
32
33 [[image:Picture 3.png]]
34
35 Click on the 'ssdd' target (If you are not in the and editor, you can just scroll down in the file until you see '<target name="ssdd" depends="build.woapp">').
36
37 The directions in the comments above the target are fairly self-explanatory, but here is the more verbose rundown:
38
39 By default, the target tag for ssdd has an attribute that looks like 'if="${{never}}{{/never}}"'. Just delete that, so that the target will execute next time you do an ant install.
40
41 A couple of targets up in the file, you will find the build.woapp target (<target name="build.woapp">). Within that tag, you will find several 'frameworks' tags. By default, these will have an embed="false" attribute. For each of them, change the "false" to "true". This is important because you are bundling a self contained thing to go in your servlet container, so everything you reference needs to be included. embed="true" makes the ant task copy all of the referenced frameworks out of /Library/Frameworks/ and /System/Library/Frameworks and into the folder that will ultimately move to your tomcat 'webapps' directory (by default, this folder will be created within your project, within a folder named 'dist'. More on this later)
42
43 At the root level of your project, you need to create a text file and name it LICENSE. Within this file, you are going to paste the contents of your WebObjects license key. For WebObjects 5.3, this can be found in the file at /System/Library/Frameworks/JavaWebObjects.framework/Resources/License.key. More on the whole license key thing [[here>>WOCOM:FAQ on WebObjects]]. Here's what my project directory looks like after I created the LICENSE file:
44
45 [[image:Picture 4.png]]
46
47 Now you need to modify your build path to include the framework that lets your webobjects application behave in the servlet world: JavaWOJSPServlet. It's in /System/Library/Frameworks. If you don't know how to modify your build path, [[go read this>>WOL:Add a Framework Dependency]].
48
49 Now open up your build.properties file, which also resides at the root level of your project. If you don't see it, it's probably hidden from view. Open up the Filters window in package explorer (just like you did above to see the build.xml file), and uncheck build.properties (WOLips). Double click the build.properties file to edit it. There will be a line that looks like "webXML = ". Make that line read 'webXML = true' and then save the file.
50
51 == Getting there.... ==
52
53 That's the long and short of it if you don't want to include wonder in your app. All you have to do is do an ant install (right click on your project folder and choose WOLips Ant Tools->Install. A bunch of stuff will be compiled into the proper exploded WAR directory structure in a folder called 'dist' in your project. If you don't see it, it could be hidden. Open up the Filters window in package explorer again and make sure dist (WOLips) is unchecked. If you still don't see it, it could just be that package explorer hasn't picked up on it yet. Right click your project and choose 'Refresh' and it should appear.
54
55 Let's have a quick look at what's in 'dist':
56
57 [[image:Picture 5.png]]
58
59 There are two top-level folders. The first is the one you will copy into the webapps directory of your tomcat installation. Inside it is the WEB-INF folder containing an auto-generated web.xml file (more on this later), the bundled frameworks (because we set embed="true" in our build.xml), a tld file that you don't really need to know too much about, a lib directory that contains, as far as I can tell, every jar resource that you have installed in /Library/WebObjects/Extensions (does anyone know how to prevent the unnecessary stuff from being copied in?), and your .woa folder, which is an exact copy of the second folder under dist, JSPHelloWorld.woa, in this case.
60
61 If this were not a project wonder project (that is, if it's Application and Session classes did not extend ERXApplication and ERXSession), we could copy over the JSPHelloWorld folder from dist into tomcat's webapps folder, restart tomcat, pull up a browser, type in http:~/~/localhost:8080/JSPHelloWorld/WebObjects/JSPHelloWorld.woa, and see the same boring hello world page we saw when we ran the application as a standalone WO app. If you really want to verify this, just make your Application and Session classes extend WOApplication and WOSession instead, rebuild the thing, deploy it into tomcat as described above, and have a go.
62
63 == Now for Project Wonder ==
64
65 But SINCE we're trying to incorporate project wonder (and what sensible WebObjects developer tries to live life without project wonder?) we instead get this awesomely helpful message back:
66
67 Servlet WOServletAdaptor is currently unavailable
68
69 Man, that tells you a lot. Checking the logs (tomcat-root/logs/catalina.out) tells you little more that makes any sense. But I've done the desparate scrounging on Google, so I'll spare you the expense: it's because Project Wonder does a lot of early intialization and patching of terrible WebObjects code, and the process of patching is broken when you run things in a servlet container. When you're running them as a standalone app, you have a static main method in your Application class that calls ERXApplication.main(), which in turn does all that cool pain saving patching. There's a pretty good thread on the whole business [[here>>http://thread.gmane.org/gmane.comp.web.webobjects.wonder-disc/5834/focus=5854]]. But the main method does not get called in a servlet container because things just operate differently in this world. So we have to do a few extra things:
70
71 == Monkeying with the setup ==
72
73 Before I get started with what worked for me, I want to give a hat tip to Henrique Prange, who came up with a bunch of code that I cobbled together and modified only slightly to get things working. Without his guidance, I'd still be scratching my head pretty and staring at my computer screen with bloodshot eyes wondering what is going on.
74
75 The gist of what we're about to do is that we're going to be writing a new ServletAdaptor, which is a subclass of WOServletAdaptor, which is itself a subclass of javax.servlet.http.HttpServlet. This is just a special kind of class that sits around and listens for HTTP requests, and hands them off to the right place when they come in (fairly similar to the WO HTTP adaptor). If you look at the documentation for WOServletAdaptor, it says it is not intended to be subclassed, and I think what they mean by that is that all of its methods except the constructor and one other special method are static. But we're going to subclass it anyway, and override the one non-static method (named init()) to setup the project wonder initialization stuff that usually happens when we call ERXApplication.main().
76
77 Henrique created a class called ERXServletAdaptor and it is submitted as a pending project wonder patch as we speak (or as I type, as the case may be). I could not get the code in that patch to run as is, so I have changed it just a bit. Here is my version of the class that works for me. Something very similar to this may soon be bundled with project wonder, but for now, you can just toss this class into your project's Sources folder:
78
79 {{noformat}}
80
81 package er.extensions.jspservlet;
82
83 import java.io.InputStream;
84 import java.lang.reflect.Method;
85 import java.util.HashMap;
86
87 import javax.servlet.ServletContext;
88 import javax.servlet.ServletException;
89 import javax.servlet.UnavailableException;
90 import javax.servlet.http.HttpServlet;
91 import javax.servlet.http.HttpServletRequest;
92 import javax.servlet.http.HttpServletResponse;
93 import javax.servlet.jsp.PageContext;
94
95 import org.apache.log4j.Logger;
96
97 import com.webobjects.jspservlet.WOServletAdaptor;
98
99 import er.extensions.ERXApplication;
100
101 /**
102 * This class is just a wrapper around <code>WOServletAdaptor</code>.
103 * <code>ERXServletAdaptor</code> must be used to make Wonder applications
104 * compliant with WAR deployment.
105 * <p>
106 * This class is responsible to invoke the
107 * {@link ERXApplication#setup(String[])} method before the application
108 * initialization.
109 *
110 * @see WOServletAdaptor
111 * @author <a href="mailto:hprange@moleque.com.br">Henrique Prange</a>
112 */
113 public class ERXServletAdaptor extends WOServletAdaptor {
114
115 /**
116 * Overrides the <code>_appliationInit</code> and invoke the
117 * {@link ERXApplication#setup(String[])} method before the application
118 * initialization.
119 *
120 * @see WOServletAdaptor#_applicationInit(ServletContext)
121 */
122
123 /*
124 *
125 * @param servletContext
126 * The servlet context to get the application class
127 * @throws UnavailableException
128 * If something wrong happens while trying to invoke the
129 * application setup method.
130 */
131 static void invokeApplicationSetupMethod(ServletContext servletContext) throws UnavailableException {
132 ClassLoader classLoader = WOServletAdaptor.class.getClassLoader();
133
134 try {
135 String applicationClassName = servletContext.getInitParameter("WOApplicationClass");
136
137 if (applicationClassName == null || "".equals(applicationClassName)) {
138 throw new UnavailableException("WOApplicationClass must be defined. Verify your web.xml configuration.");
139 }
140
141 Class applicationClass = classLoader.loadClass(applicationClassName);
142
143 Method method = applicationClass.getMethod("setup", new Class[] { String[].class });
144
145 method.invoke(null, new Object[] { new String[0] });
146 }
147 catch (Exception e) {
148 e.printStackTrace();
149
150 throw new UnavailableException("Error initializing ERXServletAdaptor: " + e.getMessage());
151 }
152 }
153
154 public ERXServletAdaptor() throws ServletException {
155 super();
156 }
157
158 @Override
159 public void init() throws ServletException {
160 ERXServletAdaptor.invokeApplicationSetupMethod(getServletContext());
161 super.init();
162 }
163
164 }
165
166 {{/noformat}}
167
168
169 When you plop this into your application, it is not going to build. The reason is that it references classes that are bundled in jar files that aren't in your build path yet. The two files you need to add to your build path are
170
171 /System/Library/Frameworks/JavaWOJSPServlet.framework/Versions/A/WebServerResources/Java/JavaWOJSPServlet//client.jar//
172
173 and
174
175 /System/Library/Frameworks/JavaWOJSPServlet.framework/Versions/A/Resources/Java/javawojspservlet.jar
176
177 I'd like to have a quiet, respectful word with whoever at Apple decided to put the first one in WebServerResources. Very confusing.
178
179 Now your project should build.
180
181 == One last thing ==
182
183 That is pretty much the trick, but there is one last catch. Somehow, tomcat has to know to use your version of the ServletAdaptor, and not the WebObjects version that breaks with wonder. The container gets this information from the web.xml file that we saw magically generated for us in dist/JSPHelloWorld/WEB-INF after we ran our ant task. If you go into the dist foldr and open up this file, you will see the following near the bottom:
184
185 {{noformat}}
186
187 <!-- The WebObjects Servlet that interfaces between the Servlet container
188 world and the WebObjects world. -->
189 <servlet>
190 <servlet-name>WOServletAdaptor</servlet-name>
191 <servlet-class>com.webobjects.jspservlet.WOServletAdaptor</servlet-class>
192 <load-on-startup>5</load-on-startup>
193 </servlet>
194
195 {{/noformat}}
196
197
198 For our little trick to work, we need to change the class to reference our class, so we change it to look like this:
199
200 {{noformat}}
201
202 <!-- The WebObjects Servlet that interfaces between the Servlet container
203 world and the WebObjects world. -->
204 <servlet>
205 <servlet-name>WOServletAdaptor</servlet-name>
206 <servlet-class>er.extensions.jspservlet.ERXServletAdaptor</servlet-class>
207 <load-on-startup>5</load-on-startup>
208 </servlet>
209
210 {{/noformat}}
211
212
213
214 We can just edit the file by hand and save it, but next time we do an install, we're going to blow that change away and then have to remember all this minutia again to figure out what's going on. So I just modified the build.xml file to make the change for us whenever we do an install. Place this at the bottom of the build.woapp target, and next time you do a build, you'll be in business:
215
216 {{noformat}}
217
218 <!-- fix the web.xml file to use a custom Servlet Adaptor that allows for Project Wonder initialization -->
219
220 <replaceregexp file="${dest.dir}/${project.name}/WEB-INF/web.xml" match="com.webobjects.jspservlet.WOServletAdaptor"
221 replace="er.extensions.jspservlet.ERXServletAdaptor" byline="true" />
222
223 {{/noformat}}
224
225
226
227 == Done and done ==
228
229 That's it! Now, just do an ant install, copy the JSPHelloWorld folder into the webapps folder of your tomcat (or whatever) installation, restart tomcat, and load it up in your browser!
230
231 [[image:Picture 6.png]]
232
233 Now go make billions of dollars deploying WebObjects apps on Tomcat. I'm going to bed.