text:
WebObjects/Web Applications/Development/Database vs Filesystem

Overview
There is an ongoing debate, generally related to media files, about whether to store media in the database or whether to store media on the fileystem and just store the reference in the database.

This article attempts to track some of the notable writings about the debates.

Joe Moreno
Keep in mind that the purpose of a database is to store data to be search and retrieved.It would be a rare case when you'd actually send a query to a database that consisted of an image blob (i.e. search for an image that matches certain binary data). More than likely, you'd perform a search for an image based on its meta-data like date, time, image name, or file system path. A good solution is to store the path to the medium and then simply build the reference URL for the client's browser to reference or have the application retrieve the medium from the file system and serve it up through the WebObjects adapter. In the former case you can keep the media under the Web server (say image thumbnails) and, in the latter case, you can keep full size images anywhere else on the server's file system and server them up based on a user's profile (i.e. did they successfully check out?, etc.).

Michael Engelhart
Storing images in the database is generally a bad idea in my opinion. There's much more overhead in retrieving image data from a database then there is in just letting Apache serve up the image. Apache has been highly optimized just for this purpose. Databases generally have not.

My suggestion is to just store a URL for the image in the database and write that URL to the dynamic page. Or if you know the path is always going to be the same you could simply store the filename.

Robert Walker
It seems that different people have different opinions on this topic. I've followed several threads on this and I still haven't come to a conclusion on the best design pattern.

I wouldn't want to put the images in a share point on the network with a link to the image directory. That would create a single point of failure. If you lose the connection to the box storing the images, for any reason, then you have no access to the images. With images stored in the database you can use database clustering to provide some redundancy and fail-over support.

In either case you will be transporting the binary data for the images across the network either directly via a share, or through the database connection. I personally take the easy way out and store the images in the database.

An Example: Say I have a Product entity and want to upload and store product photos: I would create two entities Product and ProductPhoto. I would then relate them with either a toOne or toMany relationship depending on whether I need one or many ProductPhoto objects for each Product object.

With this design pattern fetching Product data doesn't directly load the images. Instead EOF will create faults representing the images. The image data isn't fetched until the fault is fired by accessing the ProductPhoto fault object. So If you fetch 500 Products and batch them into groups of 10 with the WODisplayGroup then your first page would fetch only the first 10 images not the 500 (and only if there is a WOElement? or method that accesses the image data).

This pattern also greatly simplify uploading and storing the images because you can bind the NSData used to upload the image to your ProductPhoto's imageData BLOB.

It's probable that many will disagree with me on this issue, but I have had good success, for my purposes, with this design pattern.

You can find an implementation of this design pattern for both toOne and toMany photos in the JavaRealEstate framework example in /Developer/Examples/JavaWebObjects/Frameworks.

Michael Halliday
I haven't had any problems storing images in our database (OpenBase). We have developed many "community" based sites with photo albums as well as an online dating service, both use the same methods that Robert talked about in his message.

I have to say that we have had no performance issues, and to me this is the most elegant, scalable solution for several reasons:

 * You have all of the database clustering options available to you for data redundancy & data backup.
 * If your site was to grow substantially in size and you required multiple HTTP Servers (along with your multiple application servers), if your images were served by apache from the local filesystem you would run into issues with having to somehow replicate your image directories across the multiple Apache servers. I'm not saying this couldn't be done...it would just require the proper planning. Using the database for an image store would solve all of these issues.
 * Using the database method, WebObjects would cache your images so you wouldn't actually have to go to the database each time.
 * If you need to "associate" images with other objects I'd hate my images to be stored in the file system. You would eventually most likely run into broken links etc...this would get rather messy. Plus if you need to control access to images (I.E. Only logged on users can view product images) you would need to rely on filesystem/apache security which adds yet another layer of complexity to your application.

Again, I know many people will probably disagree with this approach. But, it is working perfectly for us and for dynamic images (or images that the user can change/upload) I think it's the most effective approach. That being said, we do use apache to serve up our static images.

I'd be interested to hear from others and there experiences with storing images in databases. You hear a lot of people saying "Don't do it, it won't perform well."...but have these people actually tried it? Or have they just been told not to do it. I have been very interested in this topic for a while now and I have done extensive searching but never come up with any "correct" answer. I think it also depends on which database you use and how exactly the database itself stores images. I know that some are much better than others and personally this is where you'd most likely run into the performance hit (if any).

Geoff Hopson
On the Fortnum & Mason online store http://www.fortnumandmason.com, the product catalog is pretty image-heavy. Also, they (F&M) change the catalog and the associated images at least twice a year. So I wrote a tool that allows their product images to be uploaded into the database, simply for the purpose of having everything in a single place for backup reasons. When a new catalog is ready to be deployed, the images are extracted from the database and placed under the webserver (since, as everyone notes, webservers are particularly good at vending images). The main F&M web application then gets all it's images from the webserver, as opposed to cached in the webobjects application after a fetch from the database.

However, in development, we used the images from the database directly. Command line switch toggles whether the images are read from the webserver or the database.

Doing all this means that the memory footprint is lower, since the application is not caching images, and it also means that we can do clever things with the webserver to spread the load a little.

Chuck Hill wrote something on the pros and cons of using the webserver yesterday - http://lists.apple.com/mhonarc/webobjects-dev/msg05564.html (use 'archives', 'archives' as the username/password).

Arturo Pérez
My opinion and experience FWIW, having done it both ways. I keep having this discussion so I'd thought I'd put it all down in one place.

Database pros:
 * It's really easy.
 * It works well.
 * It's consistent with everything else; i.e. all data comes from the database.

Database cons:
 * There are some tuning issues around BLOBs if you're using those (or equivalent).
 * I don't understand people saying "It solves single point of failure." Didn't you just move the single point of failure from a file system to the database? Sure, databases offer replication but you can achieve the same with a clusterable file system like Transarc or the Andrew File System (AFS).
 * It "makes no sense." It's just storing a bunch of non-relational data in a relational database. It feels unclean :-)
 * If you need to manipulate the data it's much much harder. For example, if it's text and you want to fix a typo it's problematic. If it's an image updating EXIF headers would be a chore. It's much easier to run a perl script over a directory of files.

Filesystem pros:
 * It scales better.
 * It's easier to manipulate the images if that's necessary. Particularly offline from serving them.
 * It's cheaper. (Disks are cheaper costwise than databases esp. if you need a DBA).

Filesystem cons:
 * It's a little harder to manage. And you might still need a database to keep track of the images.
 * It's harder to build a content management system that can upload images. WebDAV might address this issue.

Well, my 2 farthings.

Chuck Hill
One largish problem with storing them in the database is that EOF will cache the data, at least for a while. For a heavily loaded site or large contents this can really chew up the memory fast.

Another alternative is to store them on the file system but not directly available to the web server. Keep an object in the database that references the data on the file system. Use Java streams to move the data from the request to the file system and from the file system into the response. This avoids the EOF overhead but allows your application to control access. It is much more efficient to have the web server directly access and vend the images etc. but if you have access restrictions this is not an option. This hybrid database / file system approach can be useful in that situation.

PetiteAbeille wrote about an EOF file system adaptor that may be of interest in relation to this question: http://www.wodeveloper.com/omniLists/eof/2002/June/msg00053.html

Tom Pelaia
We grab images from the database in our WebObjects application (electronic logbook). It is a very heavily accessed site and allows users to make entries that have text, images and other attachments. We have found additional "pros" for loading images from a database.

Additional database pros:

 * The logbook isn't the only application that uses our database. We just use a subset of the database. Other applications write to the database and make entries into our logbook and use data from our logbook. Storing complete logbook entries (images, text, attachments) in the database means other applications have easy access to logbook entries. Hence we are integrated well.
 * The database is the one official source of all data
 * Backups are consistent and easy. When the database is backed up, all logbook data is backed up and the backups are self consistent.
 * It is easier to transfer data from one machine to another since you only have one thing to transfer.
 * Since the images are provided by users, the database provides a way to avoid file names collisions. If you use a file system you need to save filenames carefully to avoid duplication.
 * The first logbook we developed several years ago (without WO) stored data including images in the filesystem. That turned out to be very inflexible. If you want to reorganize the folder layout, lots of links will break. This is simply a non-issue for database storage.
 * Databases are simply much more flexible. Filesystem storage is very rigid. You can store your files for the present application, but needs tend to change in time and database storage is much more flexible in supporting future needs that you might not be considering today.

Whether you choose database storage or filesystem storage really depends on your application. For our application, the electronic logbook is becoming more integrated with other systems and the database has turned out to be critical in that integration.

ocs
I happily store images in the database, but... my clients use Oracle or FrontBase. The very now though I have the misfortune to work on an application which has to use the MS-SQL thing: seems it really does not support BLOBs well (actually, the database administrator just plain told me "do not use a BLOB in your tables, ever--we have the worst experience with them").

Myself, I've tried of course :) (with a test database) and found that indeed there seem to be issues, like that a BLOB is never found by a WHERE clause (even if a proper value is provably given). I haven't tested for long :)

Thus, although I am a strong believer in storing images in the database, I can understand others who are unlucky enough not to be FrontBase users might have different opinions :)

The reason I am writing: before deciding where to store your images, do check the concrete database to be used. If FrontBase or Oracle, you probably would want to store them in the database, if MS-SQL, you probably would want to store them in the filesystem :)

Jeff
I'm a little WORusty at the moment, so please excuse any gaffes in this. WO 5.3 has renewed my interest in WebObjects.

I've experimented quite a bit with using the images in a database, and have generally found that if you do it right, there's very little performance hit to doing it compared to the file system route - Databases in general have gotten much better about handling blob data and a lot of the reasons for not storing images there simply no long apply. Plus, storing the images in the database makes writing, maintaining, and backing up your application all much easier.

