text:
WebObjects/EOF/Using EOF/Undo and Redo

Overview
Can anyone tell me if the NSUndoManager is only used with EOF or can I use it in my own application (not using EOF) ?

Undo/Redo can be used with any kind of app (that's why you find NSUndoManager in Foundation and not some higher policy level framework like WOF or EOF). Of course WOF and EOF come preconfigured to use an NSUndoManager WOF automatically creates Undo Groups around the request/response loop (although you can create subgroups to get finer grain control if you like), and EOF's EOEditingContext comes prewired to use an NSUndoManager.

I don't know why people don't use this more... I guess it's inertia. If you only ever use one EOEditingContext in your app and the user clicks Undo, they may get confused about what happens unless you are careful to warn users that they are making an Undo request in a page that isn't active (they backtracked). If users backtrack to an old page and push undo there, the change that gets undone may have happened more recently. To the user nothing would appear to change when in fact something has changed, but they just don't know what. Another reason is that often EC changes are accompanied with UI state changes and undo only applies to changes in the EC. For example, you might want to undo shipping settings on order. Just calling undo would do that, but it wouldn't shift state representing your step in the check out process.

Another issue is that users and developers aren't aware that undoing some things may not be reversible without additional intervention... Like a update to a counter stored in the database. Or they may be reversible, but the user or developer doesn't realize another call to saveChanges is needed to push the undone object graph in the EC back to the DB.

Instead of using Undo, people sometimes use task-based editing contexts combined with revert and/or EC replacement... that is, you dedicate an EC to the checkout process, and another EC to data browsed in a Store, and another EC for editing account info. If a user doesn't wish to commit changes, you can call revert on the EC discarding all changes since the last commit. However, once you've broken the EC into tasks like this, it may be more clear to you and your users about what happens with Undo/Redo. For example, an Undo in Customer Edit, won't undo your last change in to your Product Management edit EC.

One desirable way to undo/redo in a web app would involve using the NSUndoManager notifications messages (that get called as undo groups are formed). This may allow the Object Graph changes (state that the EOEditingContext & NSUndoManager manage) to be kept in sync with your app specific session state (that you directly manage). It also might give you the leverage to associate the Page where a given change was undone. For example... imagine a user edits some Customer info. The user moves on to some other customer or some other type of information and edits there two. The user hits a few times. Now suppose any action that calls undo, correlates the Undo group with state stored about that group at the time the undo group was formed? You might be able to recover info about the PK and entity name associated with the page that made the change; this in turn would allow you to take the user back to an appropriate editor page pre-populated with the EOEnterpriseObject object(s) that were chiefly affected by the undo. I've never tried doing something like this, but it might be possible.

None of this would be actually that interesting or needed if a user typically views in just a few areas all the data that could be changed. Undo/Redo impact would be immediately evident. If there are uncommitted changes in an EC, you might want to still flag the user about that so they would know to try to save their changes if needed.

If users can't easily see the impact of undos and redos, another approach you might use would involve summarizing the current content of EOs in the EC's updated, inserted, and deleted lists. This might be a little region at the bottom of the screen that listed counts of these arrays or that listed the Entity names of uncommitted EOs along with the their count. You could even use the changes-from-snapshot API to show the actual changes themselves in any uncommitted updates. When a user does an undo, you might take the user to a page that explodes this change information in all its glory. The user could toggle undo/redo back and forth on such a page and get a clear indication of the scope of the undo in question. If things looked OK, then they could call saveChanges on the EC.

PS: some important docs I want to bring to light in case you Don't use undo/redo much...

More Info
If you are using Undo/Redo, it is important to read the Memory Management section.

title: WebObjects/EOF/Using EOF/Undo and Redo


text:
WebObjects/Web Applications/Development/Examples/Open Link in New Window

Opening a link in a new window using JavaScript. There's actually a surprisingly easy way to do it. Just use a WOHyperlink, with an onclick binding.

Hyperlink3: WOHyperlink { action = myCustomAction; onclick = "return openInNewWindow(this)"; target = "_new"; }

 function openInNewWindow(senderLink) { url = senderLink.href; newWindow = window.open(url,"NewWindow","width=650,height=600,resizable=yes,"+               "scrollbars=yes,location=no,toolbar=no"); newWindow.focus; return false; }

The interesting this is, the hyperlink is sending itself to the openInNewWindow? function, which then gets the URL from the href property of the link, and opens it in a new window. You can use a regular old action method and bind it to this hyperlink. Also, it will work in browsers that don't have javascript, because the function will not return false, and the link will act like a regular hyperlink whose target is "_new". When the javascript returns false, the link is not followed.

To do something a bit more complex, suppose you are storing image dimensions in the database, and want the newly opened window to be exactly the right size. You will need to programatically create the "onclick" binding string for the WOHyperlink, which will pass the dimensions to the openInNewWindow? javascript function.

Hyperlink3: WOHyperlink { action = myCustomAction; onclick = myOnclickString; target = "_new"; }

and in your component class, add the following method:

public String myOnclickString { EOEnterpriseObject imageBeingOpened; // assume this exists Integer width = imageBeingOpened.width; Integer height = imageBeingOpened.height; return "return openInNewWindow(this, " + width + ", " + height + ");"; }

And finally, change your javascript method to accept the width & height parameters:

 function openInNewWindow(senderLink, width, height) { url = senderLink.href; newWindow = window.open(url,"NewWindow","width="+width+",height="+height+",resizable=yes,"+               "scrollbars=yes,location=no,toolbar=no"); newWindow.focus; return false; }

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/


text:
Parkour/Basic Moves/King Kong Vault

A King Kong vault (or sometimes Kong vault or just Kong) is a common technique in parkour. The King Kong vault is executed when a traceur takes a leap 3 to 2 feet in front of the object, put their legs in between their arms, and let go. It is like a monkey vault except that take huge leap and get more momentum.

title: Parkour/Basic Moves/King Kong Vault


text:
WebObjects/Web Applications/Development/Profiling WO Apps

A profiler is a development tool that lets you examine memory usage and other bottlenecks in your app.

Ulrich Köster
I've tried some of them.

For the big problems jprofiler is great. The docs are useful, the interface is good and the performance is okay. I prefer jMechanic. It's small, free and works together with WOLips. Just some clicks to set it up.

I don't remember much about the others. Some of them don't run under MacOSX and it can take a lot of time to find how to use them together with WO.

profiler homepage: http://www.ej-technologies.com/products/jprofiler/overview.html j jmechanic homepage: http://sourceforge.net/projects/jmechanic/ to see jmechanic in action click on the profiler link: http://objectstyle.org/woproject/gallery.html Ulrich

Chuck Hill
If profiling on Windows is an option, you might also want to look at jSprint: http://www.javaperformancetuning.com/tools/jsprint/index.shtml

It is shareware, US$50. I've used it and was satisfied with it. It seems like a good deal for the price. I've not (yet) used jMechanic, so I can't compare the two.

Before you buy anything or try anything you would be well advised to spend a couple of hours at the above Java Performance Tuning site reading up on it: http://www.javaperformancetuning.com/index.shtml There is a lot of good information there.

David Hrivnak
(As of March 2007 the J-Sprint link above does not seem to work - the software may not be available anymore) None of the tools on this page appear to work with WebObjects 4.5.1 on Windows.

Mike Schrag
I use JProfiler from ej-technologies. It works as well as any of the other big named profilers, and it has explicit support for WebObjects. It is, however, not free like some of the other suggestions.

Pierce T. Wetter III
The CHUD tools (i.e. Shark) work on Java.


text:
WebObjects/Web Applications/Deployment/Standard Deployment

Overview
I begin by assuming that you have a compiled and built woa, say MyApp.woa.

Copy the entire woa to the application server(s), into a convenient location (/Library/WebObjects/Applications for example). When you copy the woa between different OS's, make sure that line-breaks are transferred correctly.

Verify that each application runs, by running it from the command-line (MyApp.woa/MyApp on Solaris and MacOSXServer, MyApp.woa/MyApp.cmd on Windows 2K). Check on each application server.

Now, find the WebServerResources directory inside the woa (MyApp.woa/Contents/WebServerResources). Copy this into the webserver's document root, under the WebObjects directory, _with the same path_. For example, given the above on OS X, I would copy the WebServerResources so that the final path was /Library/WebServer/Documents/WebObjects/MyApp.woa/Contents/WebServerResources).

wotaskd
Verify that wotaskd is running on each application server, by going to the URL http://appserverhost:1085 ; and checking that the output begins with "Wotaskd for WebObjects 5: ". If it is not running, try running it by hand

/System/Library/WebObjects/JavaApplications/wotaskd.woa/wotaskd

Java Monitor
Start Monitor

/System/Library/WebObjects/JavaApplications/JavaMonitor.woa/JavaMonitor

on one of the application servers. Note that Monitor _must_ be run on one of the application servers!

In Monitor (http://appserverhost:56789), go to the Site tab, and enter the HTTP Adaptor URL, based on your webserver hostname, the webserver itself, and the adaptor type. (See the earlier section on adaptors for example URLs). This URL doesn't affect configuration of the site at all - it is only used to generate URLs in Monitor itself, but we're filling it in for convenience later.

Go to the Hosts tab, and add each of the application servers (you may enter IP addresses or hostnames). If you add localhost or 127.0.0.1, you must not add any more application servers (i.e. if localhost is a host, it must be the only host). Choose the correct type for each host.

Go to the Applications tab, and add your application by name. You will be taken to the application configuration page. The only mandatory setting here is the Path - if you are deploying on multiple OSes, you may have to enter the path multiple times. The path should be the complete path to the application startup script (/Library/WebObjects/Application/MyApp.woa/MyApp or C:\Apple\Local\Library\WebObjects\Application\MyApp.woa\MyApp.cmd). At the bottom of the first section (New Instance Defaults), click the button "Update for New Instance Only". Click the button in the top right of the page that says "Detail View".

From the Detail View page, click the "Add" button to add instances of your application to various hosts (remember that an instance is a running copy of an application). Each instance should start automatically in about 30 seconds.

Once each application has started, check that they are running using Direct Connect - in the "Host-Port" column should be a hyperlink for each instance - click each link in turn and check that the instances are running properly.

Now to set up the adaptor. This is different for each platform and each webserver. First, note that the default method for all the adaptors is to get the site configuration is Hostlist. Specifically, the list of hosts is actually only "localhost" - so only applications that are on the same machine as the webserver will get vended. If you have separate application server(s), the adaptors will have to be reconfigured - adding the hosts to Monitor isn't good enough. For details on adaptor installation and options, see the Installation.html pages in the adaptor source for each type of adaptor (/Developer/Examples/WebObjects/Source/Adaptors/ /Installation.html).

Really Quick Adaptor Setup for Apache on OSX and Solaris
Locate the apache.conf file, located in /System/Library/WebObjects/Adaptors/Apache/ or $NEXT_ROOT/Library/WebObjects/Adaptors/Apache. Look for the line "WebObjectsConfig http://localhost:1085 10". If you had the site configuration from the Overview, you would change the line to say "WebObjectsConfig http://appserver1:1085,http://appserver2:1085 10".

Really Quick Adaptor Setup for IIS on Win2K
Run regedit.exe. Locate "HKEY_LOCAL_MACHINE/SOFTWARE/Apple/WebObjects/Configuration/". Choose "Add Value" from the edit menu. Type in "CONF_URL"?; for the name and choose REG_SZ for the Data Type. Enter the URL you want to use for the value. The default URL is, as above, "http://localhost:1085";, but (for our example) should be entered as "http://appserver1:1085,http://appserver2:1085";. Note that we do not enter the trailing " 10" here, since it is a separate key/value (CONF_INTERVAL).

Save changes and restart the websever.

At this point, you should be able to connect to your application, by connecting to the appropriate URL

http://webserverhost/cgi-bin/WebObjects/MyApp.woa

or perhaps http://webserverhost/scripts/WebObjects.dll/MyApp.woa


text:
WebObjects/Web Applications/Development/Examples/Return a File

To return a file from a hyperlink, the easiest way seems to be over-riding appendToResponse in a WOComponent to return that file as a response, rather than letting the WOComponent generate its own response from .html/.wod files:

public void appendToResponse(WOResponse aResponse, WOContext aContext) { //Use whatever mime-type you need for content-type: //"text/csv" is just an example. aResponse.setHeader("text/csv", "content-type"); //Assuming 'data' is a Java byte[] array. But //You can do whatever you want to wind up with //an NSData of content. aResponse.setContent( new NSData( data ) ); aResponse.setHeader("filename=MyFilename.xls", "Content-Disposition"); //or, if you want the link to "force" a SaveAs dialog... //aResponse.setHeader("attachment;filename=MyFilename.xls", "Content-Disposition");

Someone else reports: We have the following code for downloading a generated pdf...

public void appendToResponse(WOResponse aResponse, WOContext aContext) { super.appendToResponse(aResponse, aContext); //aResponse.setHTTPVersion("HTTP/1.1"); aResponse.disableClientCaching; aResponse.removeHeadersForKey("Cache-Control"); aResponse.removeHeadersForKey("pragma"); aResponse.setHeader("application/pdf", "content-type"); aResponse.setHeader("inline; attachment; filename=\"" + fileName + ".pdf\"", "content-disposition"); aResponse.setHeader(Integer.toString(resultData.length), "content-length"); aResponse.setContent(resultData); }

The lines of interest for fixing the IE problem (for us at least) were...

aResponse.disableClientCaching; aResponse.removeHeadersForKey("Cache-Control"); aResponse.removeHeadersForKey("pragma");

You might want to try:

public void appendToResponse( WOResponse r, WOContext c ) { fileName = "test.txt"; r.setHeader( contentType + "; name=\"" + fileName + "\"", "Content-Type" ); r.setHeader( "inline; filename=\"" + fileName + "\"", "Content-Disposition"); r.setContent( data ); }

Although according to RFC2183, your code should work fine, I've found that certain browsers from a monopoly that shall nameless looks only to the old RFC1341 location for the filename, which is in the Content-Type header, not in (or at least just in) the Content-Disposition header.

If you're primarily interested in having them download the file, you might want to make it:

public void appendToResponse( WOResponse r, WOContext c ) { fileName = "test.txt"; r.setHeader( contentType + "; name=\"" + fileName + "\"", "Content-Type" ); r.setHeader( "attachment; filename=\"" + fileName + "\"", "Content-Disposition"); r.setContent( data ); }

And another back-and-forth from the mailing list....

I got a problem with display PDF file [....] I can save file successfully by click [save] on Download Message Alert But when I click [open], Acrobat run and display the error message of "There is no such file", failed to open the PDF file.

From: "MacMullin, Jake (DCS)" ; To: "'Lu Yanmei'" , webobjects-dev@omnigroup.com Subject: RE: could not open PDF file Date: Fri, 30 May 2003 09:06:57 +0930

I had the exact same problem. Turned out it has to do with the headers you use. I've found this combination works best:

// set the PDF content and header response.setContent(outData); response.appendHeader("application/pdf", "Content-Type"); response.appendHeader(outData.length+"", "Content-Length"); response.appendHeader("inline;filename=\"file.pdf\"", "Content-Disposition");

And another back-and-forth from the mailing list....

''I got a problem to display a PDF file in a browser [....] I can send simple text files to open in a browser but pdf shows up as garbage. I cant depend on the user to have a plugin, is there a way to show it in the browser? Either that or is there a way to convert pdf to html and show that in the browser.''

r.appendHeader("application/pdf", "Content-type"); r.setHeader(Integer.toString(myData.length), "content-length"); r.appendHeader("application;filename=\"dockconcepts.pdf\"", "Content-Disposition"); r.appendContentCharacter('\n'); r.appendContentData(new NSData(myData));

If you want to return a file in response to the submission of a form (and have it work in IE on Windows), you need to change the 'method' of the form from 'POST' to 'GET' by adding a 'method' binding to the form in WOBuilder.

''I would strongly recommend to use the setHeader method instead of the appendHeader method to make sure that an existing content-type will be overwritten by the file return component. If a content-type already exists, appendHeader will not change the content type value which could lead to unpredictable results. --Hschottm 14:24, 16 January 2007 (UTC)''

Download from subcomponents
If you're trying to do this from a sub-component you need to use setContentInputStream instead of setContent. The documentation for the former says "only the data in the InputStream is returned -- any data that was added or set (either as String or NSData) is ignored", while the latter can be overwritten or appended to later in the page's lifecycle. For example, I have a generic component that runs a report and provides it as a PDF to the user. I want to include the component as follows in my ListPeople page:

PrintLink : ReportDownloader { title = "Download these results"; fileName = "PersonList.pdf"; data = personList.displayedObjects; definition = "personlist"; }

In ReportDownloader.java, doReport handles the click on the "Download these results" link and builds the report.

public void doReport { // ...snip... // Run the report and put the resulting PDF into the byte array pendingResponse // ...snip... }

As described above, the overriden appendToResponse method is responsible for actually writing the report into the response.

public void appendToResponse(WOResponse aResponse, WOContext aContext) { super.appendToResponse(aResponse, aContext); if (pendingResponse != null) { //aResponse.setContent(pendingResponse); ByteArrayInputStream in = new ByteArrayInputStream(pendingResponse); aResponse.setHeader("application/pdf", "Content-type"); aResponse.setHeader("inline; filename=" + fileName, "Content-disposition"); aResponse.setContentStream(in, 512, (long)pendingResponse.length); }       }

Creating a ByteArrayInputStream and setting the response to use it guarantees that the PDF content is the only content returned in the response, regardless of what the parent page (the one that contains ReportDownloader) tries to render into the response.


text:
WebObjects/Direct-To/Web

Overview
There are two commonly referred to articles by Max Muller that were written several years ago. The first is located at Stepwise (or in the Wonder frameworks): http://www.stepwise.com/Articles/Technical/D2W/D2W1.html

There is a second part to the article only available in the Wonder frameworks:

Once you install them you'll find it at: Wonder/Common/Examples/Stepwise/D2WInspectExample/Components/BestKeptSecret2.wo

The most complete overview continues to be the Apple Documentation.

There is additional material in the two chapters in the Professional WebObjects 5.0 with Java book. This material is essential reading. There is a Case Study in the book that gives some hints at how powerful the rule structure can be (I think this may have been written by Max Muller as well). This case study is also a mini introduction to some of the Wonder frameworks (before they were open sourced). The application example in the Case Study chapter is also located in the Wonder frameworks (Wonder/Common/Examples/Wrox/NetstruxrCaseStudy/). You can get it working once you make sure that the EOModel is pointing to the right database. The important thing about the NetstruxrCaseStudy is that it uses D2W and gives examples of the types of rules you can add to your application. In particular, the concept of creating sub-tasks is covered.

Additional Comments on the referenced articles
The following notes were made as I was trying to learn how direct to web works. I hope they are useful. The whole process, including all of the trials and errors, looking in the wrong place for things, getting things to build correctly, and writing up this little summary (tasks which the reader will hopefully now be able to mostly avoid) took me about three hours. At the end, I feel like I have a fairly decent basic understanding of the beginnings of how to use direct to web (without knowing anything about what project wonder adds yet).

I would recommend beginning at http://www.stepwise.com/Articles/Technical/D2W/D2W1.html. This article is also in the D2WListExample project from project wonder (see below for how to get it from Eclipse) under Components/BestKeptSecret.wo/BestKeptSecret.html. The article is pretty good, but the first part was very confusing to me. Once I finally understood what was being talked about, I wrote a brief synopsis of the content, which makes more sense to me. That is shown below, but before you start reading, I highly recommend that you create an Eclipse workspace for holding project wonder projects and examples (so that you can easily reference the source). I just went ahead and got everything, as shown below:

First, I created a new workspace in Eclipse (File->SwitchWorkspace). I called it "CurrentWonder" since it would hold all the projects imported from CVS. The project referenced in the article (D2WListExample) already exists in Eclipse format under the Wonder repository, so you don't need to follow the link from the article to get the code (the linked code is in XCode format and rather old).

Checked out wonder in eclipse, right clicked in package explorer import cvs -> Projects from CVS host: wonder.cvs.sourceforge.net repository path: /cvsroot/wonder user: anonymous Use an existing module (I selected all of the ones listed below) common-> examples -> D2WComponentTour frameworks -> all ERDirectToWeb By default, I had the eclipse compiler set to use Java 1.4.2. I seemed to have a lot of compile errors, and switching the eclipse compiler and runtime to 1.5 really seemed to help (look under Preferences->Java). Also, several of the projects apparently did not get everything down correctly when I checked them out, because they were missing pieces that the compiler was complaining about. I did an update (right click on the project in Package Explorer -> Team -> Update) on the projects in question, and everything was better. Finally after all of that, I had to clean all of the projects to get the dependent projects to build correctly after the fixes to the ones that didn't get everything (Project->Clean->Clean all projects). Now I have a happy, cleanly building set of all the project wonder frameworks and examples. OK, now we're ready! If you like, read my synopsis below and then dive into the first "Best Kept Secret" article! After you've read it, open D2WListExample->Components->BestKeptSecret and read the next one.

In a component, you often want to display a list of EOs. You could create a table and display group, add headers, add a repetition, add functions to sort the columns, add edit buttons, etc., etc.. OR, you could use a reuseable component. Because there are so many configurable pieces to this particular component, using a standard component with bindings just isn't going to work. There are too many potential bindings. So Instead, you can use a special component called D2WList which takes the list of items you want to display ("list"), the name of the entity from your eomodel for the things in the list ("entityName"), and a tag that points to a ruleset about the display properties of this list, which you define in a different format elsewhere ("pageConfiguration"). If you look at MyD2WListPage2.wo in the D2WListExample project, you will see a webobjects tag named List1 of type D2WList (bindings shown below): List1: D2WList { pageConfiguration = "ListMovies2"; list = session.movies; entityName = "Movie"; }

The "ruleset defined elsewhere" that I mentioned earlier is found in the direct to web model file in the Resources folder of the project: user.d2wmodel. You can open this file in RuleModeler (which can be found at http://webobjects.mdimension.com/wonder/), and set up various properties such as the template to use to display the list, whether each thing in the list is editable, which properties to display, etc. The "left hand side key" column shown in RuleModeler is the thing that lets the D2WList component find the rules for the list it is showing. You can also just have a full page generated for you, rather than a subsection of a page that contains your list. This part of the article made better sense to me, so I'll refer any readers directly to it (everything from the section labeled "Dynamic page creation" down). Now that you have some context for what the article is helping you do, the first part hopefully will make more sense as well.

Quickstart Demo
Here is a quick way to get started with Wonder's Direct-to-Web extensions, using Xcode 2.3, WebObjects 5.3.1, and Mac OS 10.4.6 For this new component to be visible your application has to be in development mode. This involves setting a property in the property file: er.extensions.ERXApplication.developmentMode=TRUE
 * Download and install the Wonder frameworks
 * Follow the Project Wonder Quickstart with the following differences:
 * Add ERDirectToWeb.framework to your project
 * You will need to add an EOModel to your project, either one of your own, or the sample ones in JavaRealEstate.framework or JavaBusinessLogic.framework
 * Have DirectAction extend ERD2WDirectAction instead of ERXDirectAction. You'll need to import er.directtoweb.*;
 * Now try some of the direct actions that ERD2WDirectAction provides by appending a pageConfiguration name to an entity name. So ListCustomer will use Wonder's List page configuration with the Customer entity. Some other page configurations not shown in the docs are:
 * EditList: this will show a list view of the entity but all of the fields are editable for all of the rows
 * ListGrouping: for this you will need to add a rule, for example: (pageConfiguration = 'ListGroupListingAddress') => groupingKey = "city" -- the ListGrouping template does not appear to be grouping correctly with this configuration yet.
 * ListXML: this will return the list in some funky XML format. The format of course can be changed to meet your needs. There is also a CSV format available, but you'll need to add a rule similar to (pageConfiguration like 'ListCSV*') => subTask = 'csv' and then ListCSVCustomer will output in pipe-delimited format. You can also add a rule like (pageConfiguration like '*PrinterFriendly*') => subTask = 'printerFriendly' and then ListPrinterFriendlyCustomer will show a list in a printer friendly format.
 * ListCompact, PickCompact, etc.: the compact format excludes the page wrapper and navigation bars
 * To see some D2W debugging details, open the PageWrapper.wo template in your Xcode project (in the Components folder). Insert a custom component below the existing content (Dynamic->Custom...) with a class of ERD2WDebugFlags. Click OK and then save the file.

Go back to the browser and refresh the screen, and you should see 2 new links for Log4J and D2W Info. Click the link for D2W Info, and it will show some details of what settings are active for the given components. --My version of the sample Real Estate database has some bad data for Agents, so you may have trouble working with the Agent and AgentPhoto entities until you clean it up manually.

Related Links

 * RuleModeler, a Rule Editor replacement

title: WebObjects/Direct-To/Web


text:
WebObjects/Web Services/Web Service Consumer

SSL WebServices Problems
1. WOWebServiceClient class can't access to a secure HTTP Web service provider (WO 5.2.2):

Problem: The com.webobjects.webservices.client.WOWebServiceClient class just throws exception like this one when it tries to read the WSDL from a secure HTTP Web service provider:

java.lang.IllegalArgumentException: Unable to open url: https://localhost/cgi-bin/WebObjects/Test-Server.woa/ws/Test?wsdl at com.webobjects.webservices.client.WOWebServiceClient. initializeFromURL(WOWebServiceClient.java:72) at com.webobjects.webservices.client.WOWebServiceClient. (WOWebServiceClient.java:59)

This problem doesn't seem related to self signed certificate, since I've performed tests with trusted certificate as well.

Patrick Robinson
That's strange. ..

I had the same problem when I was trying to run both service and client on my development machine (which has only a self-signed cert), and specifying an HTTPS WSDL address. But after adding my cert to /Library/Java/Home/lib/security/cacerts:

cd /Library/Java/Home/lib/security sudo keytool -import -keystore cacerts -alias myalias -file mycert.pem

(The default password for the cacerts keystore is "changeit")

everything works fine.

I also had no trouble accessing either the WSDL or the service, via HTTPS, when the service was run on a system with a valid, trusted cert.

Francis Labrie
It's finally not a real bug, thanks to PatrickRobinson and to the JAD tool. This exception is typically thrown when the server certificate is not trusted, i.e. the certificate is self-signed and not stored in the Java trusted keystore, or the hostname is not the same on the server and in the certificate, etc.

The problem is that the initializeFromURL method of the com.webobjects.webservices.client.WOWebServiceClient class doesn't wrap and forward the catched exceptions, it only throws IllegalArgumentException with a simple " Unable to open url" or " Unable to create service from url" message, without any details.

To avoid such problem, just relax the Java security manager using code like one described here: How to Trust Any SSL Certificate.

to Apple Documentation
http://developer.apple.com/documentation/WebObjects/Web_Services/Web_Services/chapter_4_section_3.html


text:
WebObjects/Web Services/How to Trust Any SSL Certificate

Francis Labrie
Sometimes, it would be useful to relax the Java security manager to allow connection to secure HTTP server using self-signed certificate, especially during development phase. To avoid Java exception on HTTPS connection, it's possible to add self signed certificate to the Java trusted X509 certificate repository using Java keystore command line tool:

% cd /Library/Java/Home/lib/security % sudo keytool -import -keystore cacerts -alias anAlias -file aCertificate

(The default password for the "cacerts" keystore is "changeit")

But this can be painful, even more if the application must be tested with various servers using self-signed or bad defined certificate. And if a server is using a certificate with a hostname different from the one you use to test it, it will still fail.

It's also possible to change the TrustManager? and HostnameVerifier? in Java code, but the API did change from JDK 1.3 and 1.4. Unfortunately, the old deprecated "com.sun.net.ssl" is still available, making setting of a custom TrustManager? and HostnameVerifier? a bit difficult.

So I've created an utility class that allow relaxing of the SSL trust rules. Simply add it to a package, an application or a framework, and call:

 * SSLUtilities.trustAllHostnames to turn off the default hostname verification on HTTPS connection;
 * SSLUtilities.trustAllHttpsCertificates to turn off the default certificate validation on HTTPS connection.

import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; /**  * This class provide various static methods that relax X509 certificate and * hostname verification while using the SSL over the HTTP protocol. *  * @author    Francis Labrie */ public final class SSLUtilities { /**    * Hostname verifier for the Sun's deprecated API. *    * @deprecated see {@link #_hostnameVerifier}. */   private static com.sun.net.ssl.HostnameVerifier __hostnameVerifier; /**    * Thrust managers for the Sun's deprecated API. *    * @deprecated see {@link #_trustManagers}. */   private static com.sun.net.ssl.TrustManager[] __trustManagers; /**    * Hostname verifier. */   private static HostnameVerifier _hostnameVerifier; /**    * Thrust managers. */   private static TrustManager[] _trustManagers; /**    * Set the default Hostname Verifier to an instance of a fake class that * trust all hostnames. This method uses the old deprecated API from the *  package. *    * @deprecated see {@link #_trustAllHostnames}. */   private static void __trustAllHostnames { // Create a trust manager that does not validate certificate chains if(__hostnameVerifier == null) { __hostnameVerifier = new _FakeHostnameVerifier; } // if       // Install the all-trusting host name verifier com.sun.net.ssl.HttpsURLConnection. setDefaultHostnameVerifier(__hostnameVerifier); } // __trustAllHttpsCertificates /**    * Set the default X509 Trust Manager to an instance of a fake class that * trust all certificates, even the self-signed ones. This method uses the * old deprecated API from the  package. *    * @deprecated see {@link #_trustAllHttpsCertificates}. */   private static void __trustAllHttpsCertificates { com.sun.net.ssl.SSLContext context; // Create a trust manager that does not validate certificate chains if(__trustManagers == null) { __trustManagers = new com.sun.net.ssl.TrustManager[] {new _FakeX509TrustManager}; } // if       // Install the all-trusting trust manager try { context = com.sun.net.ssl.SSLContext.getInstance("SSL"); context.init(null, __trustManagers, new SecureRandom); } catch(GeneralSecurityException gse) { throw new IllegalStateException(gse.getMessage); } // catch com.sun.net.ssl.HttpsURLConnection. setDefaultSSLSocketFactory(context.getSocketFactory); } // __trustAllHttpsCertificates /**    * Return   if the protocol handler property   is set to the Sun's   deprecated one, * otherwise. *    * @return                  if the protocol handler * property is set to the Sun's deprecated one, * otherwise. */   private static boolean isDeprecatedSSLProtocol { return("com.sun.net.ssl.internal.www.protocol".equals(System. getProperty("java.protocol.handler.pkgs"))); } // isDeprecatedSSLProtocol /**    * Set the default Hostname Verifier to an instance of a fake class that * trust all hostnames. */   private static void _trustAllHostnames { // Create a trust manager that does not validate certificate chains if(_hostnameVerifier == null) { _hostnameVerifier = new FakeHostnameVerifier; } // if         // Install the all-trusting host name verifier: HttpsURLConnection.setDefaultHostnameVerifier(_hostnameVerifier); } // _trustAllHttpsCertificates /**    * Set the default X509 Trust Manager to an instance of a fake class that * trust all certificates, even the self-signed ones. */   private static void _trustAllHttpsCertificates { SSLContext context; // Create a trust manager that does not validate certificate chains if(_trustManagers == null) { _trustManagers = new TrustManager[] {new FakeX509TrustManager}; } // if       // Install the all-trusting trust manager: try { context = SSLContext.getInstance("SSL"); context.init(null, _trustManagers, new SecureRandom); } catch(GeneralSecurityException gse) { throw new IllegalStateException(gse.getMessage); } // catch HttpsURLConnection.setDefaultSSLSocketFactory(context.           getSocketFactory); } // _trustAllHttpsCertificates /**    * Set the default Hostname Verifier to an instance of a fake class that * trust all hostnames. */   public static void trustAllHostnames { // Is the deprecated protocol setted? if(isDeprecatedSSLProtocol) { __trustAllHostnames; } else { _trustAllHostnames; } // else } // trustAllHostnames /**    * Set the default X509 Trust Manager to an instance of a fake class that * trust all certificates, even the self-signed ones. */   public static void trustAllHttpsCertificates { // Is the deprecated protocol setted? if(isDeprecatedSSLProtocol) { __trustAllHttpsCertificates; } else { _trustAllHttpsCertificates; } // else } // trustAllHttpsCertificates /**    * This class implements a fake hostname verificator, trusting any host * name. This class uses the old deprecated API from the  package. *    * @author    Francis Labrie *    * @deprecated see {@link SSLUtilities.FakeHostnameVerifier}. */   public static class _FakeHostnameVerifier implements com.sun.net.ssl.HostnameVerifier { /**        * Always return , indicating that the host name is an          * acceptable match with the server's authentication scheme. *        * @param hostname        the host name. * @param session        the SSL session used on the connection to          * host. * @return               the   boolean value * indicating the host name is trusted. */       public boolean verify(String hostname, String session) { return(true); } // verify } // _FakeHostnameVerifier /**    * This class allow any X509 certificates to be used to authenticate the * remote side of a secure socket, including self-signed certificates. This * class uses the old deprecated API from the * package. *    * @author    Francis Labrie *    * @deprecated see {@link SSLUtilities.FakeX509TrustManager}. */   public static class _FakeX509TrustManager implements com.sun.net.ssl.X509TrustManager { /**        * Empty array of certificate authority certificates. */       private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {}; /**        * Always return , trusting for client SSL *  peer certificate chain. *        * @param chain           the peer certificate chain. * @return               the   boolean value * indicating the chain is trusted. */       public boolean isClientTrusted(X509Certificate[] chain) { return(true); } // checkClientTrusted /**        * Always return , trusting for server SSL *  peer certificate chain. *        * @param chain           the peer certificate chain. * @return               the   boolean value * indicating the chain is trusted. */       public boolean isServerTrusted(X509Certificate[] chain) { return(true); } // checkServerTrusted /**        * Return an empty array of certificate authority certificates which * are trusted for authenticating peers. *        * @return                a empty array of issuer certificates. */       public X509Certificate[] getAcceptedIssuers { return(_AcceptedIssuers); } // getAcceptedIssuers } // _FakeX509TrustManager /**    * This class implements a fake hostname verificator, trusting any host * name. *    * @author    Francis Labrie */   public static class FakeHostnameVerifier implements HostnameVerifier { /**        * Always return , indicating that the host name is          * an acceptable match with the server's authentication scheme. *        * @param hostname        the host name. * @param session        the SSL session used on the connection to          * host. * @return               the   boolean value * indicating the host name is trusted. */       public boolean verify(String hostname,             javax.net.ssl.SSLSession session) { return(true); } // verify } // FakeHostnameVerifier /**    * This class allow any X509 certificates to be used to authenticate the * remote side of a secure socket, including self-signed certificates. *    * @author    Francis Labrie */   public static class FakeX509TrustManager implements X509TrustManager { /**        * Empty array of certificate authority certificates. */       private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {}; /**        * Always trust for client SSL   peer certificate * chain with any  authentication types. *        * @param chain           the peer certificate chain. * @param authType       the authentication type based on the client * certificate. */       public void checkClientTrusted(X509Certificate[] chain,             String authType) { } // checkClientTrusted /**        * Always trust for server SSL   peer certificate * chain with any  exchange algorithm types. *        * @param chain           the peer certificate chain. * @param authType       the key exchange algorithm used. */       public void checkServerTrusted(X509Certificate[] chain,             String authType) { } // checkServerTrusted /**        * Return an empty array of certificate authority certificates which * are trusted for authenticating peers. *        * @return                a empty array of issuer certificates. */       public X509Certificate[] getAcceptedIssuers { return(_AcceptedIssuers); } // getAcceptedIssuers } // FakeX509TrustManager } // SSLUtilities


text:
WebObjects/Web Applications/Development/Backtracking

Backtracking
When Component Actions are used with WebObjects, to be able to preserve state across requests, the server must maintain a cache of previously visited pages. This page cache has a limited size. As a result, if the user presses the back button too many times to the point of exhausting this cache, it will result in a "User has backtracked too far" error.

Prevention with Direct Actions
Because direct actions don't require page state like component actions, the best way to avoid backtracking problems is to use Programming:WebObjects/Web Applications/Development/Direct Actions in your app.

Old Sessions and Backtracking
What is the simplest way to prevent a user from backtracking (using the browser back button) to the pages previously used by another user who has logged out?

To keep users from accessing someone else's session when they use the back buttion, you should call the session.terminate in your logout action. In order for this to work, you have to issue a redirect, after calling terminate, to page with no session, because if you return another page using pageWithName, that page will reference the session you just terminated, and it won't work. My logout action looks something like this:

public WOComponent logout { WORedirect redirect = (WORedirect)pageWithName("WORedirect";); redirect.setUrl("/cgi-bin/WebObjects/MyApp";); // entry point session.terminate; return redirect; }

This will prevent the session from working if someone uses the back button, but it doesn't prevent them from seeing the content on the pages. If sensitive content is the issue, the best thing to do is to close the window when the user logs out. This would assume that when they logged in, you opened a window for the sensitive part of the app to run in. That way, when you use javascript to close it, you won't trigger a warning message that the user can intercept. With a window that you opened, you can do the redirect like above, to an html page that looks like:

  Closing Window...

Then there won't be any pages to go back to.


text:
WebObjects/Web Applications/Development/Persistent Sessions

Related question: Does anyone have a good example for how to save an expired session to database, and recreate it after it expires?

Nope, but perhaps take you could look at /Developer/Examples/JavaWebObjects/PersistentSessionStore.

From the readme file: The PersistentSessionStore application demonstrates one possible alternative session store object -- WXPersistentSessionStore. In WebObjects, the session store is the object that handles the storage of your session state between requests. The default session store is the ServerSessionStore, but that only stores the session while the app is running and cannot deal with interruptions in service and consumes virtual memory. The persistent session store solves these problems by putting the session on disk as a backup in the event the app dies or the in-memory session times out.


text:
WebObjects/Web Applications/Deployment/Windows

Overview
(rev 1.3; 2002-08-29, see full revision list) The latest version of this document can be found at http://www.tetlabors.de/wo/setup_webobjects_on_windows.html.

Legal stuff:
You can do with this how-to whatever you want but do it at your own risk. I WILL TAKE NO RESPONSIBILITIES WHATSOEVER. If you are not an expert in these things, I suggest setting up a new system just for test purposes first and then use your newly gained knowledge on a real world system.

Preface
This how-to discusses installation of WebObjects 5.1 on Windows. It started as a email-help to install WebObjects 5.x on WinNT and has evolved since. It covers most gotchas for installation and configuration of both Development and Deployment of WebObjects 5.1 on the following versions of Windows: The main course of this how-to is directed to a development installation on Win2000 Server. Differences to other configurations will be described in short throughout the text. This still is much harder than Linux/WO Deployment, where you have exactly one configuration.
 * Windows NT, both Workstation and Server
 * Windows 2000, both Professional or Server
 * Windows XP Professional

This how-to is not about deployment issues that occur after WebObjects is installed. It ends with a start of Monitor. There are, however, a lot of issues that can be solved be installing WebObjects in one of the right ways :-), so you may read this how-to anyhow.

If you have found an error or want to make suggestions, improvements etc, you can contact me here.

Before you start
To minimze delays, have the following things at hand:
 * a Java2-SDK from Sun (http://java.sun.com/j2se/1.4/download.html)
 * WebObjects 5.1 CD (get it at http://store.apple.com/)
 * a valid serial number (from either WO 5.0 or WO 5.1)
 * the latest update for WebObjects 5.1; the complete patch list lives at http://docs.info.apple.com/article.html?artnum=70037
 * some patches to complete the how-to (download them here)
 * WebObjects 4.x CD (only needed if you have installed WebObjects prior to 5.0)

If you plan to install on a new, empty machine, you additionally need:
 * Windows CD (either WinNT, Win2000 or WinXP)
 * Driver CDs or updated drivers from the internet (if you have to use special hardware)
 * Windows NT Option Pack CD (if you plan to install IIS on WinNT)
 * Windows 2000/XP CD (if you want to install the recovery console)
 * the latest service pack/rollup package and hotfixes for your version of Windows (too numerous to count, start search at http://www.microsoft.com/)

Throughout this how-to you will see the term 'open a shell' several times. This means you should go to the 'Start' menu, click on 'Run...', enter cmd and press the Return key. A new, empty window with a command prompt should open. Of course, if there is already an open shell, just use that instead...

Setup Windows and the web server
This how-to follows the installation of Windows at the example of Windows 2000 server but should be usable for all versions of Windows mentioned above. If something is really different, there will be some short notes in the text. Installation is mostly self-explanatory, I will only mention the steps that require some caution.

If you plan to do real world deployment on Windows, forget about the non Server variants of Windows. Because of Windows license restrictions, the Workstation/Professional versions allow only 10 concurrent connections to the machine - files opened over network shares count as connections, too. Setting up a deployment will of course work but performance will be very bad, you should consider buying a Windows Server version or use Linux/Apache as the web server and deploy only applications on Windows.

First comes what I call the 'Basic Setup': insert Windows CD and boot. If you have boot diskettes, a network folder or whatever as source for the installation files, use that. On some OEM versions or with unattended setup, only some or no input will be required - just skip the appropriate sections then.

If setup asks for the role of the server, select 'Single-Server'. Don't select one of the 'Domain Controller' roles, unless you know what you are doing. During network setup, you can choose to install Internet Information Server (IIS). Don't do this, because it is an outdated version. Be sure to _uncheck_ that checkbox! Don't use DHCP! See 'Network settings' below for an explanation.

Install only World Wide Web Server, Internet Information Services Snap-In and all required files. Don't install FTP, NNTP, SMTP unless you really need it. If not configured correctly, these services provide execellent hooks for an attacker.

If you have more than operating system installed, disable daylight saving for all but the first one or you will have much fun twice a year...

Choose custom network settings and enter IP/address, subnet mask, gateway and DNS server settings. Using DHCP on servers is not recommended because some services rely on a permanent IP address. If you have to use DHCP (it's a cool service, after all), configure the DHCP server so that your WebObjects machines always get the same address (address reservation, IP-to-MAC address mapping).

To be able to view other computers around, the WORKGROUP name entered must match the workgroup name of the other systems. If you do a domain based setup, there are no problems, just add the machine to your domain.

Win2000/XP has the embarrassing habit of releasing a network adaptor's configuration when it has no connection to the network. So always make sure the network cable is connected to an active station (other machine or hub/switch) before starting any services, especially while booting. If this is not the case, some services will run on localhost (127.0.0.1) and you will experience weird errors when you try to connect.

When you first log in to Windows, you will see the 'Configure Your Server' dialog. Unless you know what you are doing I suggest you choose 'I will configure this server later.' and click 'Next'. On the next page, uncheck 'Show this screen at startup' and close the dialog window. If you want, you can come back later via 'Start, Programs, Administrative Tools, Configure Your Server'.

After basic Windows Setup
If you have to install additional drivers (e.g. graphics card) do so now. Otherwise, you are done with basic Windows setup. Here are some things you might want to do now:

Single-CPU support
If you have a multiprocessor system, you should be able to start Windows with only one processor enabled (Single CPU HAL), This can be incredibly helpful if you run into hardware issues later and have to do an emergency boot on a motherboard that supports only one CPU. Also, some applications don't run on multiprocessor systems. To get single CPU support, add a new entry to c:\boot.ini with a '/onecpu' switch at the end. Don't copy-and-paste from this how-to, as disk and partition numbers may vary for your installation, just duplicate the entry that is already there.

[operating systems] multi(0)disk(0)rdisk(0)partition(1)\WINNT="Windows 2000 Server" /fastdetect multi(0)disk(0)rdisk(0)partition(1)\WINNT="Windows 2000 Server (one CPU)" /fastdetect /onecpu

You might have to remove write protection from c:\boot.ini to be able to add entries. Open a shell and enter this command: attrib -r -s c:\boot.ini There is no need to add write protection afterwards.

Recovery console
Another helpful tool is the Windows recovery console, especially when Windows crashes during startup (bluescreens, bad drivers, bad config, cannot boot after adding a new hard disk etc.). The Win2000 version of the recovery console can be used on all versions of Windows while the WinXP version cannot be used on a machine that runs WinNT only. To add it, insert your Windows2000/XP CD, open a shell and enter the following: cd /d :\i386 winnt32 /cmdcons A new entry is added to c:\boot.ini that can be choosen from the list of installed operating systems.

Install Windows Service Pack
As of the time of this writing, no service pack is available for WinXP. Install hotfixes as needed. For Win2000, install service pack 2 and all hotfixes needed. For NT4, install service pack 6, and then the past-sp6 rollup package. This should give you 128 bit encryption strength.

Install IIS on WinNT from the Windows Option Pack CD
Insert the Windows NT Option Pack CD and install Internet Explorer 4.01 (or a newer version).

After this is done:

Launch :\setup.exe

Setup warns that the option pack was not tested with service packs newer than SP4. Click 'Yes' to proceed anyway.

Choose 'Custom' as the installation type.

On the next window, uncheck _all_ components, answering 'Yes' to possible warnings.

Then go to 'Internet Information Server (IIS)' and click on 'Show Subcomponents...'

Move to the end of the new selection list and check 'World Wide Web Server'. You might want to check 'Documentation' (at the beginning of the list). All dependent software components are selected automatically.

To avoid security issues, don't install the following unless you need it and know what you are doing:
 * FTP, SMTP, NNTP servers
 * Index Server
 * FrontPage 98 Server Extensions
 * Certificate Server
 * Microsoft Script Debugger
 * Windows Scripting Host

If setup asks you to specify to specify a folder for the WWW service, you can use the suggestion 'c:\inetpub\wwwroot'.

For Microsoft Transaction Server, just click 'Next' twice. Option Pack Setup should eventually complete. After that, you may have to re-install all service packs/updates for WinNT as some files may have been overwritten by Option Pack Setup.

Securing the Windows/IIS installation
Running NT / IIS out of the box is quite dangerous, so I suggest you take one of security how-to's that are available in this area. A secure IIS installation is not needed for simple in-house testing but it should be considered harakiri if you connect an unprotected machine to the Internet. You have been warned. Here are some links that might help: www.microsoft.com/security/ (then dive in from there) http://www.intersectalliance.com/projects/WinNTConfig/index.html (securing WinNT) http://www.intersectalliance.com/projects/Win2kConfig/index.html (securing Win2000) http://www.shebeen.com/iis4_nt4sec.htm (securing IIS on NT) http://www.intersectalliance.com/projects/IIS4Config/index.html (securing IIS) For more information, look for 'hardening+iis+against+attack' at Google.

Prepare Windows for WebObjects installation
Now that you have a running Windows box - hopefully secure :), we can prepare it for WebObjects installation. The stuff in this section works for both WebObjects 5.0 and 5.1.

Uninstall a previous version of WebObjects
In case you are using an existing machine with an older version of WebObjects, you have to uninstall it before installing WebObjects 5.1. On the WebObjects 4.5 CD, under :\development\Windows\Uninstall you will find several directories: Use the appropriate one for your version. To uninstall WebObjects 5.0, use 'Start, Control panel, Add/Remove Programs'.
 * UninstallWO3.51
 * UninstallWO4.0
 * UninstallWO4.5

Replacing the shell
The shell built in to WinNT does not work with WebObjects at all. For the Win2000 version, there are some issues if the command line gets longer than 2000 characters due to heavy use of frameworks. Because of this, you will have to replace the shell. The good news is that you can use each version of CMD.EXE on every version of Windows, so the basic idea is to get a CMD.EXE from a WinXP installation and use it to replace your default shell - unless you run WinXP :).

If you do not have a Win2000/XP CD, you can at least download Win2000 Service Pack 2 (or Windows XP service pack 1 as it becomes available) and extract CMD.EXE from there. There should be no legal issues since you can download service packs freely.

Before attempting to replace the shell, be sure to cut access to the installation files or Windows File Protection will restore the old version within a few seconds. In case you installed from a CD, remove the Windows installation CD. If you installed over a network, unplug the network cable before starting the replacement. Windows File Protection does not exist on WinNT, you don't have to worry there.

Within the additional files package (download it here), there is a script that does the replacement for you. Feel free to get your own version of CMD.EXE if you don't feel comfortable with the supplied one.
 * Close all open shell windows, as CMD.EXE cannot be replaced if it is running.
 * Double-click on CMD.EXE that came with the script; this opens a new (local) shell that can be used to replace the default shell.
 * In the open window, enter: shellReplace
 * The script will backup the original version of CMD.EXE to CMD.EXE.ORIGINAL and then replace it with the new version. A second later, Windows File Protection will open a dialog, requiring you to insert the Windows installation CD. Press 'Cancel' here and answer 'Yes' in the next windows that pops up.

Now close the shell and open a new one, via 'Start, Run..., enter cmd and press Return'. Look what it says on the first line:

With the original versions, the first line in a shell should look like this:

 * WinNT:       Microsoft(R) Windows NT(TM)
 * Win2000:    Microsoft Windows 2000 [Version 5.00.2195]
 * WinXP:       Microsoft Windows XP [Version 5.1.2600]

When you get this instead: Microsoft Windows XP [Version 4.0.1381] it means that you actually run CMD.EXE from WinXP on a WinNT 4.0 installation (4.0.1381). Other possible combinations are left as an excercise to the reader. :-)it is the good is great and all of slove

Set up Dr. Watson
Now you should change the configuration of Dr.Watson, Windows' integrated crash logger and debugger, so it runs without user interaction. Open a shell and enter: drwtsn32 -i This installs Dr. Watson as the default application debugger, which can be useful if you have installed Visual Studio Debugger and this beast shows its ugly head after each application crash.

After this, enter: drwtsn32 and a dialog window pops up. Here you should use the following settings:

Enable (check):
 * 'Dump Symbol Table'
 * 'Dump All Thread Contexts'
 * 'Append To Existing Log File'

Disable (uncheck):
 * 'Create Crash Dump File'
 * 'Visual Notification'
 * 'Sound Notification'

These settings instruct Dr. Watson to collect as much information as possible and to finish without user interaction. Otherwise, a crashed application will not terminate until the user clicks 'OK'.

 * 'Number of Instructions': 20
 * 'Number of Errors To Save': 50

This saves the last 50 errors with the last 20 instructions for each error.

 * 'Log File Path': Set this to something short and easy to remember. Maybe you already have a folder for log files; I always use the TEMP folder for this.

SDK installation
Next thing is to install a Java-SDK. The version of the SDK must be greater or equal to 1.3.1, SDK 1.3.0 will not work. Also, you have to install a SDK, installing only a runtime environment (JRE) will not work.

I would suggest using SDK 1.4.0 or higher unless you experience problems with it (someone mentioned issues with Timestamp's). Especially on multiprocessor systems, 1.4.0's concurrent garbage collection is a big plus. If you do cross-platform development (OSX/Windows) be sure to download the full international SDK from Sun (the biggest package), as only this version contains all the font encodings from the Mac. Else, you will get exceptions stating that NSMacOSRomanStringEncoding could not be found.

Run the SDK installer.

Use the path suggested (i.e. c:\j2sdk1.4.0).

You can deselect 'sources' and 'examples', they are not needed for WO deployment. Install the Java plugin if you need it.

After SDK setup has finished, open a shell and enter: java -version You should see an output like this:

java version "1.4.0" Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92) Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)

Good job! You are now ready to install WebObjects.

Install WebObjects
With everything set up and running properly, you can now install WebObjects. Be sure to log in as user with Administrator privileges. This section also works for both WebObjects 5.0 and 5.1.

Copy CD contents to local hard disk
Create a new folder on your hard disk and copy the contents of the WebObjects installation CD there. Don't use a shared folder on the network, as Setup needs to restart when it is half way done and fails if it cannot find the contents of the CD; bad karma arises and you will be left with an unusable WO installation ... an empty folder like c:\cdrom would be just fine.

Choose the right version of NMServer.reg
The supporting files package contains several versions of NMServer.reg. Choose the one you need:
 * NMServer.reg.deployment	- use for all version of Windows if you install WO deployment (the original one has a bug)
 * NMServer.reg.nt4-devel	- use for WO development on NT4 (the original one only runs on Win2000/XP)
 * NMServer.reg.win2k-devel	- use for WO development on Win2000/XP (the one included with WO by default)

Rename the appropriate file to NMServer.reg and copy it to
 * c:\cdrom\Deployment\Windows\Install\NMServer.reg (for deployment) or
 * c:\cdrom\Developer\Windows\Install\NMServer.reg (for development).

Depending on the type of setup, run
 * for Development install: c:\cdrom\Developer\Windows\Setup.exe
 * for Deployment install: c:\cdrom\Deployment\Windows\Setup.exe

To complete all steps in this how-to (like compiling the adaptor), development setup is necessary. As someone put it nicely:

Development = Deployment + Developer Tools

If you want to install Deployment only, just skip the steps you don't need. Notice that you will not be able to compile the ISAPI adaptor (which has an ugly bug) if you install Deployment only. Be sure to set up a Development installation (maybe on another machine), too.

Accept the license agreement.

Enter User Name, Company Name and the appropriate serial number for your setup (development, in this case).

No JRE found during setup
I have seen this only on WinNT but it may happen on other versions as well: Although you have installed Java2-SDK 1.4.0, Setup complains that there is no JRE and that you should install Sun Java 1.3 or greater. In this case, quit Setup and import the file jresettings.reg from the supporting files package (just double-click it). You may have to open it first with a text editor (notepad would be fine) and change the path settings in there. By default, it points to c:\j2skd1.4.0. If you have installed the SDK there, no changes to jresettings.reg are necessary.

Choose 'Custom Setup'.

Select C:\Apple as the destination folder. You may specify another drive letter but don't put the Apple folder anywhere else but into the root folder. Great frustration with long paths will manifest itself if you do. Also, under no circumstances put spaces in the path!

On the 'Select Components' don't change anything; simply click 'Next'.

After this dialog, an 'ComponentAddItemError' dialog box may pop up. Simply click 'Ok' there and select 'Other' from the web server list; then click 'Next'.

'CGI-BIN Directory': Specify the path to the IIS scripts folder, which is typically located at c:\inetpub\scripts. You can use the 'Browse...' button to avoid typos.

'Document Root Directory': The path to the IIS wwwroot folder, typically c:\inetpub\wwwroot.

Click 'Next' on all further dialog windows. Setup should start copying all files needed to the right locations. Then it asks you to reboot. Do so and log in again using the same user name as before. Setup will continue with the installation and eventually finish. Don't interrupt it before, this will render the installation useless!

After setup has finished, install the latest update for your version of WebObjects. Reboot when asked to do so.

Congratulations! WO Setup is complete.

Post WebObjects installation steps
Having all the WebObjects files on you disk does not mean sunshine immediately. Here comes what I call the 'post WebObjects setup'.

WebObjects base installation
Open 'Services':

 * on WinNT: 'Start, Control Panel, Services'

%systemroot%\system32\services.msc /s You will find the following WebObjects related services. The name of the service is written into brackets - you can use that name to start/stop these services from the command line.
 * on Win2000/XP: Open a shell and enter:

These two are installed in both Development and Deployment: This service manages all running application instances. It should be set to run automatically at startup on both Development and Deployment. This service provides a user interface to create new and manage existing application instances, sort of a frontend to wotaskd. It is not needed to start and manage application instances and should (for security reasons) be set to run manually. To start it, open a shell and enter:
 * Apple WebObjects Task Daemon 5 (wotaskd5)
 * Apple WebObjects Monitor 5 (womonitor5)

net start womonitor5

After the time it takes to start (usually only some seconds), you can open a browser and point to http:// :56789 to see the first page. To improve security, go to the 'Preferences' tab and set a password for accessing Monitor. Then use it like you normally would. To stop it after use, enter:

net stop womonitor5

In WebObjects 5.0, wotaskd and Monitor are not installed as services by default. To change this, you will need the instsrv and srvany tools from the supporting files package. Here comes a quick step-by-step guide to install wotaskd and Monitor as services, for more information see srvany.doc that comes with srvany.exe Move the 'Start Task Daemon' shortcut out of the Startup folder to stop wotaskd from starting when you log in. I always put it into the 'WebObjects' folder but you can delete it as well. Open a shell and enter instsrv wotaskd c:\apple\srvany.exe Change the DisplayName property to 'Apple WebObjects Task Daemon' Change the Start property to '2' if it is not already 2. This means that the service should start automatically. Under this subkey, create a new Application property (type string) with the following value: C:\Apple\Library\WebObjects\JavaApplications\wotaskd.woa\StartWOTaskD.exe Do not use %NEXT_ROOT% here - this may not yet be available when the service is about to be started resulting in a failure. Use the absolute path name to your WebObjects root folder instead. After reboot, you should be able to view http:// :1085 from another machine without logging in to the Windows box. If this works you have done everything correctly.
 * Right-click on the Start menu button and choose 'Explore all users'. In the Explorer window that opens, go to 'Programs, Startup'.
 * Copy srvany.exe to an easy to remember place like C:\Apple (or C:\WinNT).
 * Open RegEdit and go to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\wotaskd
 * Under the wotaskd key, add a new subkey and name it Parameters.
 * Create a second string value property, AppDirectory, with the following value C:\Apple\Library\WebObjects\JavaApplications\wotaskd.woa
 * Reboot your system

If you want to install Monitor as a service, too, do the following. Change DisplayName to 'Apple WebObjects Monitor' Change Start to '3' which means 'manual startup'. If you do not have to care about security, you can set it to '2' (automatic startup). Under this subkey, create a new Application property (type string) with the following value: C:\Apple\Library\WebObjects\JavaApplications\JavaMonitor.woa\JavaMonitor.cmd C:\Apple\Library\WebObjects\JavaApplications\JavaMonitor.woa -WOPort 56789 You can specify more command line arguments here if you like. Now, try to start Monitor. Open a shell and enter net start womonitor After a few seconds, open your browser and go to http:// :56789. You should see Monitor's starting page.
 * Open a shell and enter: instsrv womonitor c:\apple\srvany.exe
 * Open RegEdit and go to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\womonitor
 * Under the womonitor key, add a new subkey and name it Parameters.
 * Create a second string property, AppDirectory, with the following value
 * To start Monitor on port 56789 every time, add a third string property, AppParameters, with the following value

If you have WO Development installed, there are some more services
These two services - together with Pasteboard Server and Window Server - provide the base to run YellowBox applications (most of the WebObjects Developer Tools) on Windows. You don't have to care about them, just set them to start automatically. This service starts the Openbase database engine and provides several sample databases. If you don't want to use OpenBase, you can save memory by setting the startup type to 'Manual' which prevents this service from starting automatically. To get rid of the Openbase processes that are running at the moment, you can either reboot or kill all relevant processes using the 'kill' tool (included in the supporting files package). Open a shell and enter:
 * Apple Mach Daemon (Apple_Mach_Daemon) and Apple Netname Server (Apple_Netname_Server)
 * openexec (openexec):

kill OpenBase.exe kill pgroup.exe kill databackup.exe kill openinfo.exe

Important notice for all services
Never set the startup type of an entry to 'Disabled' unless you are dead sure what you are doing! 'Manual' services can be started from the system as needed while 'disabled' services cannot be started at all (unless you select a different startup type). Disabling the wrong services may prevent the system from booting and is not considered to be a good idea.

Besides all those services, for a Development installation there are two shortcuts with the following settings in the 'All Users' Startup folder: Target: %NEXT_ROOT%\Library\Frameworks\AppKit.framework\Resources\pbs.exe Start in: %NEXT_ROOT%\Library\Frameworks\AppKit.framework\Resources Run: Minimized Target: %NEXT_ROOT%\Library\System\WindowServer.exe Start in: %NEXT_ROOT%\Library\System Run: Minimized
 * Pasteboard Server
 * Window Server

WinXP Fast User Switching
'Pasteboard Server' and 'Window Server' are started everytime a new user logs on. With Fast User Switching, more than one user can be logged in at the same time, causing Pasteboard Server and Window Server to be started more than once. Both programs don't like it and stop working which in turn causes the developer tools (ProjectBuilder, EOModeler, WOBuilder) to stop working, too. ;-(

Workaround: If only one user on the machine is developing with WebObjects, move the shortcuts for Pasteboard Server and Window Server from the 'All Users' Startup folder to the personal Startup folder of the áppropriate user. If more than one user on the machine is developing with WebObjects, make sure they are not logged in at the same time. If nothing else helps, you can disable Fast User Switching.

WebObjects Update program
There are at least two bugs when installing WebObjects Updates:
 * Files that you have changed yourself are not updated, the updater just silently skips them. Because of this, if you want to be sure that a given file is updated, rename it - for example, to  - and then run the update again.
 * Everytime the update is run, the WebObjects relevant folders are added to the PATH variable. Depending on how often updates are installed, this can result in an extremely long path. After the update has finished, clean the PATH variable manually.

Changes to environment variables
Open 'System' in Control Panel. On the 'Advanced' tab, click 'Environment Variables', then click the name of the user variable or system variable you want to change, as follows: Click New/Edit/Delete to add/change/remove a new variable name and value. You may have to close and reopen running programs for the new settings to take effect. To update all running services with the new values, it may be easier (and less error prone) if you just reboot the machine once more.

The following environment variables should exist after WO setup:

one contains the path to several libraries used be the WebObjects developer tools and should point to C:\Apple\Developer\Libraries. You usually don't have to change anything here, it is just mentioned for completeness.
 * LIB:

New: Don't change the slash in NEXT_ROOT into a backslash! Just leave it as it is. The compilation of the adaptor sources will fail, if you do this. It's all my fault, that I suggested otherwise in a previous version.
 * NEXT_ROOT:

The following three entries are the only ones needed by WebObjects but they are usually duplicated by running the WebObjects Update. This does not harm except that it makes the PATH variable unnecessary long. Be sure to include the following only once (this assumes that you have installed WebObjects to C:\Apple): C:\Apple\Library\Executables; C:\Apple\Library\JDK\bin; C:\Apple\bin;
 * PATH:

For the user accounts used to develop and run WebObjects applications, remove both TMP and TEMP from the user variables section and add them to the all users section. Change TEMP and TMP to a path without spaces, like c:\temp. You may have to create c:\temp if it does not exist yet. IMHO, that makes finding launch script zombies, log files and other WebObjects temporary stuff much easier.
 * TEMP:

To clean the zombies, you alternatively can use any application you have written and start it in a shell with the 'clean' parameter: cd myapp.woa myapp.cmd clean

This contains the path to the extensions folder for additional JAR files. A typical path is C:/PROGRA~1/Java/J2RE14~1.0/lib/ext. Be sure not to put spaces in here, either move your JRE to another folder or use short names (8dot3 notation like old MSDOS).
 * WEBOBJECTS_JAVA_EXTENSIONS

This specifies the path to the SDK used by WebObjects applications you write. A typical path is C:/J2SDK1~1.0. Also like above, don't use spaces in the path name.
 * WEBOBJECTS_JAVA_HOME

As opposed to the SDK you specify here, the WebObjects developer tools always use JDK 1.1.8 in %NEXT_ROOT%\Library\JDK - you don't have to care about that.

Upgrade the license
If you have installed WebObjects Development but want to fully use its Deployment capabilities (like multiple instances, unlimited requests etc.) you have to upgrade your license to the Deployment version. There are two ways to do this. First one is to use the License Upgrader. Open a shell and enter:

%NEXT_ROOT%\Demos\JavaWebObjectsLicenseUpgrader.app\JavaWebObjectsLicenseUpgrader.exe

Alternatively, you can open it via 'Start, Programs, WebObjects, WebObjects 5 License Upgrader'. The second way (if the License Upgrader does not start) is to edit the key file with a text editor (Notepad would be okay). Just start the editor and open

%NEXT_ROOT%\Library\Frameworks\JavaWebObjects.framework\Resources\License.key

Delete the old license key and insert the new one.

You have to restart wotaskd and (if it is running) Monitor for the changes to take effect: net stop wotaskd5 net stop womonitor5 net start wotaskd5 net start womonitor5

Mess around with the adaptor
There is a ugly bug in the IIS adaptor that extremely degrades performance when it is put under heavy load. You will reach about 3 to 5 requests per second and memory will be eaten at extreme speed, resulting in OutOfMemoryExceptions after just a few sessions have been created. This happens whenever you use the ISAPI adaptor, even when your applications run on another machine with a different OS.

Luckily, Karl Hsu found a fix to this which I am extremely grateful for. Thanks Karl! Karl states that you should incorporate this fix only if you are seeing massive performance problems, so do some test before patching. The Apache Benchmark tool ab (included in support files package) can be used for this.

Both files, request.h and request.c can be found at %NEXT_ROOT%\Developer\Examples\WebObjects\Source\Adaptors\Adaptor

Patched versions of request.h and request.c that include the code below are also included in the support files package. If you did not make changes to these two files yourself, you can copy them to %NEXT_ROOT%\Developer\Examples\WebObjects\Source\Adaptors\Adaptor and start compiling right away.

Else, in request.h, search for #include "WOURLCUtilities.h" and add the following line right below:

#include "wastring.h"

In request.c, search for the beginning of the method 'int req_sendRequest' (around line 217) and add the following code block _before_:

#ifdef WIN32 static void req_appendHeader(const char *key, const char *val, String *headers) { int valLength = strlen(val); while (val[valLength - 1] == '\r' || val[valLength - 1] == '\n') { valLength--; } str_append(headers, key); str_appendLiteral(headers, ": "); str_appendLength(headers, val, valLength); str_appendLiteral(headers, "\r\n"); } int req_sendRequest(HTTPRequest *req, net_fd socket) { struct iovec *buffers; int bufferCount, result; String *headersString; buffers = WOMALLOC(3 * sizeof(struct iovec)); headersString = str_create(req->request_str, 0); if (headersString) { st_perform(req->headers, (st_perform_callback)req_appendHeader, headersString); } buffers[0].iov_base = headersString->text; buffers[0].iov_len = headersString->length; buffers[1].iov_base = "\r\n"; buffers[1].iov_len = 2; bufferCount = 2; if (req->content_length > 0) { bufferCount++; buffers[2].iov_base = req->content; buffers[2].iov_len = req->content_length; } result = transport->sendBuffers(socket, buffers, bufferCount); str_free(headersString); WOFREE(buffers); if (result == 0) result = transport->flush_connection(socket); else WOLog(WO_ERR, "error sending request"); return result; } #else

Now, right after this #else comes the original req_sendRequest method. Go to the end of this method and insert a #endif in the next line. When you have successfully patched the source file, it is time to...

Compile the adaptor
Next bug: The makefile expects the OS environment variable to be either empty or contain the string 'WINDOWS'. Unfortunately, the value of OS is WINDOWS_NT instead of WINDOWS so the make process will fail to compile the ISAPI adaptor. You could change the makefile so it works with WINDOWS_NT but you will have to do _a lot_ of changing then.

Instead, let's just change the OS variable. However, don't change this permanently via control panel, as I do not know if it is needed elsewhere. Just setting it to the proper value before compile is enough. Open a shell and enter: cd /d %NEXT_ROOT%\Developer\Examples\WebObjects\Source\Adaptors set OS=WINDOWS make clean make If compilation was successful (no error messages), we now should have the WebObjects adaptor as EXE (CGI) and DLL (ISAPI) file. These two should be copied to the proper location (change c:\inetpub to match your system). net stop w3svc copy /y cgi\webobjects.exe c:\inetpub\scripts copy /y iis\webobjects.dll c:\inetpub\scripts net start w3svc Stopping IIS before copying prevents possible sharing violations for the ISAPI adaptor as you cannot overwrite the file if it has already been loaded by IIS.

ISAPI Adaptor configuration settings
Now that you have compiled the ISAPI adaptor, you have to configure it. For a single server deployment (IIS and wotaskd on the same machine), no further configuration is necessary. However, if you have wotaskd running on another machine, or want to use more than one application server, you have to tell the adaptor where to look for them. The way that always worked for me is via host list in the registry.

Open Regedit (open a shell and enter regedit) and go to:

HKEY_LOCAL_MACHINE\SOFTWARE\Apple\WebObjects\

If it does not exist yet, create a new subkey and name it 'Configuration'. Then go to:

HKEY_LOCAL_MACHINE\SOFTWARE\Apple\WebObjects\Configuration

and add the following two keys (type REG_SZ):

CONF_INTERVAL 10

This means that every 10 seconds, the ISAPI adaptor will talk to the wotaskd's and re-read the configuration.

CONF_URL http://host1:1085,http://host2:1085,http://host3:1085

This is a comma-separated list of all hosts, on which a wotaskd is running. 1085 is the default port. Just add all application servers and be sure not to put spaces after the commata - WO does not like spaces. If you have fixed IP-addresses, you can specify them, otherwise use hostnames and make sure the IIS machine can resolve hostnames to valid addresses. This should be no problem if all machines run Windows, otherwise you have to use a DNS server or a host file.

Testing the installation
Now that everything should be set up properly, it is time for a small test. First, let's look if wotaskd is working.

Open a browser and go to http:// :1085

If it works, you should see the host's configuration displayed in the browser. Now, open a shell and start Monitor if it is not already running: net start womonitor5 Wait a few seconds, then point the browser to http:// :56789

You should see Monitor's main window. Now you can start writing and deploying applications. Have fun!

Troubleshooting
Q: I keep getting the following exception when running the WO applications: java.io.UnsupportedEncodingException: NSMacOSRomanStringEncoding

A: Make sure the charsets.jar library is in your Java runtime path. This is installed in the JDK but not always in the JRE.

Revision list
1.3: 2002-08-29 - removed suggestion to change the slash in NEXT_ROOT into a backslash, because this breaks compilation of the adaptor sources

1.2: 2002-07-26 - added steps to install wotaskd and Monitor as services in WebObjects 5.0

1.1: 2002-07-10 - added two workarounds for errors during WebObjects setup on WinNT

1.0: 2002-07-09 - first public release

0.9: 2002-06-28 - work in progress


text:
WebObjects/Web Applications/Development/WebObjects and Subversion

Ryan
Notes about using subversion with WebObjects, Interface Builder, and Mac OS X.

9/2/2005

Versions Used:
 * Mac OS X 10.4.2
 * subversion 1.2.3
 * Interface Builder 2.5.1 (439)
 * XCode 2.1
 * WebObjects 5.3

Appendices C and D of the Apple XCode User Guide (Using Subversion and Configuring Your SSH Environment) were fairly helpful in getting things set up although I would recommend installing subversion via the download links on the main subversion web page rather than using DarwinPorts. The binary version available via these links is easier to install since it uses a traditional double clickable package installer. Note that your install path will be /usr/local/bin instead of /opt/local/bin.

I also found the book "Version Control with Subversion" by Ben Collins-Sussman, Brian W. Fitzpatrick, and C. Michael Pilato to be very helpful.

For example: svnadmin create --fs-type fsfs /path/to/repos command="/usr/local/bin/svnserve -t -r /Users/svn/svnrep/" ssh-dss
 * When creating the repository it seems preferable to use the file system rather than the Berkeley DB. To do this, pass the --fs-type fsfs flag to the create command.
 * There are at least three different ways to authenticate to the repository. I used the svn+ssh method although I'm sure the others would work as well.
 * If you use the ssh+svn method, it's helpful to provide a virtual repository path so that end users don't have to type the entire path. For example, in my authorized_keys file, my key begins with:

This allows users to use a path like "svn+ssh://server-name/project-name" rather than "svn+ssh://server-name/Users/svn/svnrep/project-name".

 * When using ssh with multiple users, in order to prevent file permission issues, each subversion command on the server should be wrapped by a shell script which first calls umask, ie:

$ cat /usr/local/bin/svn #!/bin/sh umask 002 /usr/local/bin/svn-real "$@"

So this means you should rename each command and create a shell script wrapper which calls the actual command.

http://www.lachoseinteractive.net/en/community/subversion/svnx/features/
 * If you create trunk, branches, and tags directories as suggested in the book, you should only checkout the trunk.
 * The subversion support in XCode has a few problems, and I have had better luck using the free "svnx" which is available on the web.

 * Subversion leaves ".svn" directories in each directory that it's managing which causes problems with earlier versions of Interface Builder and EOModeler which delete all of these directories upon save. Make sure you're using the latest version available of each of these tools.

 * If you're developing a Java Client program using Interface Builder, your nib file will contain a java file buried under several directories. Each of these directories contains an ".svn" directory which even the newer versions of Interface Builder delete. In order to correct this problem, you'll have to download and install the "SubversionPalette.palette"; from http://www.rtlabs.com/downloads/. Even though this is apparently fairly old, it still seems to work (9/2005).

That's it! Good luck and enjoy subversion.

Peter Vandoros
I have ported the EOModeler fix from http://www.rtlabs.com/downloads/ to Windows. Get it from http://wocode.com/cgi-bin/WebObjects/WOCode.woa/wa/ShareCodeItem?itemId=451.

Subversion


text:
WebObjects/Web Applications/Development/Third Party Jars

Problem
You're using Xcode, and you'd like to use one or more third party JAR files in your project.

As of WO 5.2, one reasonable solution would be to just toss all the JAR files that you need into /Library/WebObjects/Extensions/ (see What's New in WebObjects 5.2 in the section on Launch Architecture: http://developer.apple.com/documentation/WebObjects/WhatsNew5.2/ ). The problem with this solution is that it creates a bit of a management headache. You don't necessarily know which projects are using which JAR files, and you might have trouble if different projects were to need different versions of the same third party JARs.

For example, we have a project that uses the Batik SVG Toolkit. We needed classes from many of the jar files that are included with batik-1.5.1. But what happens when another project comes along that needs a different (e.g. a newer) version of Batik? We'd have to either make sure the old project works with the newer batik, or restrict ourselves to the old batik code for the new project.

Solution
One solution to this problem is to create a WebObjects framework that contains all the jar files for a particular version of an external project (like Batik). That way, several WO apps can use the same WO framework (e.g. batik-1.5.1.framework), but if a newer batik comes out that we need to use for a new project, and if it turns out to be at all incompatible with the old version, then we can just create another batik WO framework, named for the new version; e.g. something like batik-2.0.framework, if/when a batik-2.0 is released.

Here's how to do it (I'll continue to use my batik-1.5.1 example):

(1) In Xcode, create a new WebObjects Framework project (e.g. batik-1.5.1).

(2) Create a new group under the framework project's Resources group. This step is optional, but is recommended simply for organizational clarity. Also toward that end, I suggest you call your new group Java (we'll see why in a moment).

(3) Add the desired JAR files to this group. For my batik example, I selected all the .jar files in my batik-1.5.1/lib directory. Check Copy items into destination group's folder (if needed), and select the aggregate target (i.e. the one with the same name as your project).

(4) We want to add a new build phase to the aggregate target. Open the Targets group (under Groups & Files) and double-click on the aggregate target. It's essential that this new build phase be added to the aggregate target rather than the Application Server target!

(5) We want to add the new build phase at or near the end of the list of Build Phases. So right-click (or control-click) on the last build phase, select New Build Phase, then New Copy Files Build Phase. You can also drag the new build phase to a new position after you create it.

(6) Change the Where: popup for your new build phase to Java Resources.

(7) Back in the main project window, select all the JAR files that you added to your new Resources -> Java group, and drag them into the Files: area for your new Copy Files build phase (in the aggregate Target panel).

(8) Build and install your WO framework:

xcodebuild install -configuration Deployment DSTROOT=/

That's all there is to it. Inside your installed framework, you'll find all your .jar files located in the Resources/Java directory (e.g. /Library/Frameworks/batik-1.5.1.framework/Versions/A/Resources/Java), regardless of what you named the group in your project.

Many thanks to Chuck Hill for the key ingredients of this solution.

Another Solution
A WebObjects application will automatically load any .jars located in the sub-folder "Contents/Extensions" inside the .woa wrapper. Compared to the Frameworks method, it has the advantages of keeping the .woa completely self-contained and avoiding potential version conflicts if different WebObjects applications need different versions of the .jar file. The downside is that the .jar is not shared between applications, so that if you want to update it you need to update it for each .woa individually, and you can have multiple copies of the .jar taking up disk space. (The latter is usually not a problem these days.)

To automate this as a part of the build process in XCode, add a Copy Files build phase to the top-level target. Set the Destination to "Wrapper" and the Path to "Contents/Extensions".


text:
WebObjects/EOF/Using EOF/Logging

Overview
You can turn SQL logging on when launching by using -DEOAdaptorDebugEnabled=true or EOAdaptorDebugEnabled YES. However, this can slam out a ton of SQL. While this is useful for some debugging it is way too much to sift through when trying to analyze a particular problem.

You can use this method to selectively enable and disable SQL logging:

public static void logSQL(boolean shouldLog) { if (shouldLog) { NSLog.allowDebugLoggingForGroups(NSLog.DebugGroupSQLGeneration |                                        NSLog.DebugGroupDatabaseAccess |                                          NSLog.DebugGroupEnterpriseObjects); } else { NSLog.refuseDebugLoggingForGroups(NSLog.DebugGroupSQLGeneration |                                          NSLog.DebugGroupDatabaseAccess |                                          NSLog.DebugGroupEnterpriseObjects); } }

This can be useful for such things as determining what fetches are performed when a page is rendered: public void appendToResponse(WOResponse aResponse,                            WOContext aContext) { logSQL(true); super.appendToResponse(aResponse, aContext); logSQL(false); }

title: WebObjects/EOF/Using EOF/Logging