Generally, I create a direct action method for dispensing the images. Something like this in your DirectAction (except that you might want to add validation if you don't want just anyone getting to your images by hacking the URL):

public WOActionResults imageAction {   // PictureTest is an EOEntity with a BLOB containing the image data PictureTest pt = getPictureTestEO; return jpegResponseWithData(pt.image); }    private PictureTest getPictureTestEO {   // Yes - you can get the session in a direct action // you just need to be prepared to deal with one not existing // whether you return an image if no session exists depends on  // on your own application needs. WOSession theSession = existingSession; EOEditingContext ec = (theSession == null) ? new EOEditingContext : theSession.defaultEditingContext; String picid = (String)request.formValueForKey("picid"); return (PictureTest)EOUtilities.objectMatchingKeyAndValue(ec, "PictureTest","id", new Integer(picid)); } private WOResponse jpegResponseWithData(NSData theData) {  // This method returns the data so that the browser // recognizes the image type. In this particular application // I've just hardcoded a mime type of JPEG because I only // use JPEG images, but a better way would be to store the mime-type // that corresponds to the image data in the BLOB as a separate // field. I might revise this sample later on to show that. WOResponse response = WOApplication.application.createResponseInContext(context); response.appendHeader("image/jpeg", "Content-Type"); response.appendContentData(theData); return response; }

Then, in your WOComponent, you create a virtual accessor like this:

public String imageURL {   return context.directActionURLForActionNamed("icon", null) + "?picid=" + pictureItem.id; }

in this case, pictureItem is a PictureItem EOEntity instance that I use in a WORepetition - I'm just pulling the id number from the currently selected picture.

Then, in WOBuilder, you bind the WOImage or WOActiveImage's src binding to imageURL

This approach eliminated a lot of the overhead that you get by just binding directly to the EOEntity's attribute, and really isn't much extra work.

Bill Bumgarner
Storing images in the database is generally a bad idea for a whole slew of reasons. First and foremost, it is loads slower than serving images directly from the web server and it completely bypasses numerous automatic "optmiziations" that are present when serving from a filesystem. If it can't be avoided, it can't be avoided.... however, if you have any hopes of scaling your solution to a large community of users or a heavy hit rate, expect to expend a lot of engineering and hardware dollars making images-in-the-database go fast.

In particular:

Images are normally served statically from the filesystem... because you are now serving them as dynamic content, the following performance hits occur:
 * no client side caching [ouch] five copies of a single image on a page yields five separate hits on WO.
 * image requests must be serialized-- not only do IMAGE hits have to be serialized, but all other hits on the WOF app will have to wait for any pending image hits to be handled. In terms of Netscape's REALLY SLOW table layout algorithm that requires the size of all images to be known, this means that the user WON'T see the contents of the table until ALL image hits have returned at least the size of the image.... since hits are serialized, that means that all but the last image must be entirely handled.
 * performance difference between a static hit vs. a fully dynamic hit is tremendous [in favor of static]. Think about it... a static hit basically means the web server opens a file, reads/writes the contents to a socket, closes... dynamic hits require IPC, a database round trip [maybe], a bunch of memory munging, a pass through request/response, etc.etc.etc...
 * no server side caching; every instance of your app will end up with a copy of every image served in its memory. As well, the IPC between database and WO app server will have to pass all that data back and forth, as well.
 * most databases are not designed to handle BLOBs well.... regardless

Alternatives?

Stick a path in the filesystem in your database instead of a blob; abstract arbitrarily to facilitate administration, etc... If you REALLY need the images to come from the database, build an image manager that maintains a hierarchy of the images in the filesystem and arbitrates the updates between the database and the images.

One thought; if an image needs to be refreshed and you are worried about client-side or proxying-firewall caching, rename the image in the filesystem (or move it) and generate a new URL-- this should be the image managers responsibility.

John Ours
I agree with almost everything said above, but the one consideration that often has me moving images and files into the database is security. If there's no security consideration then putting them on the filesystem and serving them straight from the web server is quite reasonable (your biggest challenge is keeping everything in sync...making sure a careless administrator or virus scanner doesn't come by and remove/rename/alter files out from under your application). However, you're often dealing with use cases like "this group of users can read this PDF" or "only registered users of a certain level can see this image". In those cases the hit you take by serving the PDF or image dynamically is a small price to pay for the administration headache of trying to implement other access controls, like physically separating the files and using .htaccess or windows file permissions. In my experience, most solutions to this problem end up with a proxy arrangement - where the application reads the resource and then writes it back to the client - in which case the image may has well have been in the database to begin with.


text:
WebObjects/Overview/Objective-C to Java Transition

The contents of this wikibook are out of date and have now moved to http://wiki.objectstyle.org/confluence/display/WO/Home.

Windows
WebObjects 5 projects will require converting with the JavaConverter, whether or not they are already written entirely in Java. This is best run from a Bourne Shell, and is at $NEXT_ROOT/Library/WebObjects/JavaApplications/JavaConverter.woa/JavaConverter.cmd. Run it with the full path of your chosen project and it will create a new project, whose name will be the original name with "Java" appended.

You should then be able to open the new project and browse the source code. The Java Converter has non-trivial tasks to perform, more so if you've come from Objective-C, so you'll want to read through your source code and check it's done a job you're happy with. As an alternative, you could search merely for its comments (labelled JC_WARNING or JC_ERROR) and review each one.

Note that the Makefile will be removed by the conversion process, and for some reason best not asked about, Project Builder will recreate the Makefile only if files are added or removed from the project - and not in the rather more obvious case where you try to build and there is no Makefile. So you'll need to add and remove a dummy class, for example, before your project will build.

EOModels will need converting to the JDBC adaptor. Since JDBC is itself a multiple-database protocol, the JDBC adaptor itself does not have the usual knowledge of external types - but if you give it an appropriate database URL and it can locate the appropriate plugin, it will then know which types are possible, so you can use the external type pulldown list to get them right. The JDBC adaptor and EOF 5 is, of course, a whole new learning curve of little fiddly bits, so expect to have to spend a bit of time experimenting and reading up in order to make all your database types work correctly, especially if it is complex. As the community (and the database vendors) discover more about the environment, this stage is becoming easier and easier.

Once your application is built you can run it, of course. Be aware WebObjects 5 uses some bizarre scripts to launch applications, and these are worse than usual on Windows, where the command line has a maximum length, so your classpath may not fit. My personal opinion (MalcolmCleaton) is that this is an extremely ill-conceived method for launching Java applications, and you'd be best off doing away with it as soon as possible, but that's another discussion.

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


text:
WebObjects/Web Applications/Development/Generating Static Pages

Building static sites from WebObjects
During WWDC last week, a member of the audience asked at a WebObjects session how to use WebObjects to create static pages from a dynamic site. Bill Bumgarner fielded the question, as I recall. This is a problem that comes up many times over and over when proposing any sort of dynamic site. At one time, a brief description of how to handle this was included in the Programming Topics of the WebObjects on-line documentation, written by the Apple employee who set up the initial web site for the BBC; that note did not survive the many purges of the web site - which is all to the good, as it was very brief and cursory.

There are many reasons to create static sites from a dynamic engine; the BBC chose to do so out of concerns for the performance of WebObjects. More reasonable justifications are to use html as a portable documentation format; for example, to create a corporate image manual that is distributed on CD. I have been involved with several projects that have done this, including the two mentioned above.

I will entirely skip over how to create dynamic pages from within WebObjects and concentrate on how to create static pages from within a page level component. Dynamic content is a simple problem to solve, and it is done many times over. The problem of creating static pages divides neatly into two: the obvious part is how to write the page to an html file, which is usually referred to as "spidering"; the less obvious part is that all the page links have to somehow be converted from WebObjects dynamic actions into static URLs.

Spidering
First, you need a mechanism to visit every page on your site. I will typically create an action in an administrative application for this purpose. I will have a database structure with a record (an enterprise object) for every page to be written out. This will contain relationships for the children of the page, and for the parent. The top level page can be identified because it has no parent; this structure can also be extended to represent multiple sites, or top level pages, within a single database if required. My page, or node, EO will have a method to write itself out; the file system location to write to will be calculated from the node path to the current node, using a file system name in the EO to label a path.

I use the following method within a component to write out the page, which is called from the spidering code; node is the EO that represents a page or node, and htmlPath is an obvious method.

public void writeToHtml { WOResponse r = null; String c = null; try { File folder = new File(NSPathUtilities.stringByDeletingLastPathComponent(node.htmlPath)); folder.mkdirs; BufferedWriter out = new BufferedWriter(new FileWriter(new File(node.htmlPath))); r = generateResponse; if (r == null) NSLog.debug.appendln("writeHtml: no response"); c = r.contentString; if (c == null) NSLog.debug.appendln("writeHtml: no content"); out.write(c); out.flush; } catch (IOException e) { System.err.println(e.getMessage); } catch (Throwable e) { System.err.println("writeToHtml(" + node.htmlPath + "): " + e.getMessage); }   }

An example implementation for the htmlPath method follows:

public String htmlPath { return NSPathUtilities.stringByAppendingPathComponent(PLUtilities.defaultForKey(null, "docroot", "/Library/WebServer/Documents").toString, urlPath); }   public String urlPath { String fileName = name + primaryKeyString(this) + ".html"; return "/" + NSPathUtilities.stringByAppendingPathComponent(site.name, fileName); }   static public String primaryKeyString(EOEnterpriseObject object) { NSDictionary dict = EOUtilities.primaryKeyForObject(object.editingContext, object); String result = ""; java.util.Enumeration enumerator = dict.objectEnumerator; while (enumerator.hasMoreElements) { Object anObject = enumerator.nextElement; result += anObject.toString; }       return result; }

A more intelligent implementation may be appropriate, to build a path string depending on the node hierarchy, or to take a file name from EO attributes, etc.

Dynamic/Static Links
The only actions that can be used in components for this type of site are WOHyperlinks; these can be converted to static page references, whereas form actions, etc, cannot. The base assumption is that any pages will be served from a file system, not from a web server, and so cgi will not be supported. In theory, you could make use of Javascript for any dynamic needs of the pages.

As it is useful to allow the site to run as a dynamic site as well as the static version, I replace all WOHyperlinks with the following construction (from the wod file):

Generic1: WOGenericContainer { elementName = "a"; href = nodeHref; invokeAction = nodeLink; }

These two methods have to be sensitive to context, and know when to generate a dynamic, WOHyperlink style, link, and when to generate a static link. In my administration application, used for creating and editing the site, I include a flag that is set according to if we are viewing (dynamic) or generating (static) the site.

public String nodeHref { if (sessionViewMode) return context.componentActionURL; return aNode.urlPath; }   public CustomTemplate nodeLink { if (aNode != null) return pageWithName(aNode.name); }

The call to pageWithName takes a component name from the node EO; again, this can be replaced with a more intelligent call that sets values in the component, etc.

The generic container build an  tag with an href that will either contain the appropriate static url, or will act exactly as a WOHyperlink, depending on the value returned by sessionViewMode.


text:
WebObjects/Web Applications/Deployment/Killing WOA Processes

This is one of the most vexing question. How to kill a run away WO application? The ps command does not give you any information as it list the process as java.

Try to use lsof. You need to run it with admin privileges so the command is

sudo lsof -i tcp:xxxx

Alternatively you can have a script:

#!/bin/sh # #  portslay:  kill the task listening on the specified TCP port # kill -9 `lsof -i tcp:$1 | grep LISTEN | awk '{ print $2;}'`

You will also have to do a sudo for the script to run.

For those stuck with the CLOSE_WAIT problems try this: sudo lsof -i tcp:xxxx

Alternatively you can have a script: #!/bin/sh # #  portslay:  kill the task listening on the specified TCP port # kill -9 `lsof -i tcp:$1 | grep CLOSE_WAIT | awk '{ print $2;}'`

run it by doing:

sudo ./portslay xxxx-yyyy

where xxxx is the first port and yyyy the last port

how about (pref. inside a script):

ps aux | grep java | grep  | grep -v grep | awk '{ print"kill -9 "$2 }' | sh

Mike Schrag
I just use

ps auxww

which will show the full commandline. You can see the app name from this view.

Fabian Peters
On FreeBSD one needs to set

kern.ps_arg_cache_limit=1024

in /etc/sysctl to reveal the full command line with ps -auxww. To set it immediately:

sysctl kern.ps_arg_cache_limit=1024

Alternatively, one can use Johan's script below.

Johan Henselmans
I have written a small script that uses lsof to find the process by looking at some specific file that is opened, the returned processes can then be used to kill the process

 * 1) !/bin/sh

if [ $# = 0 ]; then echo "" echo "  usage: $0 javaname(s)" echo "  The current processes that  containt javaname will be displayed" echo "  eg: $0 JavaMonitor.woa" echo "" exit 1 fi

OS=`uname -s` case ${OS} in 'FreeBSD') LSOF=/usr/local/sbin/lsof 'Linux') LSOF=/usr/sbin/lsof 'Darwin') LSOF=/usr/sbin/lsof echo "no lsof command available on this OS!"; exit 1 esac
 * 1) echo $OS

for i in $* do ${LSOF} -c java | grep -i $i | awk '{print $2}' | sort -u; done


text:
WebObjects/Web Applications/Development/Testing and JUnit

WOUnitTest
WOUnitTest is a WebObjects framework for unit testing WebObjects applications. WOUnitTest is based on JUnit. You can download WOUnitTest from SourceForge.

CodeFab has an excellent article on WOUnitTest on their blog.


text:
WebObjects/Web Applications/Deployment/Logging

Mike Kienenberger
I can't remember who gave me the basic starting point for this, but I think it may have been one of the Jonathans. You'll need to change the filenames to make it work under non-unix deployment systems.

You'll find it easier to copy out the source by going into Edit mode first.

public static void main(String argv[]) {       try {           String baseOutputPath = null; //look for a -WOOutputPath argument for (int i=0; i<argv.length; i++) { if ( argv[i].equals("-WOOutputPath") &&                    !argv[i+1].equals("/dev/null")  ) { String outputPath = argv[i+1]; baseOutputPath = outputPath; java.io.File outputFile = new java.io.File(outputPath); if (outputFile.exists) { // move old file out of way to new location, //name based on existing name with an appended timestamp // Format the current time. java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("yyyy-MM-dd_HH:mm:ss"); java.util.GregorianCalendar now = new java.util.GregorianCalendar; String dateSuffix = ".bck-" + formatter.format(now.getTime); System.err.println("new name: " + outputPath + dateSuffix); java.io.File renamedFile = new java.io.File(outputPath + dateSuffix); outputFile.renameTo(renamedFile); }                   break; }           }        }        catch (Throwable e) { //just so any Throwables generated in trying to do this don't           //keep our app from launching. System.err.println("Ignoring: " + e); }       WOApplication.main(argv, Application.class); }


text:
WebObjects/Web Applications/Development/Examples/Page Layout

Here is a trivial example of a page layout WOComponentContent:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">       cxvxcvxcvc   vxcvxcvxcvcxvxcvcxvcxv         And the wod: Title: WOString {   value = title; }; Stylesheet: Stylesheet { };  Body: WOBody { };  Table: WOGenericContainer {   elementName = "table"; border = "0"; cellSpacing = "0"; cellPadding = "0"; class = "FullWidth"; }; Form: WOForm {   multipleSubmit = true; }; Header: Header {   title = ^title; page = ^page; pathComponents = ^pathComponents; }; Footer: Footer { };  Content: WOComponentContent { }; To use this layout, you could do the following in one of your pages: <webobject name = "PageLayout"> <webobject name = "Table">  <webobject name = "Envelopes">   <webobject name = "Table">  <webobject name = "ThreadList">  <webobject name = "DocumentList">  <webobject name = "LinkList">  <webobject name = "RelatedList">

wod file: PageLayout: PageLayout {   title = title; page = name; pathComponents = pathComponents; }; Name: SpanString {   value = value; isBig = true; isBold = true; }; Table: WOGenericContainer {   elementName = "table"; border = "0"; cellSpacing = "0"; cellPadding = "0"; class = "FullWidth"; }; Envelopes: Table {   list = envelopes; keysDescription = "self|recipientNames.iterator.next|sentDate"; headersDescription = "Subject|Recipient|Date"; }; ThreadList: List {   title = "Thread"; list = threads; minimumSize = 0; //maximumSize = 20; }; DocumentList: List {   title = "Attachment"; list = documents; minimumSize = 0; //maximumSize = 20; }; LinkList: List {   title = "Link"; list = links; minimumSize = 0; //maximumSize = 20; }; ListList: List {   title = "List"; list = lists; minimumSize = 0; }; RelatedList: List {   title = "Other"; list = value.relatedNames; minimumSize = 0; };


text:
WebObjects/Web Applications/Development/Examples/List Components

So you have a title and a "list" of objects. The list has a minimum and maximum number of objects to display. If there is "too many" objects there is a "more..." link.

Here is the html:

       more...

And here is the wod:

HasData: WOConditional { condition = hasData; }; Title: SpanString { value = title.toUpperCase; isSmall = true; isBold = true; class = "Label"; }; Items: WORepetition { count = count; index = index; }; Item: SZLink { value = item; description = description; altDescription = altDescription; href = href; pageName = pageName; isSmall = true; class = "Label"; }; HasMore: WOConditional { condition = hasMore; }; MoreCount: SpanString { value = moreCount; }; MoreLink: WOHyperlink { action = displayMore; };


text:
User talk:Venkatesh2007

Hello and welcome to Wikibooks!

Here are some tips to help you get started:
 * You can sign your name on Talk and vote pages using four tildes, like this: ~~~~.
 * Remember to conduct any editing experiments in the sandbox.
 * Check out our help page if you need additional help.
 * Eventually, you might want to read the Manual of Style and Policies and Guidelines.
 * Take a look at Naming policy
 * See list of templates which can save your time
 * Remember to maintain a Neutral point of view.
 * Explore, be bold in editing pages, and have fun!

You will find more resources in Community Portal. If you want to ask a question, visit the Study help desk, the Staff lounge, IRC channel or ask me personally on my talk page.

Good luck! -- LV (Dark Mark) 18:11, 21 May 2006 (UTC)

title: User talk:Venkatesh2007


text:
A-level English/Spies

''Discussion of ironic narratives in ‘Spies’ by Michael Frayn

In the novel, ‘Spies’, written by Michael Frayn, there are a lot of ironic narratives used by the author to give the reader a sense of higher knowledge above the characters. Through the medium of this essay I will be looking at these ironic narratives, focusing on chapter 3.

A lot goes on in this third chapter. The chapter picks-up on the revelation at the end of chapter 2 that Keith, Stephen’s best friend, firmly believes that his mother (Keith’s) is a German spy. We can then find many ironic narratives bound-up in this revelation. Indeed, most of the ironic narratives are found in the way we are transported into Stephen’s mind and presented with his deepest feelings. Stephen and Keith become convinced that Keith’s mother is a German spy. Stephen didn’t think it proper to contend what Keith tells him, or even ask how Keith came about this information. I will look at some examples of this in the next paragraph.You can even say that Steven's friendship resolves around Keith as Keith is seen to be dominant and in charge, this can be shown through. "He was the leader and I was the led."

While the two boys are investigating the German spy, better known as Mrs Hayward; Keith’s mother, they follow her around over the day. When Keith’s mother spots the boys crawling around gathering evidence from her, she points an imaginary gun at them ‘as if we were children’ in Stephen’s words. This is quite ironic as Keith’s mother isn’t actually a German spy and the two boys are children! Another humorous, similar example is when the children find some ‘mysterious’ marks in Keith’s mother’s diary. The boys quickly jump to the conclusion that these marks must be the dates where the ‘spy’ goes for secret rendezvous with her German compatriots. The truth is that the ‘monthly meeting dates’ are actually the dates in which Keith’s mother is on her period. The other marks, occurring at irregular intervals and on Keith’s parents anniversary are the dates in which she makes love to her husband. Stephens thoughts on these dates are particularly humorous, ‘some secret thing, what is it?’ Stephen thinks. That paragraph is particularly ironic.

Another falsity that Stephen believes is Keith’s father’s heroism. Keith’s dad keeps a car in the garage. For reasons relating to Germans hijacking the vehicle, Keith’s dad has removed the wheels and hung them on the wall. Keith is very impressed by his father’s knowledge and tells Stephen how clever he is. Keith also believes that his father is in the secret service. We can find the ironic narratives in this instance because we know the truth. Sadly, the Great War hero that Keith believes his father to be is actually doing his bit for the Duration in the home front or, the ‘Dads Army’. This is typical behaviour for a young boy, as nearly every child sees their father as a higher being, who they aspire to. Some quotes relating to this belief follow in the next paragraph.

Stephen explains that he could have believed that anyone, even Keith’s dad, was a German spy after the ‘truth’ about Keith’s mother was revealed. However, he could not believe that Keith’s dad was a spy as Keith had made him aware of his father’s ‘secret service work and his notable attempts to reduce the German population during the Great War’. This is ironic as there isn’t much truth in Stephens’s interpretation of Keith’s father either. Another ironic quote from Stephen is when he spies a neighbouring child’s tricycle. He is shocked to believe that it is left out. ‘Even the Avery’s’ boys three-wheeler is sitting abandoned in its oil stains - easy prey, with all three wheels still attached, for any passing German troops’. Stephen was inspired to think this, as he knows that Keith’s father keeps a car, minus wheels, in his garage. Keith tells Stephen that the reason for this is so that passing German troops cannot hijack his vehicle.

All in all, we can conclude that Chapter 3 is bursting with ironic narratives and I have looked at and evaluated the main points.'' To conclude spies is a dramatic novel which interweaves many key and apt themes along with reoccurring motifs and extended metaphors

Frayn also creates tension which is reoccurring throughout the entire novel.

title: A-level English/Spies


text:
Suomen kieli ulkomaalaisille/Sisältö/Genetiivi

What is the Genitive case?
The genitive case is used to indicate the ownership of an object to some other person or object.

Its equivalent in English is the use of "'s" (apostrophe s) to indicate ownership, but in Finnish it also extends to an attributive form which in English is not marked with apostrophe s.

Genitive singular case ending
Rule: The genitive singular case ending is -n.

Genitive plural case endings
The genitive plural is a rather complex subject.

The plural is usually formed by adding the plural marker -i- to the singular stem before adding the genitive -en. However, the formation will transform the -i- to -j- if it appears between two vowels, and there are other transformations that often take place.

The common genitive plural endings are -ien -iden -itten -ojen and less often -sten -ten

Do you already understand the genitive plural? You can improve this section by writing the rules for the formation of the genitive plural!

Genitive object attribute
The genitive case is used an attribute of ownership in much the same way as apostrophe s ('s) does for nouns in English.

Tämä auto on Timon
 * This is Timo's car (Literally, this car is Timo's)

Sometimes, however, English will mark ownership with the word "of". In equivalent phrases in Finnish the genitive is used.

Baabelin torni
 * The tower of Babel

Helsingin yliopisto
 * The University of Helsinki

And there are situations where English does not always mark the attribute with an "apostrophe s" or with the word "of", but uses the nominative form. However, Finnish is usually consistent and marks it with the genitive.

Turun Linna
 * Turku Castle (Turku's castle or the Castle of Turku)

Lontoon pörssi
 * The London Stock Exchange (London's Stock Exchange or The Stock Exchange of London)

Personal Suffixes with Nouns and Pronouns
Except in existential phrases, the genitive marker -N is added to the person or the pronoun and the object owned is marked with a personal suffix.

The personal suffixes are as follows

Meidän talomme on jo remontoitu
 * Our house has already been renovated
 * me we becomes meidän our in genitive. Talo house adds -mme as a personal suffix.

Pesin isän auton
 * I washed dad's car

""""""""::Note that isän is the genitive. Car, auto, also has an -n ending but this is not the genitive. It is instead a whole object marker known as the -N accusative.

Pesin hänen autonsa

However, in spoken language, (and especially in Helsinki!) the personal ending is often left out.

Pesin sen auton
 * I washed his car

We have already seen the exceptional existential phrase structure that does not mark the sentence's OBJECT with a personal suffix. These will always have the existential verb olla.

Tämä auto on Timon
 * That is Timo's car (Literally, that is the car Timo's)
 * The phrase is essentially existential (something is something). These types of sentence do not take a personal suffix.

But on the other hand, if the object owned is the SUBJECT of the sentence then the personal suffix must be given.

Minun kelloni on rikki
 * My watch is broken

The personal suffix is always added to the stem but if the word already carries a case marker then the personal suffix is always added after the case ending.

He tulivat meidän talollemme
 * They came to our house
 * House Talo adds case ending lle before personal suffix-mme

Hampaani on kipeä
 * My tooth aches
 * Tooth, hammas has the stem form hampaa- to which the personal siffix -ni is added.

'''But note! The addition of a personal suffix will NOT of itself cause a hard grade consonant to weaken.'''

Meidän kirkkomme
 * Our church
 * the word for church kirkko normally weakens to a stem form kirko- when adding a case ending. However, personal suffix endings DO NOT WEAKEN THE FINAL CONSONANT.

Special Rules for the third person

Rule 1. The third person suffix is the same whether the subject is singular or plural. That is, it applies to situations such as "his", "her" or "their". Whether the meaning is singular or plural is known because the personal genitive will either be singular or plural.

Se on hänen talonsa
 * Its his house

Se on heidän talonsa
 * Its their house

Rule 2. The genitive marker of the person is not used if the person is the person owning is the person of the verb

He menivät pankkiinsa
 * They went to their bank
 * pankkiin + -nsa becomes pankiinsa (- see Rule 3). Heidän pankkiinsa would be incorrect if the bank was the place where "they" do their own banking.

But

'''Kokkoset kulkivat kauppatorilla Erkki ja Marja Suomelan kanssa. Sitten he menivät heidän pankkiinsa'''
 * The Kokkonens walked in the market square with Erkki and Marja Suomela. Then they went to their bank.
 * the use of the word heidän their can only mean the Suomelas' bank, not the Kokkonens' bank.

Rule 3. The third person marker is only -nsa (or -nsä) if the case is nominative singular (unmarked), nominative plural (-T) genitive, illative, or accusative ( -N or -T forms). The -N or -T from these forms is dropped before adding the -NSA. Otherwise the vowel at the end of the case ending is lengthened and just -n is added to make the personal suffix.

Se on heidän talonsa
 * Its their house
 * -nsa is used in the nominative

He kulkivat takaisin talolleen
 * They walked back to their house
 * talolle + -nsa becomes talolleen The meaning is implicitly their own house. If it was someone else's house it would add heidän their

Se ei ole heidän talonsa
 * Its not their house

Hän päätti, että hän ei myy taloaan
 * He decided not to sell his house
 * house is partitive for the same reason as above. So taloaan can be singular as in this example or plural (as in the previous example)

Kuluttajat luottavat lompakkoonsa
 * Consumers trust their (own) wallets
 * the verb luottaa trust requires the illative case (one puts trust INTO something in Finnish). Lompakko wallet is therefore in the illative "lompakkoon". This already has a long vowel ending so we do not use the vowel lengthening form. Instead, like all illative forms with the third person personal suffix, -nsa stays in its -nsa form. We know its third person plural because the verb has the ending -vat indicating plural person. Because the person owning is the same as the person of the verb, we do NOT say heidän lompakkoonsa because this would mean some other group's wallets.

Hänen omat saksensa eivät purreet siihen materiaaliin
 * His own scissors would not cut into the material
 * Sakset scissors is a plural noun as in English. Sakset + -nsa becomes saksensa. The plural t is lost altogether.

Raine on kadottanut lompakkonsa
 * Raine has lost his wallet
 * lompakko (nominative) plus -nsa. Note: No consonant degradation.

Raine ja Anne ovat molemmat kadottaneet lompakkonsa
 * Raine and Anne have both lost their wallets
 * So lompakkonsa can be both plural and singular depending on context. Lompakot (nominative plural) + -nsa becomes lompakkonsa

Hän otti lomapakon
 * He or she took the wallet (but we don't know whose wallet)
 * lompakon is in -N accusative form, which is identical in form to the genitive.

but

Hän otti lompakkonsa
 * He or she took (his or her own) wallet
 * Lompakon + -nsa becomes Lompakkonsa. Notwithstanding that this was originally in the weak case, the addition of the -nsa cause the original -n to be lost and the weakened stem to become string again.

and just to reinforce a point made before

Hän otti hänen lompakkonsa
 * He or She took his or her (i.e. someoneelse's) wallet

Genitive within Compound Words
The genitive form is sometimes seen within compound words. E.g. pyykinpesu pianonsoitonopettaja and talonpoika.

Knowing when to compound words and when to use the genitive in a compound word is quite a complex matter and even many Finns find it hard to understand. You can get an explanation of this at http://www.cs.tut.fi/~jkorpela/kielenopas/6.1.html#yhdyss-merk.

If you understand Finnish and understand the article at the link above and can explain this en English, you may be able to help students reading this wikibook by updating this page with an English translation of elements relating to compound genitive words. Please also write this in the Finnish version of this page in the Finnish Wikibook.

Genitive with Prepositions
Many of the words describing relative place in Finnish such as edessaä in front of, takana behind, vieressä alongside come after the word describing the absolute place. This word is always a noun that is in the genitive case. Words such as veressä are therefore known as prepositions (meaning put after). Equivalent English words such as behind, under, over are known as prepositions. Lets look at a few genitive prepositional phrases.

Poytä on sohvan edessä
 * The table is in front of the sofa
 * So the construction when transliterated is something like the table is of the sofa in front. Because the construction is so different from English this sounds quite strange.

Kirkon vieressä on kirkkotupa 1600-luvulta.
 * Next to the Church is the church hall from the 1600s
 * tupa is cabin or hut but is translated here as hall (a meeting place separate from the Church itself)

Lapsuuteni kesät kuluivat isovanhempieni luona Raisiossa (pieni kaupunki Turun lähellä)
 * My childhood summers were spent at my grandparents place in Raisio (a small town close to Turku)

See the additional article about genitive prepositions.

Exercise One
Genitive with Prepositions

Complete the sentences with the help of the hints that are in brackets.

 * 1) a on kaupunki (close to Rovaniemi).
 * 2) b on kylä (beside Turku).
 * 3) (On the table) on lehtiä ja (under it) on matto.
 * 4) (Inside the box) on pommi. (Inside the bomb) on ruutia.
 * 5) (in the front of the department store) on myyntikoju.
 * 6) (Beside the shop) on kirkko ja (Beside the church) on metsä.

Exercise one

 * 1) a on kaupunki Rovaniemen lähellä.
 * 2) b on kylä Turun vieressä
 * 3) Pöydän päällä on lehtiä ja sen alla on matto. (Pöydän päällä = pöydällä).
 * 4) Laatikon sisällä on pommi. Pommin sisällä on ruutia.
 * 5) Tavaratalon edessä on myyntikoju.
 * 6) Kaupan vieressä on kirkko ja kirkon vieressä on metsä.


text:
WebObjects/Web Applications/Development/Examples/Calendar Component

Here is a little example on how to write a "calendar" component:

So you have a specific month, some of the dates are links (that will depend on what the calendar is for) and some basics navigations (next/previous month, year).

That's how the html could look like:

              <webobject name = "HasNavigation">  <webobject name = "HasPreviousMonth"> <webobject name = "PreviousMonthLink"><webobject name = "PreviousMonth">   <webobject name = "HasNextMonth"> <webobject name = "NextMonthLink"><webobject name = "NextMonth">

And here is the wod:

Table: WOGenericContainer{ elementName = "table"; border = "0"; cellSpacing = "0"; cellPadding = "0"; }; HasNavigation: WOConditional {   condition = hasNavigation; }; YearLink: WOHyperlink{ action = displayYear; }; Year: SpanString{ value = month.year; isBold = true; isSmall = true; class = "Label"; }; Rows: WORepetition{ count = rowCount; index = rowIndex; }; Columns: WORepetition{ count = columnCount; index = columnIndex; }; Month: SpanString{ value = month.monthName.toUpperCase; isBold = true; isSmall = true; class = "Label"; }; DayName: SpanString{ value = dayName; isSmall = true; class = "Label"; }; HasLink: WOConditional{ condition = hasLink; }; HasNoLink: WOConditional{ condition = hasLink; negate = true; }; IsCurrentDay: WOConditional{ condition = isCurrentDay; }; Link: WOHyperlink{ action = displayDay; }; Day: SpanString{ value = day.day; isSmall = true; isBold = isCurrentDay; isItalic = isCurrentDay; class = "Label"; }; HasNextMonth: WOConditional{ condition = hasNextMonth; }; NextMonthLink: WOHyperlink{ action = displayNextMonth; }; NextMonth: SpanString {   value = nextMonthName; isSmall = true; class = "Label"; }; HasPreviousMonth: WOConditional{ condition = hasPreviousMonth; }; PreviousMonthLink: WOHyperlink{ action = displayPreviousMonth; }; PreviousMonth: SpanString{ value = previousMonthName; isSmall = true; class = "Label"; };

The component implementation is left to the imagination


text:
WebObjects/Web Applications/Development/Custom Resource Manager

Petite Abeille
Here is a little example on how to write your own WOResourceManager:

The main methods to implement are urlForResourceNamed and bytesForResourceNamed.

 * urlForResourceNamed simply build an URL for a resource from wherever you would like (e.g. a jar file). Unfortunately, urlForResourceNamed uses pathForResourceNamed so you will need to rewrite that also:

private URL urlForResourceNamed(String aResourceName) {   return this.getClass.getResource( aResourceName ); } public String pathForResourceNamed(String aResourceName, String aFrameworkName, NSArray someLanguages) {   URL  anURL = this.urlForResourceNamed( aResourceName ); if ( anURL != null ) {     return anURL.toExternalForm; }   return null; } public String urlForResourceNamed(String aResourceName, String aFrameworkName, NSArray someLanguages, WORequest aRequest) {   String  anURL = super.urlForResourceNamed( aResourceName, aFrameworkName, someLanguages, aRequest ); this.bytesForResourceNamed( aResourceName, aFrameworkName, someLanguages ); return anURL; }

 * bytesForResourceNamed simply retrieve a resource from wherever you would like (e.g. a jar file):

public InputStream inputStreamForResourceNamed(String aResourceName, String aFrameworkName, NSArray someLanguages) {   return this.getClass.getResourceAsStream( aResourceName ); } public byte[] bytesForResourceNamed(String aResourceName, String aFrameworkName, NSArray someLanguages) {   if ( aResourceName != null ) {     URL  anURL = this.urlForResourceNamed( aResourceName ); if ( anURL != null ) {       String      aKey = anURL.toString; WOURLValuedElementData anElement = (WOURLValuedElementData) _cache.get( aKey ); if ( anElement == null ) {         synchronized( this ) {           InputStream  anInputStream = this.inputStreamForResourceNamed( aResourceName, null, null ); if ( anInputStream != null ) {             try {               InputStream  aBufferStream = new BufferedInputStream( anInputStream ); byte[]   someBytes = new byte[ aBufferStream.available ]; aBufferStream.read( someBytes ); aBufferStream.close; anInputStream.close; {                 Data  someData = new Data( someBytes ); String aType = this.contentTypeForResourceNamed( aKey ); anElement = new WOURLValuedElementData( someData, aType, aKey ); _cache.put( aKey, anElement ); }             }              catch(Exception anException) {               SZLog.warning( anException ); }           }          }        }        return ( (Data) anElement.data ).bytesNoCopy; }     SZLog.debug( "Null url for resource named '" + aResourceName + "'." ); return null; }   throw new IllegalArgumentException ( "ResourceManager.bytesForResourceNamed: null resource name." ); }

 * Last but not least, you need to take care of those funky WOURLValuedElementData so dataForResourceNamed will work:

public NSData dataForResourceNamed(String aResourceName) {   this.bytesForResourceNamed( aResourceName, null, null ); String     aKey = this.urlForResourceNamed( aResourceName ).toString; WOURLValuedElementData anElement = (WOURLValuedElementData) _cache.get( aKey ); return anElement.data; }

 * Finally, you need to register your resource manager with WOApplication:

anApplication.setResourceManager( new ResourceManager );

Handling of languages and frameworks are left as an exercise to the reader.


text:
WebObjects/Web Applications/Development/Examples/Alphabetic List

An alphabetic list is simply a list that display its elements in, er, alphabetic order... There is also some pagination (not shown) when there is too many elements in one batch. The pagination is done alphabetically at runtime if necessary: eg A-D E-Z.

Here is the html:

     |                     <webobject name = "Prefix"> <webobject name = "Suffix"> <webobject name = "HasNoHref"> <webobject name = "Link"><webobject name = "Prefix"> <webobject name = "Suffix"> <webobject name = "HasBatches">  <webobject name = "PreviousLink"><webobject name = "Previous">   <webobject name = "NextLink"><webobject name = "Next">

And the wod:

HasData: WOConditional{ condition = hasData; }; Table: WOGenericContainer{ elementName = "table"; border = "0"; cellSpacing = "3"; cellPadding = "0"; }; ItemDescription: SpanString{ value = itemDescription; isSmall = true; class = "Label"; }; HasBatches: WOConditional{ condition = hasBatches; }; Batches: WORepetition{ count = batchCount; index = batchIndex; }; Batch: SpanString{ value = batchLabel; isItalic = isCurrentBatch; isSmall = true; class = "Label"; }; IsCurrentBatch: WOConditional{ condition = isCurrentBatch; }; IsNotCurrentBatch: WOConditional{ condition = isCurrentBatch; negate = true; }; BatchLink: WOHyperlink{ action = displayBatch; }; Rows: WORepetition{ count = rowCount; index = rowIndex; }; Columns: WORepetition{ count = columnCount; index = columnIndex; }; Prefix: SpanString{ value = prefix; isBold = shouldBreak; class = "Text"; }; Suffix: SpanString{ value = suffix; class = "Text"; }; ShouldBreak: WOConditional{ condition = shouldBreak; }; Link: WOHyperlink{ action = displayItem; title = altDescription; }; HasHref: WOConditional{ condition = hasHref; }; HasNoHref: WOConditional{ condition = hasHref; negate = true; }; HrefLink: WOHyperlink{ href = href; title = altDescription; target = "SZLink"; }; PreviousLink: WOHyperlink{ action = displayPrevious; }; Previous: SpanString{ value = "<PREVIOUS"; isSmall = true; class = "Label"; }; NextLink: WOHyperlink{ action = displayNext; }; Next: SpanString{ value = "NEXT>"; isSmall = true; class = "Label"; };

As usual, the component implementation is left to the imagination of the reader.


text:
WebObjects/Web Applications/Development/Examples/Path Inspector

First of, it's handy to add a convenience method to your "page" component to define this "list" of objects:

protected void addObjectToPathComponents(Object anObject, String aPageName, Object aTitle) {   if ( anObject != null ) {     Map  aMap = new HashMap; aMap.put( Path.ObjectKey, anObject ); aMap.put( Path.PageNameKey, aPageName ); aMap.put( Path.TitleKey, aTitle ); this.pathComponents.add( aMap ); return; }   throw new IllegalArgumentException ( "Page.addObjectWithPageNameAndTitleToPath: null object."); }

Now, in each concrete page you could initialize the path components:

protected void initPathComponents {   Envelope  anEnvelope = this.value; Date   aDate = anEnvelope.creationDate; List   aList = anEnvelope.list; this.addObjectToPathComponents( aDate, "Timeline", "TIMELINE" ); this.addObjectToPathComponents( aDate ); if ( aList != null ) {     this.addObjectToPathComponents( aList ); }   this.addObjectToPathComponents( anEnvelope ); }

Finally, the path component itself could look like this:

Components: WORepetition {   count = count; index = index; }; IsMain: WOConditional{ condition = isMain; }; IsNotMain: WOConditional{ condition = isMain; negate = true; }; ainLink: WOHyperlink{ action = getMail; title = "get new mail"; }; MainLabel: SpanString{ value = "get mail"; isSmall = true; isUpperCase = true; //isBold = true; class = "Label"; length = 30; }; HasPrefix: WOConditional{ condition = hasPrefix; }; Prefix: WOImage{ src = prefixUrl; border = 0; align = "middle"; }; Component: Link{ value = component.object; description = component.title; altDescription = component.title; value = component.object; pageName = component.pageName; isUpperCase = true; isSmall = true; //isBold = true; class = "Label"; }; LastComponent: SpanString{ value = lastComponentTitle; isSmall = true; isUpperCase = true; //isBold = true; class = "Label"; length = 30; };

That's all folks.


text:
WebObjects/Web Applications/Development/Frameworks

Overview
A Framework is Apple's mechanism for packaging and distributing shared functionality. Frameworks can contain not only code, but also Resources and Web Server Resources much like a full-blown WOA.

Images deployed in a Framework

If a framework contains images, accessed from your main application, you must remember to change the framework binding in some of your WOComponents. The failure to do so will become evident in development. The default is "app", but you must change it to the name of your framework. When it comes time to deploy, you application frameworks must be included on the deployment machine. Images do not seem to display properly if you only put your frameworks in this location: /Library/Frameworks. You'll also want to put your webserver resources from your framework in the /WebServer/Documents/WebObjects/Frameworks folder. I just included the whole .framework folder in that directory and the images were served from the webserver properly. All the above paths are for MacOS X.

Framework Initialization
''I have a framework that I would like to do some explicit initialization on. Something akin to doing a class static initializer in Java. Where is the proper place to place such code? Or can I just use a class static initializer?''

Gary Teter
Add a text file called CustomInfo.plist to your framework's resources. Add this text to the file:

{   NSPrincipalClass = MyFrameworkInitializer; }

Create a java file, MyFrameworkInitializer.java and initialize any classes in your framework in a static block in this class. Of course you can call the class whatever you want, just make sure you put the full class name, package and all in the .plist file.

This is a hangover from NeXT days and may stop working if frameworks ever go away and WO switches to NORMAL java deployment. Basically the framework loader will reference every class declared as NSPrincipalClass? in this way. The neat part is your framework get's initialized w/o having to remember to do it yourself in th app constructor.

Arturo Pérez
I could not get things to work as described above. The problems:

 * The Info.plist generated does not contain that NSPrincipalClass? reference and apparently nothing happens. If you do it as described, you actually end up with a CustomInfo.plist. I had expected the CustomInfo.plist to be merged into the Info.plist. I found that if you do want it merged into the Info.plist then you need to "Project-Edit Active Target" then under the Info.plist twistee there's an entry called "Cocoa Specific". There you find the entry for "Principal Class" and you put the initializer class there.

 * The initializer class is called _way_ early. I expected it to work like the database connections; i.e. after the application had been initialized. In fact, in my case, the framework is initialized right after the main. If you have the run log opened the initialization happens when the first is printed after the classpath list is printed. So, I came to believe it wasn't happening because I was looking for the initialization much later.

HTH the next intrepid developer. BTW, the Xcode documentation for this is only tangentially useful to a WO developer. The panels described don't actually exist when you're doing a WO framework. The information described in terms of the necessary entries helps, though.

King Chung Huang
Tip: I often have my Framework initializers create a worker object to listen for ApplicationWillLaunchNotification or ApplicationDidLaunchNotification. if I need to do initialization just before or after the WOApplication instance is constructed.


text:
WebObjects/Web Applications/Deployment/Linux

Overview
(rev 1.5; 2003-03-02, see full revision list) The latest version of this document can be found at http://www.tetlabors.de/wo/setup_webobjects_on_linux.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 describes how to setup WebObjects deployment for Linux in about two hours - if you know what you are doing. Otherwise, it might take a little bit longer. :-) It now covers installation of WebObjects 5.2 as well (see revision list at the end of this how-to for changes).

This how-to is based on a German description on how to setup WebObjects under Linux by Timo Buhmann. Translation and reformatting into this HTML document was done by me with suggestions from the WebObjects mailing lists at OmniGroup and an article about Linux and WO by Jonathan Wolf Rentzsch.

I am no guru when it comes to Linux - I have used it for about two days before writing this tutorial. Luckily, there were enough people around here I could ask all kinds of questions when I got stuck. If you see strange things or clumsy stuff in this how-to, please mail me and kindly send along corrections or improvements. Maybe Apple integrates Linux support in the next release of WebObjects so you won't not need this how-to any longer. In the meantime: let's hack!

Before you start
To minimize delays during the process, you might want to put together all the stuff you need _before_ you start. For this how-to the following software was used: I am not sure if installing just the JRE for Linux is enough. It definitely does not work for Windows deployment, so I decided to choose the (somewhat bigger) SDK for Linux, too. a working deployment license key; for WebObjects 5.1 you can use a license key from either a WebObjects 5.0 or 5.1 installation IMPORTANT: For WebObjects 5.2 you need a key specifically made that version! License keys for older versions seem to work at first but later on you cannot run any applications. :-( changes to the makefiles that are discussed in step 6 can be downloaded here, if you do not want to apply them yourself. Inside the zipfile there are different folders for WebObjects 5.1 and 5.2.
 * a Linux distribution. For our actual installation of WebObjects 5.2, we used SuSE 8.0 Professional now.
 * Apache 1.3.x: http://httpd.apache.org (the new version was done with version 1.3.27)
 * Java2 SDK: http://java.sun.com/j2se/1.4.1/download.html (J2SDK 1.4.1_02, standard RPM package)
 * WebObjects 5.1 or 5.2 CD for Solaris / Windows (get it at http://store.apple.com)
 * WebObjects updates:
 * WebObjects 5.1 Update 3 (a compressed tarfile and a shell script) for Solaris: http://docs.info.apple.com/article.html?artnum=75277; the complete patch list lives at http://docs.info.apple.com/article.html?artnum=70037
 * There is no update for WebObjects 5.2 yet, so I took the update part out for now.

In your Linux installation, the following packages should installed (the letter at the beginning of each line lists the appropriate package series for a SuSE 7.1 installation, this may be different for other Linux distributions):

 * a: compat (libs needed to run Java)
 * ap: mc (Midnight Commander, not _really_ needed :)
 * d: gcc (compiler)
 * d: gdbm-devel (libs needed to compile the Apache sources)
 * d: linclude (needed by gcc)
 * d: make (make command)
 * n: ftp (ftp client to get files from other systems, way faster that sftp)
 * n: openssh (ssh server and client tools, when you want to deploy applications later on)
 * n: smbclnt (to mount shares from Windows boxes)

Here is what you _do not_ need, un-select this before installing (or uninstall these packages if you already have a running system):

 * n: apache (you are going to make this yourself).
 * d: java (JDK 1.1.8)
 * d: javarunt (Java Runtime 1.1.8)

A good place to look for packages is http://rpmfind.net. Let package dependencies be resolved by the installer automatically and you are on the way...

After Linux installation is complete, create the following folders; we later use them to drop the installation packages in and remove them when we are done: mkdir -p /opt/install/apache mkdir -p /opt/install/java mkdir -p /opt/install/woupdate

Install Apache
As Apache version 2.x will not work with the WebObjects adaptor, you have to use Apache version 1.3.x.

Important Around June 20th, 2002, a security hole in Apache became known that allows attackers to execute code on the server or maybe get even root access. As all versions up to 1.3.24 are affected, you should use at least version 1.3.26, which fixes this security hole.

Put the downloaded apache sources (apache_1.3.27.tar.gz) in /opt/install/apache. Then untar: cd /opt/install/apache tar xfvz apache_1.3.27.tar.gz cd apache_1.3.27 Configure, build and install Apache. The gdbm-devel package must be installed for this to work. ./configure --prefix=/usr/local/apache --enable-module=so --enable-module=rewrite --enable-shared=max --enable-rule=SHARED_CORE

make clean; make; make install Make starting Apache a bit easier: ln -s /usr/local/apache/bin/apachectl /usr/bin/apachectl Try if it works: apachectl start Now open http:// in your browser. If you see the Apache default page, everything is fine.

Important In its default installation (as explained in this how-to), the system should be considered unsecure and not connected to the Internet without some kind of protection! As I know next to nothing about Linux/Apache security I strongly suggest that you consult some security how-to on how to make you system more secure.

Install Java2 SDK 1.4
Put the downloaded SDK (j2sdk-1_4_0_01-linux-i586-rpm.bin) to /opt/install/java cd /opt/install/java chmod a+x j2sdk-1_4_1_02-linux-i586-rpm.bin ./j2sdk-1_4_1_02-linux-i586-rpm.bin Read the license agreement and enter yes at the end. The script will then unpack the RPM package, so you can now install it: rpm -i -vv j2sdk-1_4_1_02-fcs-linux-i386.rpm ln -s /usr/java/j2sdk1.4.1_02/bin/java /usr/bin/java To see if it works, try this: java -version The output should look similar to this: java version "1.4.1_02" Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_02-b06) Java HotSpot(TM) Client VM (build 1.4.1_02-b06, mixed mode)

Install WebObjects 5.1 Deployment for Solaris
Mount the WebObjects 5.1 installation disc: mount -t iso9660 -o nojoliet -o norock -o map=o /dev/cdrom /cdrom cd /cdrom ls Important:  As the install script is case sensitive, files and folders on the CD have to contain normal and capital letters. If you see everything in /cdrom being displayed in either lowercase or uppercase but nothing mixed, it did not work. :-(

To get around this, you can copy the contents of the CD to a shared folder on a Windows box and read it from there. Provided that the Windows box has the adress 192.168.1.1 and the share name is wocd, you can reach it like this: mount -t smbfs //192.168.1.1/wocd /cdrom -o username=smb,password=smb In this example, you will need a user smb with its password set to smb on the Windows box. If you activate the Guest account on Windows (not recommended), you can omit the username/password for the mount command.

Start the install script and follow the instructions: cd /cdrom/Deployment/SOLARIS ./install.sh For batch setup, you can use the following: cd /cdrom/Deployment/SOLARIS ./install.sh -license B-111-AAA-111-AAA-111-AAA-111-AAA-111 -adaptorsOnly NO -minimalInstall NO -cgibin /usr/local/apache/cgi-bin -docroot /usr/local/apache/htdocs -woroot /opt/Apple First, be sure to cd to /cdrom/Deployment/SOLARIS _before_ starting the installation or the license key check may fail. Second, instead of B-111-AAA-111-AAA-111-AAA-111-AAA-111 you should provide your real license key. :-)

Follow the instructions on the screen and wait for the installation to finish.

In case of an error, try the following: ln -s /usr/bin/uncompress /usr/bin/compress Now, create /etc/profile.local if it does not exist yet: touch /etc/profile.local To export NEXT_ROOT at each startup, add the following lines to /etc/profile.local:

NEXT_ROOT=/opt/Apple export NEXT_ROOT

Now you should log off and log in again to make NEXT_ROOT known. You can also export NEXT_ROOT for your current session manually: export NEXT_ROOT=/opt/Apple To see if it works, enter this: echo $NEXT_ROOT If /opt/Apple is echoed back, everything's fine. :-)

Install WebObjects 5.1 update 3
Skip this step for WebObjects 5.2 as there is no update available yet.

Before starting the update, mod_WebObjects has to be unloaded from the web server and all WebObjects related processes have to stop. It should not matter in the case of this how-to since we never used WebObjects so far, but to be on the safe side, enter the following: apachectl stop

cd $NEXT_ROOT/Library/WebObjects/Executables ./WOServices stop If you have standalone (EOF/WO) apps running, kill them, too. Furthermore, gnutar has to be available for the patch script. If it is not, you can create a link: ln -s /bin/tar /bin/gnutar To install the update, you have to download two files: the update itself (WO51SolarisUpdate3.TAR.Z) and the patch script (patcher.sh). Put these two in /opt/install/woupdate and then start the update: cd /opt/install/woupdate cdsfdsfs chmod a+x patcher.sh ./patcher.sh -install WO51SolarisUpdate3.TAR.Z  cd $NEXT_ROOT/Library/WebObjects/WODocumentRoot cp -R WebObjects /usr/local/apache/htdocs/WebObjects

Edit $NEXT_ROOT/Library/Frameworks/JavaWebObjects.framework/Resources/WebServerConfig.plist :

line 2: change /Library/WebServer/Documents to /usr/local/apache/htdocs

Now comes the fun part: Compiling the adaptor!

Compile the Apache adaptor for Linux
As the makefiles do not ship with Linux support - is there any sound reason for this, by the way? - you have to add support yourself. Luckily the adaptor sources come with WebObjects.

All of the previous solutions I found broke the script in some way. For example, it was not possible to run the script on Solaris without further modifications. Here we will do a 'clean' solution that adds support for Linux without breaking it for the other platforms.

First, export the OS variable, so the script knows we are on Linux: export OS=LINUX We do need this only while compiling, it is not necessary to put it into /etc/profile.local (but you can do this if you want to). Next, open the file $NEXT_ROOT/Developer/Examples/WebObjects/Source/Adaptors/make.config with an editor of your choice and insert (copy and paste) the following lines right before the MacOS X Server entry (around line 17):

# LINUX ifeq "LINUX" "$(OS)" ADAPTORS = CGI Apache # Default path for apxs APXS = /usr/local/apache/bin/apxs endif

Save and exit.

The next part is a bit tricky (for me) because here WebObjects 5.1 and 5.2 differ.

Open $NEXT_ROOT/Developer/Examples/WebObjects/Source/Adaptors/Apache/Makefile and copy-and-paste the following lines after the last endif (around line 66):

ifeq "LINUX" "$(OS)" # If Apache is in a nonstandard location, change this APACHEINCLUDEFLAGS = -I/usr/local/apache/include CFLAGS = -O2 -Wall -I../Adaptor ${LDAPACHESSLFLAG} $(DEBUG_FLAG) -DSINGLE_THREADED_ADAPTOR -D$(OS) -DEAPI -DFORKING_WEBSERVER -DAPACHE ${OPENSSLINCLUDEFLAGS} ${APACHEINCLUDEFLAGS} ${OPENSSLLIBFLAGS} -L/lib APXSFLAGS = -i LDFLAGS += -G -L/lib -lc ${OPENSSLLIBFLAGS} all: adaptor adaptor: mod_WebObjects.so ○${APXS} ${APXSFLAGS} mod_WebObjects.so   mod_WebObjects.so : mod_WebObjects.o ${COMMON_OBJFILES} # ${APXS} ${APXSFLAGS} -c mod_WebObjects.c ${COMMON_OBJFILES} # ${APXS} ${APXSFLAGS} mod_WebObjects.o ${COMMON_OBJFILES} ○ld ${LDFLAGS} mod_WebObjects.o ${COMMON_OBJFILES} -o mod_WebObjects.so clean: ○rm -f mod_WebObjects.so mod_WebObjects.o *.o #mod_WebObjects.o: mod_WebObjects.c  # ${APXS} ${APXSFLAGS} -c mod_WebObjects.c  mod_WebObjects.o: mod_WebObjects.c ${COMMON_OBJFILES} ○${CC} -c ${CFLAGS} mod_WebObjects.c ${COMMON_OBJFILES} endif

Note: These funny looking circles (like at the beginning of: ○${APXS} ${APXSFLAGS} mod_WebObjects.so) are no typing errors but the TAB (tabulator) key. The make process needs them to complete successfully, so do not forget them!

  Open $NEXT_ROOT/Developer/Examples/WebObjects/Source/Adaptors/Apache/Makefile and cut it around line 29, that means after the ifeq "SOLARIS" "${OS}" ... endif block, delete all following lines until the end of the file. Then append the lines below to the end of the file so it looks like this:

ifeq "LINUX" "${OS}" ENABLE_SSL_SUPPORT = OPENSSL_INCL_FLAGS = OPENSSL_LIB_FLAGS = APACHE_INCL_FLAGS = -I/usr/local/apache/include CFLAGS +=-DEAPI ${APACHE_INCL_FLAGS} LDFLAGS += -G -L/lib -lc ${OPENSSL_LIB_FLAGS} endif CFLAGS += ${OPENSSL_INCL_FLAGS} APXSFLAGS = -i all: mod_WebObjects.so mod_WebObjects.so : mod_WebObjects.o ${COMMON_OBJFILES} ○ld ${LDFLAGS} mod_WebObjects.o ${COMMON_OBJFILES} -o mod_WebObjects.so ○${APXS} ${APXSFLAGS} mod_WebObjects.so  mod_WebObjects.o : mod_WebObjects.c ${COMMON_OBJFILES} ○${CC} -c ${CFLAGS} mod_WebObjects.c ${COMMON_OBJFILES} clean: ○rm -f mod_WebObjects.so mod_WebObjects.o *.o include ../Adaptor/make.postamble

From here on it is the same again for both versions.

The last file to change is $NEXT_ROOT/Developer/Examples/WebObjects/Source/Adaptors/Adaptor/appcfg.c. Search for the following three lines, starting around line 52:

#ifndef MAXPATHLEN #define MAXPATHLEN 255 #endif

Cut-and-paste these three lines _after_ the following lines (around line 72), like so:

#include  /* inet_addr */ #include  #endif #ifndef MAXPATHLEN #define MAXPATHLEN 255 #endif

After applying these changes, do the following:

cd $NEXT_ROOT/Developer/Examples/WebObjects/Source/Adaptors make clean; make During the compilation process, mod_WebObjects.so is automatically placed into /usr/local/apache/libexec, there is no need to do this by hand. If you find mod_WebObjects.so there, compilation was successful. The CGI adaptor has been built, too and can be found at $NEXT_ROOT/Developer/Examples/WebObjects/Source/Adaptors/CGI/WebObjects, if you want to play around with that.

Gratulations, you are almost done!

Configure Apache for WebObjects
Copy the configuration file for WebObjects to the Apache conf folder: cd $NEXT_ROOT/Developer/Examples/WebObjects/Source/Adaptors/Apache cp apache.conf /usr/local/apache/conf/webobjects.conf Edit /usr/local/apache/conf/httpd.conf. Search for the line "LoadModule rewrite_module ..." and insert the following lines right _before_ it:

Include /usr/local/apache/conf/webobjects.conf  SetHandler WebObjects

Search for the line "AddModule mod_rewrite.c" and put the following line right _after_ that:

AddModule mod_WebObjects.c

Search for a line "ServerName ..." and put your DNS host name in there. If you do not have one, you can use your IP adress, too. The explanation within httpd.conf about this is very good.

ServerName yourhost.yourdomain.com

Now edit /usr/local/apache/conf/webobjects.conf:

line 4: replace SYSTEM_LIBRARY_DIR/WebObjects/Adaptors/Apache/mod_WebObjects.so with /usr/local/apache/libexec/mod_WebObjects.so line 5: put a # in front of AddModule mod_WebObjects.c to comment it out line 9: replace LOCAL_LIBRARY_DIR/WebServer/Documents with /usr/local/apache/htdocs

Optionally, you can change the following:

line 13: /cgi-bin/WebObjects (can be changed to /myFunkyAlias/WebObjects) line 28: http://localhost:1085 10 (can be changed to http://host1:1085,http://host2:1085 10, don't put spaces after the comma sign when adding multiple hosts!)

Save and exit. Now, to see if everything is right, try: apachectl configtest If it says "Syntax OK", you are fine. Else, try to find and correct the errors it reports.

Start Apache und WO Services
Starting Apache is easy: apachectl start Start wotaskd: $NEXT_ROOT/Library/WebObjects/JavaApplications/wotaskd.woa/wotaskd & When it is still running in the foreground after a while, you can press CTRL+C to get back to the shell; wotaskd then continues running in the background.

Start JavaMonitor and test the installation
The last step before adding application is starting JavaMonitor and see if it works: $NEXT_ROOT/Library/WebObjects/JavaApplications/JavaMonitor.woa/JavaMonitor -WOPort 56789

After JavaMonitor is launched, you will see a line similar to the following:

Your application's URL is: http://yourhost.yourdomain.com:56789/

Open this URL in a web browser.

Cleaning up
Remember those installation folders you created in step 1? Now you can safely delete them unless you want to install again: rm -rf /opt/install/apache rm -rf /opt/install/java rm -rf /opt/install/woupdate Important: Be absolutely sure you type the path names in correctly; rm -rf takes no prisoners...

Troubleshooting
Q: When I try to compile the adaptor sources, I get the following error and compilation stops: In file included from ../Adaptor/appcfg.c:64: /usr/include/unistd.h:734: conflicting types for `gethostname' ../Adaptor/appcfg.c:57: previous declaration of `gethostname' make[1]: *** [appcfg.o] Error 1 make: *** [CGI] Error 2 What does this mean?

A: You have forgotten to export the OS variable. Do export OS=LINUX before compiling.

Q: When I try to compile the adaptor sources, I get the following error and compilation stops: Makefile:79: *** missing separator. Stop. make: *** [Apache] Error 2

A: On the line the error message specifies (79 in this example), add a TAB character at the beginning. Just adding several 'space' characters will not help. TAB characters (ASCII character no. 9) can be entered this way:

 * Press and hold the left ALT key on the keyboard
 * Hit the '9' key on the numerical keyblock; 'Numlock' must be on.
 * Release the ALT key.

Q: During compilation, a lot of warnings are displayed. Do I have to worry about that?

A: There are two different things displayed:

mod_WebObjects.c:242: warning: `setOption3' defined but not used

You can probably comment out this method when compiling under Linux but I am not sure if you may need this on Solaris or OSX. My goal was to make the sources compatible on every platform.

cc: .o: linker input file unused since linking not done

Probably linking works different or is not needed on Linux. If you know which line to tweak in the makefile, please send me the changes and I will update the how-to.

Q: I cannot connect to an application when it is run from the command line, i.e. not via Monitor, what can I do?

A: Just add -WODirectConnectEnabled YES as an argument when starting the application and then connect via http://host:port.

- no more troubles to shoot so far, waiting for new questions to arise :-)

Revision list
1.5: 2003-03-02 - added support for WebObjects 5.2 installation as well - updated Apache and Java SDK version numbers and hyperlinks

1.4: 2002-08-01 - cloaked most "mailto:" links to make it more difficult for spammers (should have done this right from the start)

1.3: 2002-06-30 - added two questions to the troubleshooting section - added a warning for the Apache security hole and updated the how-to for Apache 1.3.26 - added security warning at the end of step 2 - added 'how to create a tab on the keyboard' in the troubleshooting section

1.2: 2002-06-18 - added changes to appcfg.c to avoid a compiler warning

1.1: 2002-06-14 - added some troubleshooting stuff - updated relevant parts of the how-to to use Update 3 for WebObjects instead of Update 2

1.0: 2002-06-09 - first (working) release

0.9: 2002-06-08 - first release, waiting to pass self-test


text:
WebObjects/Web Applications/Development/Examples/Login

Simple Login
Here is a little example on how to redirect a request to a "login" page if necessary:

public void appendToResponse(WOResponse aResponse, WOContext aContext) {   String	aPageName = LoginPage.pageWithContext( aContext ); if ( ( aPageName != null ) && ( this.name.equals( aPageName ) == false ) ) {     WOComponent	aComponent = this.pageWithName( aPageName ); this.currentSession.setPage( aPageName ); aContext._setPageComponent( aComponent ); aContext._setCurrentComponent( aComponent ); aComponent.appendToResponse( aResponse, aContext); return; }   this.currentSession.setPage( this.name ); super.appendToResponse( aResponse, aContext); }

WOContext._setPageComponent and WOContext._setCurrentComponent are undocumented methods, but are required in order to update the WOContext with the current page and component information.

The LoginPage class (not shown) will decide if a login is necessary or not.

Avoiding Session Timeouts at Login
A good login should not timeout when no user is logged in. This can be a problem if your application uses a session in the page generation.

Fortunately Apple has provided us with a way of doing this correctly: "DirectAction";. You should implement your login page as a DirectAction and be very careful not to create a "lazy" session otherwise you will have worked for nothing. WO create a session when you call certain methods so you have to be careful. Use this.context.hasSession to check that everything is OK. After successful login you create a session by calling this.session in your component and store the user in it; you are in business.

On logout you destroy the session and return the direct action page that way the user has a new login screen instead of an empty screen. The easiest way of generating a login page on logout is to return a redirect page (302) to the main URL of the application it is transparent to the user and does exactly what you need.

You will find the code sample there after. I place it in the Application and when I want to logout I just call handleLogout with the context.

private WOResponse responseForPageWithName(String aPageName, WOContext aContext) { if ( aPageName != null ) { WOResponse	aResponse = new WOResponse; String adaptorPrefix = aContext.request.adaptorPrefix; String applicationName = aContext.request.applicationName; String anURL = adaptorPrefix + "/" + applicationName + ".woa"; aResponse.setHeader(anURL, "Location"); aResponse.setHeader("text/html", "content-type"); aResponse.setHeader("0","content-length"); aResponse.setStatus(302); return aResponse; }   return null; } public WOResponse handleLogout(WOContext aContext) { WOResponse aResponse = this.responseForPageWithName("Login", aContext); if ( ! aContext.session.isTerminating ) { ((Session)aContext.session).setUser(null); aContext.session.terminate; }   return aResponse; }


text:
WebObjects/Web Applications/Development/Examples/Anchors

Put the following in the superclass for your components: private String anchor; public void appendToResponse(WOResponse response, WOContext context) { if (anchor != null) { response.setHeader(context.componentActionURL + "#" + anchor, "location"); response.setHeader("text/html", "content-type"); response.setHeader("0", "content-length"); response.setStatus(302); anchor = null; } else { super.appendToResponse(response, context); }   } // appendToResponse

public String getAnchor { return anchor; }   public void setAnchor(String s) { anchor = s;   }

In the component where we want to use an anchor you just put in

and something like the following in the code where we want to jump to this anchor:

protected WOComponent doSometingAndJumpToAnchor { // do something :)     setAnchor("myanchor");      return null;  }


text:
WebObjects/EOF/Using EOF/Delegates and Notifications

Pierre Bernard
To invalidate cached versions of derived values, you might want to use this:

/** Utility class. Provides for a way to watch for changes in EOs. The main use of this would be to   * be able to safely keep cached/computed/derived values that get cleared once out of sync. *    * @author bernard * @version ChangeNotificationCenter.java,v 1.1 2003/03/11 16:26:59 bernard Exp **/ public class ChangeNotificationCenter {   // Public class constants /** Name of the posted notification */   public static final String OBJECT_CHANGED = "MBSObjectChanged"; /** Possible type of change a notification is posted for */   public static final String INVALIDATION = "Invalidation"; /** Possible type of change a notification is posted for */   public static final String DELETION = "Deletion"; /** Possible type of change a notification is posted for */   public static final String UPDATE = "Update"; // Private class constants /** Key in the posted notification's userInfo dictionary. */   private static final String OBJECT = "MBSObject"; /** Key in the posted notification's userInfo dictionary. */   private static final String TYPE = "MBSType"; /** Selector used for calling the watcher object upon a change */   private static final NSSelector watchedObjectChangedSelector = new NSSelector("watchedObjectChanged", new Class[] { NSNotification.class }); // Private class variables /** Reference holding the shared singleton instance. */   private static ChangeNotificationCenter defaultCenter = null; // Constructor /** Singleton class */   private ChangeNotificationCenter {     super; Class[] notificationArray = new Class[] { NSNotification.class }; NSSelector objectsChangedIInEditingContextSelector = new NSSelector("objectsChangedInEditingContext", notificationArray); NSNotificationCenter.defaultCenter.addObserver(       this,        objectsChangedIInEditingContextSelector,        EOEditingContext.ObjectsChangedInEditingContextNotification,        null); }   // Public instance methods /** Method called when a change notification is received. *     * The notification is split and redispatched for all updated objects. */   public void objectsChangedInEditingContext(NSNotification notification) {     NSArray updated = (NSArray) notification.userInfo.objectForKey(EOObjectStore.UpdatedKey); if (updated != null) {       int count = updated.count; for (int i = 0; i < count; i++) {         Object object = updated.objectAtIndex(i); NSDictionary userInfo = new NSDictionary(new Object[] { object, UPDATE }, new Object[] { OBJECT, TYPE }); NSNotificationCenter.defaultCenter.postNotification(OBJECT_CHANGED, object, userInfo); }     }      NSArray invalidated = (NSArray) notification.userInfo.objectForKey(EOObjectStore.InvalidatedKey); if (invalidated != null) {       int count = invalidated.count; for (int i = 0; i < count; i++) {         Object object = invalidated.objectAtIndex(i); NSDictionary userInfo = new NSDictionary(new Object[] { object, INVALIDATION }, new Object[] { OBJECT, TYPE }); NSNotificationCenter.defaultCenter.postNotification(OBJECT_CHANGED, object, userInfo); }     }      NSArray deleted = (NSArray) notification.userInfo.objectForKey(EOObjectStore.DeletedKey); if (deleted != null) {       int count = deleted.count; for (int i = 0; i < count; i++) {         Object object = deleted.objectAtIndex(i); NSDictionary userInfo = new NSDictionary(new Object[] { object, DELETION }, new Object[] { OBJECT, TYPE }); NSNotificationCenter.defaultCenter.postNotification(OBJECT_CHANGED, object, userInfo); }     }    }    /** Method to be called when one creates a cached value that depends on the attributes of a given object. *     * Upon registration the object is watched and the watchedObjectChanged is called once it changes, thus providing an     * opportunity to invalidate the cached value. *     * @param watcher the watching object which should unregister before disposing * @param object the object to watch * @see #unregisterCacheDependancy * @see #unregisterCacheDependancies */   public void registerCacheDependancy(ObserverInterface watcher, EOEnterpriseObject object) {     NSNotificationCenter.defaultCenter.addObserver(watcher, watchedObjectChangedSelector, OBJECT_CHANGED, object); }   /** Method to be called when one abandons or clears a cached value that depended on attributes of a given object. *     * @param watcher the watching object * @param object the object to watch * @see #registerCacheDependancy */   public void unregisterCacheDependancy(ObserverInterface watcher, Object object) {     NSNotificationCenter.defaultCenter.removeObserver(watcher, OBJECT_CHANGED, object); }   /** Unregisters all of the callers dependancies. To be used sparingly as it may break unknown but required * dependancies. Usually called when disposing the calling object. *     * @param watcher the watching object * @see #registerCacheDependancy * @see #unregisterCacheDependancy */   public void unregisterCacheDependancies(ObserverInterface watcher) {     NSNotificationCenter.defaultCenter.removeObserver(watcher, OBJECT_CHANGED, null); }   // Public class methods /** Gets or lazily instantiates the shared instance of the ChangeNotificationCenter *     * @return the unique instance */   public static ChangeNotificationCenter defaultCenter {     if (defaultCenter == null) {       createDefaultCenter; }     return defaultCenter; }   /** Utility method. Allows for retrieving the changed object from a notification * that results of registering interest in a change. *     * @param notification the received notification * @return the object that triggered the notification */   public static EOEnterpriseObject objectFromNotification(NSNotification notification) {     return (EOEnterpriseObject) notification.userInfo.objectForKey(OBJECT); }   /** Utility method. Allows for retrieving the type of change that occurred from a notification * that results of registering interest in a change. *     * @param notification the received notification * @return the type as one of the class constants defined in ChangeNotificationCenter */   public static String typeFromNotification(NSNotification notification) {     return (String) notification.userInfo.objectForKey(TYPE); }   // Private class methods /** Creates the singleton instance ensuring there is no existing instance. */   private static synchronized void createDefaultCenter {     if (defaultCenter == null) {       defaultCenter = new ChangeNotificationCenter; }   }    // Inner interface definition /** Interface to be implemented by objects that need to listen to changes. *     * CAVEAT: in most cases it is recommended to not directly implement the interface, * but rather create an inner class that implements the interface or better yet * extends the default implementation. Indeed if an object (e.g. an EO) which * participates in an inheritance hierarchy is used as receiver for notifications, * registering or unregistering might break functionality in a parent class. */   public static interface ObserverInterface {     /** Method called when an object on which locally cached values depend is modified. *       * This hook is provided as an opportunity to clear locally cached values. It's a good idea not to recreate * the cached values immediately, but on an as-needed basis. You should refrain from changing persisted EO      * attributes from within this method as this might kick off another chain of notifications. *       * Once the caches cleared, it would be a very good idea to unregister from further notifications until * the cache is recreated. *       * @see lu.bcl.enterprise.entity.ChangeNotificationCenter#objectFromNotification * @see lu.bcl.enterprise.entity.ChangeNotificationCenter#registerCacheDependancy * @see lu.bcl.enterprise.entity.ChangeNotificationCenter#unRegisterCacheDependancy */     public void watchedObjectChanged(NSNotification notification); }   // Inner class /** Convenience class for implementing ObserverInterface. *     * The recommended way of registering for change notificications is to create an inner * class extending this one. */   public abstract static class Observer implements ObserverInterface {     /** Method to be called by subclasses when the create a cached value that depends on the attributes of a given object. *       * Upon registration the object is watched and the clearCachedValues is called once it changes, thus providing an       * opportunity to invalidate the cached value. *       * You need to register for each object your locally cached values depend on. E.g. if you build a cached value from * attributes of EOs in a too-many relationship, your cache depends on each of the objects in the relationship as well * as on the source of the realtionship. *       * N.B. Extend the dispose method to unregister any dependancy you might be registered for. *       * @param object the object to watch * @see #unregisterCacheDependancy * @see #clearCachedValues */     protected void registerCacheDependancy(EOEnterpriseObject object) {       ChangeNotificationCenter.defaultCenter.registerCacheDependancy(this, object); }     /** Method to be called by subclasses when they abandon or clear a cached value that depended on attributes * of a given object. *      * @param object the object to watch * @see #registerCacheDependancy * @see #clearCachedValues */     protected void unregisterCacheDependancy(EOEnterpriseObject object) {       ChangeNotificationCenter.defaultCenter.unregisterCacheDependancy(this, object); }     /** Unregisters all of the callers dependancies. To be used sparingly as it may break unknown but required * dependancies. Usually called when disposing the calling object. *       * @see #registerCacheDependancy * @see #unregisterCacheDependancy */     public void unregisterCacheDependancies {       ChangeNotificationCenter.defaultCenter.unregisterCacheDependancies(this); }     /** Overridden to unregister all cache dependancies */     public void finalize throws Throwable {       ChangeNotificationCenter.defaultCenter.unregisterCacheDependancies(this); super.finalize; }   }  }


text:
WebObjects/Web Applications/Development/WOEvents and Logging

NSDebugLevel
These are the available debug levels (WebObjects 5.2.2):

 * DebugLevelOff 0
 * DebugLevelCritical 1
 * DebugLevelInformational 2
 * DebugLevelDetailed 3

NSDebugGroups
Debug groups are defined as flags in a 64-bit array. The value shown in the documentation is the index of the specific debug group flag in this array.

For example, if you want to start the logging of WebObjects and EO debug information you can do:

NSLog.allowDebuggingForGroups(NSLog.DebugGroupWebObjects|NSLog.DebugGroupEnterpriseObjects);

Or:

NSLog.allowDebuggingForGroups(6); // Flags 2 and 1, i.e. 6 = 1L<<2 | 1L<<1

You can specify debug groups in a command-line argument, either as a single numerical value (as above), as a list of flags or as a range of flags. For example, all the following are equivalent:

./MyApp -DNSDebugGroups=6 ./MyApp -DNSDebugGroups="(2, 1)" ./MyApp -DNSDebugGroups="(NSLog.DebugGroupWebObjects, NSLog.DebugGroupEnterpriseObjects)" ./MyApp -DNSDebugGroups=1:2

These are the available debug groups (WebObjects 5.2.2):

 * DebugGroupApplicationGeneration 3
 * DebugGroupArchiving 6
 * DebugGroupAssociations 19
 * DebugGroupComponentBindings 9
 * DebugGroupControllers 20
 * DebugGroupComponents 26
 * DebugGroupDatabaseAccess 16
 * DebugGroupDeployment 22
 * DebugGroupEnterpriseObjects 1
 * DebugGroupFormatting 10
 * DebugGroupIO 13
 * DebugGroupJSPServlets 27
 * DebugGroupKeyValueCoding 8
 * DebugGroupModel 15
 * DebugGroupMultithreading 4
 * DebugGroupParsing 23
 * DebugGroupQualifiers 11
 * DebugGroupReflection 24
 * DebugGroupRequestHandling 25
 * DebugGroupResources 5
 * DebugGroupRules 21
 * DebugGroupSQLGeneration 17
 * DebugGroupTiming 14
 * DebugGroupUserInterface 18
 * DebugGroupValidation 7
 * DebugGroupWebObjects 2
 * DebugGroupWebServices 2