Watershed

Safari Kiosk Tabs

An AppleScript to ensure that new Safari windows are opened in a tab

In the Watershed Café/Bar there are a number of ‘Slacker Tables’—public web browser kiosks set in glass tables. Oliver asked about a way to ensure that the instances of Safari running on the tables would open a new tab and not a new window. This is a simple AppleScript, to be saved as an Application and run, in a loop in the background.

This was only a quick, five minute hack but the results are surprisingly usable. Running the script as an application in the background doesn't appear to affect performance or stability over longer periods of time.

Note: this script has only been tested with Safari 3.2.1 in MacOS X 10.5.5

  1. repeat
  2. delay 1
  3. tell application "Safari"
  4. if the (count of documents) > 1 then
  5. set theURL to the URL of the front document
  6. if theURL is not "" then
  7. close front document
  8. tell front window
  9. -- Safari appears to only let the front window create tabs
  10. set theTab to make new tab
  11. set current tab to theTab
  12. set the URL of theTab to theURL
  13. end tell
  14. end if
  15. end if
  16. end tell
  17. end repeat
|

Cocoa network persistence

Persisting NSDictionary ‘documents’ into the multi–user CouchDB store across the network

Apache CouchDB is a distributed, fault-tolerant and schema-free document-oriented database. CouchDB does support data structures within the document but deep hierarchies are best served using a relational database.

Document elements are stored in a JSON representation within a document. There is a natural “impedance match” between JSON and Cocoa’s Property List objects with the exception of NSDate. Dates will need to be serialised into strings.

Collections of documents are grouped in Views, specified by Map Reduce functions written in Javascript.

CouchDB has a low barrier to entry. CouchDB’s most beneficial feature to Watershed’s situation now is the casual integration with all categories of applications and the ease with which sophisticated shared storage is possible from application resource–sensitive client environments such as Cocoa on the iPhone and Javascript applications.

NSMutableDictionary Category

Persistence is achieved in Cocoa by using NSMutableDictionary as the container for each document. Each document can be identified by the identifer and the revision, stored in the dictionary using the NSString keys _id and _rev. The documents are mutable to track revisions over time. The persistence is implemented using a category on NSMutableDictionary. We haven’t done much work here—just assembled the pieces Lego style. Most of the hard work is done by json-framework which will need to be included in the project.

The category, composing of two methods, is partially listed below:

  1. #define COUCHDB_ID_KEY @"_id"
  2. #define COUCHDB_REV_KEY @"_rev"
  3. #define HTTP_METHOD_POST @"POST"
  4. #define HTTP_METHOD_PUT @"PUT"
  5. @implementation NSMutableDictionary (NSDictionary_FBTFCouchDB)
  6. + (id)dictionaryFromDocumentStore:(NSURL *)storeURL withIdentifier:(NSString *)identifer {
  7. if (!storeURL || !identifer) return nil;
  8. NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:identifer relativeToURL:storeURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
  9. NSError *error = nil;
  10. NSURLResponse *response = nil;
  11. NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
  12. if (error) {
  13. NSLog(@"Network Connection Error: %@", [error localizedDescription]);
  14. return nil;
  15. }
  16. // Couch uses UTF8 encoding for transfers
  17. NSString *jsonString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
  18. if (!jsonString) return nil;
  19. error = nil;
  20. SBJSON *jsonParser = [[SBJSON alloc] init];
  21. id result = [jsonParser objectWithString:jsonString allowScalar:YES error:&error];
  22. [jsonString release];
  23. [jsonParser release];
  24. if (error) {
  25. NSLog(@"JSON Parse Error: %@", [error localizedDescription]);
  26. return nil;
  27. }
  28. return result;
  29. }
  30. - (BOOL)persistInDocumentStore:(NSURL *)storeURL {
  31. // We do both inserts and updates in this method
  32. NSString *methodName;
  33. NSURL *requestURL;
  34. if ([self objectForKey:COUCHDB_ID_KEY]) {
  35. // this dictionary originated from the document database so update by including ID in URL
  36. methodName = HTTP_METHOD_PUT;
  37. requestURL = [NSURL URLWithString:[self objectForKey:COUCHDB_ID_KEY] relativeToURL:storeURL];
  38. } else {
  39. // This is a new Dictionary so we are inserting
  40. methodName = HTTP_METHOD_POST;
  41. requestURL = storeURL;
  42. }
  43. NSMutableURLRequest *aRequest = [[NSMutableURLRequest alloc] initWithURL:requestURL];
  44. [aRequest setHTTPMethod:methodName];
  45. NSError *error = nil;
  46. SBJSON *jsonParser = [[SBJSON alloc] init];
  47. NSString *argsData = [jsonParser stringWithObject:self allowScalar:NO error:&error];
  48. if (error) {
  49. NSLog(@"JSON Serialisation Error: %@", [error localizedDescription]);
  50. return NO;
  51. }
  52. // Finish setting up the request and send to document store
  53. [aRequest setHTTPBody: [argsData dataUsingEncoding:NSUTF8StringEncoding]];
  54. NSURLResponse *response = nil;
  55. error = nil;
  56. NSData *responseData = [NSURLConnection sendSynchronousRequest:aRequest returningResponse:&response error:&error];
  57. [aRequest release];
  58. // Now parse the results
  59. NSString *result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
  60. error = nil;
  61. NSDictionary *results = [jsonParser objectWithString:result allowScalar:NO error:&error];
  62. if (error || [results objectForKey:@"error"]) {
  63. // Something went wrong. report, clean up and return
  64. if (error) {
  65. NSLog(@"Can not parse results: %@", [error localizedDescription]);
  66. } else {
  67. NSLog(@"Database Error: %@", [results valueForKey:@"reason"]);
  68. }
  69. [jsonParser release];
  70. [result release];
  71. return NO;
  72. } else {
  73. // update the revision and the id (in case this is an insertion we do both)
  74. [self setValue:[results valueForKey:@"id"] forKey:COUCHDB_ID_KEY];
  75. [self setValue:[results valueForKey:@"rev"] forKey:COUCHDB_REV_KEY];
  76. [jsonParser release];
  77. [result release];
  78. return YES;
  79. }
  80. }
  81. @end

Other Possibilities & Further Development

The next step will be creating a database co–ordinator or connection proxy to manage persistence in a more sophisticated fashion. This can support features such as authentication & authorisation, revisions, rollback & merging of documents and the efficient storage & retrieval of collections.

Whilst CouchDB is not an object database and doesn’t encourage deep hierarchies, we’ve experimented recently combining CouchDB documents with Core Data’s NSManagedObjects by keeping document identifiers in managed objects and merging at display time. In the other direction, NSManagedObjects’ snapshots can easily be included in documents in the store when the NSManagedObject is saved in the context.

CouchDB is a peer-based distributed database system, it allows for users and servers to access and update the same shared data while disconnected and then bi-directionally replicate those changes later. We’d like to look into the possibility of using Couch, embedded in the desktop application bundle, locally and then allow CouchDB to replicate and sync between different instances as they appear on the network.

|

Replacing Cocoa Persistence Framework

Taking Watershed's Cocoa desktop application to relational database communications forward

Currently…

Watershed's Communications staff use an in-house desktop application, named Communicate, to edit and control "what's on" information for the organisation. Communicate uses a Cocoa persistence framework, I wrote some years ago in Objective-C, to fetch and save objects into an Openbase relational database. The framework uses plug-ins to directly connect to and communicate with databases from different vendors.

An Objective-C, Cocoa, business objects framework is written on top of the persistence classes which describes and implements all the business logic. This framework is a copy of a server-side, WebObjects, Java framework.

Network performance is good, especially as most users use the applications over a LAN or local wireless connection. As the size of the database has grown, unarchiving large streams of data into the object graph is beginning to use a significant amount of time.

The persistence framework was created, over five and a half years ago, in 2003. New technologies used to develop desktop applications have become available. The persistence framework uses its own classes for sorting and qualifiing object fetch requests. For instance, we could reduce our maintenance footprint by adopting NSSortDescriptor and NSPredicate.

Aims & Objectives

A new persistence framework must give Watershed database independence. We would like to move from Openbase to MySQL some point in the medium term. The flexability is currently provided by database specific plug-ins and we would like to change this to use a three tier architecture for desktop client applications.

Using a set of desktop frameworks, written in Objective-C, mirroring a set of server frameworks, written in Java, means defining and implementing any object relationships, validation rules and convenience routines twice. It's difficult to create comparable classes using Java and Objective-C and it's obviously twice the work and twice the maintenance. A new framework should help alleviate this duplication.

Core Data is Apple's object life-cycle and persistence framework. Using Core Data has advantages, such as automatic undo, and moving forward will gain new features such as possible automatic AppleScript access to the model. Core Data has a natural synergy with bindings, KVC and KVO. NSManagedObject implements many techniques, including uniquing, identification and change tracking, used in a persistence framework. It would be sensible for a new persistence framework to take advantage of existing technology in Core Data.

Web applications, such as Basecamp, Lighthouse and soon Watershed, frequently provide a REST API to the services. Often the API sends and receives XML over an HTTP connection. This API can be used to build a wide range of software but is especially useful for desktop applications, widgets and iPhone applications communicating with the web service. A persistence framework would be all the more valuable if it helped us to communicate with these types of services.

Option One: Enhance Exitsting Framework

The existing persistence framework can still be used. There is nothing intrinsicaly wrong and we can write adapters for almost all networked, relational databases. The framework connects to the database and fetches the data it needs. This approach where the framework connects and authenticates directly to the database just feels wrong viewed alongside mordern techniques for over the internet delivery.

Some performance issues could be resolved by adding the ability to partially load business objects, similar to the way raw rows can be used in EOF. We suspect performance improvements could also be found in the routines that deal with the model lookups and the creation of Cocoa objects from huge streams of database data.

Option Two: FBTFPersistentObjects with Core Data In-Memory Stores

We have an object persistence framework that we have been using for the past five years. We could leverage some of the existing code by moving the framework onto Core Data and modernising the interfaces. Using an in-memory store and the notifications we could replicate the functionality we currently have. Moving our base business object to a subclass of NSManagedObject we can reuse many of the change tracking functionality built into NSManagedObject to store our objects into a network RDBMS.

FBTFPersistentObjects uses built-in database adapters. For each database vendor, there will be an adapter. The database adapter subclasses manage SQL generation and network communication. The database adapter is not dynamically loaded but is compiled and linked into the framework therefore changing databases could require substantial work meaning no real world database independence.

Clients connecting directly to the database requires negotiating network security and authenticating users' requests on the database, loosing our independent user management. Business logic and validation written into the client can't be easily reused in other applications.

Option Three: Core Data REST Stores

The third option is to essentially use Core Data as a local cache for REST web services. Using a local SQLite store, each client would keep its own copy of the used portions of the server data. The managed object model would be interrogated to generate network request URLs and formulate request data. A mapping delegate or configuration file would also be available to replace the defaults on a project-by-project basis.

Using the delegate notifications from the managed object context, changes would be propagated through RESTful HTTP POST and PUT methods to URLs on the server. At this point objects can be updated, inserted and removed from the server store. Fetches are likely to be performed against the server if a network connection is available and the local cache updated. Data schema formats could include XML, JSON or Binary Property Lists.

By using REST stores we're complementing the work we have been doing to add RESTful request handing to WebObjects applications and would be useful when writing desktop clients for the upcoming changes in dShed.net as well as the ever increasing number of web applications with REST APIs.

To be a truly effecient companion to REST web services, NSManagedObjects should be capable of sub-atomic operations. We would need to find out if managed objects could be made to support property values that can be trickled in as they are needed, in a similar fashion to the way relationships are faulted in.

Other Considerations

iPhone

Working closely with Core Data prohibits using the persistence framework with iPhone client applications. With the limited memory and performance available on the iPhone, I suspect it would be advantageous to create a separate, simpler, resource saving mapping routines that deserialze XML or binary Property LIsts.

WebObjects REST Handler

We've been using WebObjects' frameworks to create web applications for some time now and have recently been working on a method to quickly and effortlessly add RESTful interfaces to applictions.

The REST interface over HTTP was designed, not only to provide a publicy available API, but also as low configurtion data provider to a desktop persistence framework.

Ruby on Rails

Whilst, not heavy user of Ruby on Rails, it's impressive how simply and amazingly quickly a web application can be developed to vend XML, or JSON, data for a desktop client. These applications use type hints in the attribrutes of the XML and expect XML elements to be named to a convention. Any Cocoa persistence framework developed should be able to work closely, and cleanly, with a generated scaffolded Rails application with little configuration.

|

Cocoa EOF? No thanks

As our current persistent framework at Watershed needs work I’ve been thinking about shared persistent data frameworks

It appears to be with increasing frequency that one can hear experienced Cocoa developers reminiscing about EOF for desktop applications and new developers wondering why Core Data doesn’t use stores in network RDBMS’s from a range of vendors.

As Watershed’s persistence framework will shortly be replaced, I’ve been working on some possible replacements. One of the best things about being an in-house developer is, I get to to see people using my applications every day. I walk past people’s desks and see which windows are open and what tasks users perform. An incredibly frequent task runs as follows:

  1. The user needs to make a quick edit to an item stored in a relational database.
  2. The user opens their Cocoa client and a window opens, normally containing a NSTableView listing all the entries.
  3. The persistence routines generate the required SQL, sends it to the database and receives a stream of data.
  4. The persistence routines turn the data into a object graph generating thousands and thousands of objects. Each business object, even with relationships represented by proxy objects, contains many attributes each of which is represented by an object.
  5. Once the persistence routines have finished creating the thousands and thousands of objects, making copies & caches and linking them together by setting object instance variables: the user opens the first item in the list, corrects a typo and quits the application.

The problems evident in the example could be seen as a UI issue. Possibly the way forward is to follow iPhone applications’ methods of display a small number of entries then prompting the user to load then next batch. iTunes displays thousands of tracks in a table on launch so why can’t my applications do the same? The business object implemented using NSManagedObject or an EOGenericRecord is an atomic unit. Why does the application need to populate an entire business object just to display the title in one column?

Caching large chunks of data on the client made efficiency enhancements that were just not needed. Launch times are incredibly slow, damaging the user experince. Worried about excess network traffic, we started off designing clients with aggressive caching. Our controllers in the Cocoa applications now save changes more frequently, and with less modality in the edits. This results in a constant trickle of data across the network but a much smoother user experience.

Public applications with a large number of users render bulky local caches ineffective. As the number of editors increases, the probability that a local cache is no longer synchronised increases.

Frameworks such as EOF or ActiveRecord work very well over high speed, high quality networks but many network connections for Macintosh, and iPhone, clients are mobile and made wirelessly or are intermittent. Many clients connect over fragile internet connections where the performance will destroy the user experience.

So, no I don’t want Apple to re-release EOF for Objective-C or allow Core Data stores to reside in a network RDBMS—I want something better.

|

Using RESTful WebObjects

Using requests with the WebObjects REST Handler for HTTP requests.

The previous post described building a new request handler allowing REST behaviour in WebObjects applications. This post will explain how the handler responds to specific requests. The current test application was built by simply, creating a new WebObjects application in Eclipse, adding a business objects framework and adding the REST handler. The test framework is the same framework that provides the foundation for www.watershed.co.uk so the examples will deal with films, screenings, seasons & festivals. The examples below use two Entities from the model, Exhibits and Programmes. Essentially, a Programme represents a season or festival of films. An Exhibit, generally, represents a film. There is a to-many relationship from Programme to Exhibits; i.e. A Programme contains a collection of Exhibits. This can be seen in use at the Watershed site. The reverse relationship is also modelled, so Exhibits have a to-one relationship to a Programme.

exhibit_programme

Rewriting

REST URLs describe the location of the resource in a hierarchy, so arguments are sent mimicking a directory structure: www.watershed.co.uk/cgi-bin/WebObjects/WatershedAPI.woa/REST/exhibits/1527 The request URLs are often written into executable code or synthesised from String components so keeping the URLs simple and short aids their use. URLs generated by the data modules are 'pretty' and incoming requests are rewritten in Apache so the previous URL becomes: api.watershed.co.uk/exhibits/1527.

Tools

Although not particular to our WebObjects REST Handler, using special tools can make testing much more efficient and pleasant. Whilst a web browser can be used to generate GET and POST requests they struggle with the other HTTP methods. Using CURL, from Terminal, can be useful, optionally adding headers to, or, setting the method or body of, the HTTP request. Typing man curl into Terminal will display a page explaining the options. RESTClient is a Java Swing application to test RESTful web services. It can be used to test variety of HTTP communications and has GUI options for most of the HTTP 1.1 specification, including setting the method, authentication values and the message body as well as viewing the response headers. RESTClient is a useful tool for any type of web application development.

REST URLs

URL Structure

Just as a WebObjects' component action URL has a structure, then URLs accepted by the REST Handler have a structure. Each action has a target and a method. The method is part of the HTTP specification and is explained later. The URL structure is essentially:

host/entity_name/primary_key/relationship/...

The entity name and the primary key together define the target. The entity names in the URLs follow a casual, but established, convention and are lower-case, underscore-separated and plural. So the URL /exhibits/1527 will map to an Exhibit Enterprise Object with a primary key value of 1527. Using the model described earlier, we can find the Programme with the primary key of 57 by using the URL: api.watershed.co.uk/programmes/57/ . As the entity name is underscore separated then to set the target for the MediaSet entity with a primary key of 1234, we can use the URL: api.watershed.co.uk/media_sets/1234.

The REST Handler uses the EOModels in the application, or linked frameworks, to map the URL to the correct Entity names and thence to a table in the database. The Handler then retreives the correct Enterprise Objects and delivers these targets to the appropriate data module for output in the response.

Relationships & Key paths

Objects in isolation are often of little practical use. Modelling real world environments requires a hierarchy of objects and containers. Creating a new season isn't useful unless we can insert films into it.

Once the target object has been established key paths defined in the model are then available. The relationship between Exhibit and Programme, illustrated in the diagram above, can now be used. To find the Programme of an Exhibit we use the URL: api.watershed.co.uk/exhibits/1527/programme/. We can follow the key path, defined in the EOModel, even further by using the to-many relationship back to an array of Exhibits: api.watershed.co.uk/exhibits/1527/programme/exhibits/. In a similar fashion to entity names, key paths in the model are renamed to maintain the pretty URLs. Although not illustrated, Exhibit has a to-many relationship to an array of MediaSets, named mediaSets. Using this relationship results in a URL similar to: api.watershed.co.uk/exhibits/1527/media_sets/.

Now that we have URLs that point to relationships, we can use these to add objects into and remove objects from collections.

Model Delegates

Comparing the illustration of the Entities, above, and the actual XML generated by a GET request displays an inconsistency. Although, attributes with null values are not currently listed, the attributes notes and history are never included. The REST Handler and the data modules use the model delegate to provide some information about how to render the data. To hide the notes and history attributes:

  1. public NSArray excludeAttributesForEntity(String entityName) {
  2. // Always remove notes and history regardless of
  3. // the entity
  4. return new NSArray(new String[] {"notes", "history"});
  5. }

The use of the model delegate is optional and if the delegate is not set, the entity and attribute names will be taken from the model and adjusted.

Varying Data Representations

Outlined in the last post was a technique whereby changing the Accept HTTP header changed the response data format or allowed a client to send updated data to the web application in a variety of formats. Sending a request using cURL in Terminal shows the effect of the accept header.

curl -H 'Accept: text/xml' http://api.watershed.co.uk/exhibits/1530/
produces an XML response, whilst:

curl -H 'Accept: text/plist' http://api.watershed.co.uk/exhibits/1530/
produces a response in Apple's Property List format.

Actions

As the URL specifies the resource then the HTTP method specifies the action to perform on it. The REST Handler responds to the HTTP actions defined in the 1.1 specification. Four of the HTTP methods are mapped to the CRUD actions. The currently deployed application will not receive requests other than GET, HEAD & POST. The FreeBSD web server adapter, for watershed.co.uk applications, was compiled from the Apple source code and does not support the PUT & DELETE methods. The sites will be moving to a new virtual server in the next couple of weeks and we will compile a new adapter from the WebObjects 5.4 adapter source.

To read an Exhibit from the data store:
curl -X GET http://api.watershed.co.uk/exhibits/1530/

To delete an Exhibit from the data store:
curl -X DELETE http://api.watershed.co.uk/exhibits/1530/

To update the title attribute of an Exhibit in the data store:
curl -X PUT -d "A new title</exhibit>" http://api.watershed.co.uk/exhibits/1530/</code></p> <p>To <em>create</em> a new Exhibit in the data store:<br /> <code>curl -X POST -d "<exhibit><title>A new Exhibit<title><copyText>What a wonderful…<title></copyText>" http://api.watershed.co.uk/exhibits/1530/</code></p> <p>By following the key paths in the URLs we can insert and remove objects from a relationship. This part is still very much a work in progress and is still not 'quite right'.</p> <p>To insert a new Exhibit in the data store and add it to the array of exhibits in a programme, specify the array as the target resource:<br /> <code>curl -X POST -d "<exhibit><title>A new Exhibit<title><copyText>What a wonderful…<title></copyText>"<br />http://api.watershed.co.uk/programmes/57/exhibits/</code></p> <p>To remove an Exhibit from the to many relationship of a Programme but leave it in the data store:<br /> <code>curl -X DELETE http://api.watershed.co.uk/programmes/57/exhibits/1530/</code></p> <h2>Notes and Incomplete</h2> <p>As mentioned previously, the adapter installed on the web server does not handle PUT & DELETE requests but we will compile a new version that supports these methods shortly. Manipulating relationship collections feels 'clunky' at the moment and this needs some work. The data modules, both the XML and the Property List, need significant work. A JSON data module would be a welcome addition and would compliment the XML and Property List moduels.</p> <p>One of the original reasons for REST Handler was to provide an efficient and quick data flow to a new persistence framework for Cocoa applications. We already have a framework that consumes large chunks of data from a database, we need to deliver data in a constant trickle just as it's needed. Large collections, and objects in a collection, should be available in a more terse output to reduce response times and bandwidth.</p><p class="blog-entry-tags">Tags: <a href="tag-webobjects.html" title="WebObjects" rel="tag">WebObjects</a>, <a href="tag-java.html" title="Java" rel="tag">Java</a>, <a href="tag-xml.html" title="XML" rel="tag">XML</a>, <a href="tag-web-development.html" title="web development" rel="tag">web development</a>, <a href="tag-rest.html" title="REST" rel="tag">REST</a></p><div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_42_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_42_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_42=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_42_page0'); </script></a></div></div></div><div id="unique-entry-id-40" class="blog-entry"><h1 class="blog-entry-title"><a href="restful_webobjects.html" class="blog-permalink">RESTful WebObjects</a></h1><div class="blog-entry-date">02/06/08 10:13 </div><div class="blog-entry-body"><p class="summary">Development of a REST request handler for WebObjects applications.</p> <p>As part of watershed.co.uk's ongoing development, we have a number of immediate requirements:</p> <ul> <li>Start building a replacement for the FBTFPersistentObjects framework used to provide object persistence and network database connectivity for Communicate.</li> <li>Vend XML and other data types using a technology with easier to implement clients than SOAP.</li> <li>Transfer data to iPhone applications efficiently using XML or binary plists.</li> <li>Do all the above across a number of different databases and business object frameworks.</li> </ul> <p>REST web services vending XML & plist data appeared a good fit for all the above but we didn't want to have to write a whole collection of direct actions and components for each site. So we are in the process of building a REST Request Handler for new and existing applications. This is still a work in progress and is only, a day or two old, but hopefully in the near future there should be useable code alongside the explanation.</p> <h2>So how do we use our REST Request Handler in applications?</h2> <p>First we create a new instance of the handler and assign it to handle particular requests. We do this in Application's constructor in Application.java</p> <ol class="code"> <li><code>RESTHandler restRequestHandler = new RESTHandler();</code></li> <li><code>restRequestHandler.setSecurityDelegate(new MyRESTSecurity());</code></li> <li><code>registerRequestHandler(restRequestHandler, RESTHandler.REQUEST_HANDLER_KEY);</code></li> </ol> <p>We also set a security delegate here which is explained below. The request handler key is "REST" so any request sent to www.domain.com/cgi-bin/WebObjects/App.woa/REST/... will be processed by our handler.</p> <p>Unfortunately, the REST Handler is not quite the simple, drop-in unit we would like. WebObjects will only create and initialise a WORequest with a HTTP GET, HEAD & POST method. Sending a HTTP DELETE or PUT action to a WebObjects application results in an exception. The solution is quite simple, subclass WORequest and return it from a re-implemented createRequest(...) in your Application class.</p> <h2>Security Delegate</h2> <p>The REST Handler can automatically provide public access to any database so as a precaution a security delegate must be implemented and added to the request handler before any requests can be accepted. The security delegate can be any object that implements the few methods declared in the security delegate interface, RESTSecurityDelegate.</p> <ol class="code"> <li><code>public interface RESTSecurityDelegate {</code></li> <li class="indent1"><code>public int requiredHTTPAuthentication(WORequest aRequest);</code></li> <li class="indent1"><code>public boolean requireSecureTransport();</code></li> <li class="indent1"><code>public boolean shouldAllowRequest(WORequest aRequest);</code></li> <li><code>}</code></li> </ol> <p>The security delegate methods are simple to implement and can be varied per request. There are some general REST authentication and authorisation practices in use and these leverage existing features of HTTP: with a secure HTTPS connection to prevent eavesdropping and use, either, HTTP Basic or Digest authentication, or, include a security token as a HTTP header key such as:</p> <p><code><pre>GET / HTTP/1.1 User-Agent: curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 Host: www.watershed.co.uk X-Watershed-Auth: U2FsdGVkX1zuHyuXFLEBoBjEHtQO. Accept: */*</pre></code></p> <p>Currently only HTTP Basic support is implemented and we will add Digest at a later date. The REST Handler will first check if it is allowed receive requests over plain HTTP by calling requireSecureTransport(). If the return value is true then the handler will redirect the client to a secure connection. Next the REST Handler asks the delegate, using <code>requiredHTTPAuthentication()</code>, if the request will require authentication and, if the client hasn't provided any, will ask the client to provide authentication. Currently, only Basic HTTP authentication is supported. Finally, the delegate decides if the handler should process the request in the <code>shouldAllowRequest(WORequest aRequest)</code> method.</p> <p>The listing below illustrates a possible implementation of the required methods.</p> <ol class="code"> <li><code>public class SecurityDelegate implements RESTSecurityDelegate {</code></li> <li><code></code></li> <li class="indent1"><code>public int requiredHTTPAuthentication(WORequest aRequest) {</code></li> <li class="indent2"><code class="comment">// Give everyone read-only access</code></li> <li class="indent2"><code>if (aRequest.method().equals("GET")) return SecurityDelegate.AUTHENTICATE_NONE;</code></li> <li class="indent2"><code>return SecurityDelegate.AUTHENTICATE_BASIC;</code></li> <li class="indent1"><code>}</code></li> <li><code></code></li> <li class="indent1"><code>public boolean requireSecureTransport() {</code></li> <li class="indent2"><code>return false;</code></li> <li class="indent1"><code>}</code></li> <li><code></code></li> <li class="indent1"><code>public boolean shouldAllowRequest(WORequest aRequest) {</code></li> <li class="indent2"><code class="comment">// Give everyone read-only access</code></li> <li class="indent2"><code>if (aRequest.method().equals("GET")) return true;</code></li> <li><code></code></li> <li class="indent2"><code class="comment">// A user must be authenticated to change resources</code></li> <li class="indent2"><code>if (aRequest.headerForKey("authorization") != null) {</code></li> <li class="indent3"><code>String encodedAuth = aRequest.headerForKey("authorization");</code></li> <li class="indent3"><code>String decodedAuth = null;</code></li> <li class="indent3"><code>BASE64Decoder decoder = new BASE64Decoder();</code></li> <li class="indent3"><code class="comment">//encoded string starts after "Basic " </code></li> <li class="indent3"><code>encodedAuth=encodedAuth.substring(encodedAuth.indexOf(" ")+1); </code></li> <li class="indent3"><code>try{</code></li> <li class="indent4"><code>decodedAuth=new String(decoder.decodeBuffer((new ByteArrayInputStream (encodedAuth.getBytes())))); </code></li> <li class="indent3"><code>} catch(IOException ex) {} </code></li> <li><code></code></li> <li class="indent3"><code>String tokens[] = decodedAuth.split(":");</code></li> <li class="indent3"><code>if(tokens.length != 2) return false;</code></li> <li><code></code></li> <li class="indent3"><code class="comment">// Check the username & password</code></li> <li class="indent3"><code>if (tokens[0].equals("benjamin") && tokens[1].equals("mypassword")) return true;</code></li> <li class="indent2"><code>}</code></li> <li class="indent2"><code>return false;</code></li> <li class="indent1"><code>}</code></li> <li><code></code></li> <li><code>}</code></li> </ol> <h2>Data Modules</h2> <p>Once the security delegate has approved the request, a data module is then invoked with the task of handling of the request. Using a number of installed data modules allow the application to vend REST web services in any number of different data formats. Data modules render Enterprise Objects out in a response and update, or insert, EO's from parsed data in a request. The data module also deletes EO's from the store for some requests. Each data module implements an interface based on HTTP actions and the REST Handler invokes a method passing the original WORequest and the target EO, or array of EO's.</p> <p>Currently, the REST Handler selects an appropriate data module based on the Accept HTTP header sent in the request by the client. It was assumed that clients would have fine grained control over the request and the client would not always be a browser window so the REST Handler treats each acceptable MIME Type as HTTP 1.1 would, MIME Types are listed in order of preference: most desirable first. There is one exception to the HTTP 1.1 specification; the relative quality parameter is currently ignored. If a header such as <code>Accept: image/jpeg, video/mp4, text/xml;q=0.9, text/json, text/plist, */*</code> were received then the request handler would look first for a module that handled JPEG images and so on.</p> <p>Each available data module is mapped to one or more MIME types. Currently we are just using a particular XML data module for testing and in the process of writing a plist module for use with Cocoa desktop and iPhone clients. A module is not required to support all four actions create, read update, and delete. Extra modules, such as JSON or vCard can be written relatively quickly and registered with the REST handler before accepting requests. There currently isn't a way to request a particular data format based upon the 'file' suffix in the request.</p> <ol class="code"> <li><code>restRequestHandler.registerDataModuleForMIMEType(new DataModuleXML(), "*/*");</code></li> </ol> <h2>Model Delegate</h2> <p>The REST Handler will pass the request and the target Enterprise Objects to the data module but on some occasions we may not want make every entity available to the public. We may also want to make some attributes, such as private internal notes, unavailable. When a request needs to render 10,000 objects to XML we may not need to include every attribute, just an essential subset. An optional model delegate provides the REST Handler and the data modules with answers and information they need to handle the requests efficiently. If the model delegate is not provided then the handler use the default model group.</p> <ol class="code"> <li><code>public interface RESTModelDelegate { </code></li> <li class="indent1"><code>public boolean cacheModelAdjustments();</code></li> <li class="indent1"><code>public boolean includeEntity(WORequest aRequest, String entityName);</code></li> <li><code></code></li> <li class="indent1"><code>public NSArray removeAttributesForEntity(String entityName);</code></li> <li class="indent1"><code>public NSArray includeExtraAttributesForEntity(String entityName);</code></li> <li><code></code></li> <li class="indent1"><code>public NSArray attributesForCollection(String entityName);</code></li> <li class="indent1"><code>public NSArray attributesForObject(String entityName);</code></li> <li><code></code></li> <li class="indent1"><code>public String aliasForEntity(String entityName);</code></li> <li><code></code></li> <li class="indent1"><code>public String labelForEntity(String entityName);</code></li> <li class="indent1"><code>public String labelForEntities(String entityName);</code></li> <li class="indent1"><code>public String labelForProperty(String propertyName);</code></li> <li><code>}</code></li> </ol> <h2>To be Implemented…</h2> <p>The REST Handler has only had a handful of train journeys of development so there are plenty of rough edges. It's currently difficult to adjust or influence the target enterprise objects provided to the data modules which would be useful for situations where we may not want to include all the available films, just the ones screening this month. There is no HTTP Digest authentication and the ability to specify query strings, such as <code>commence<2007-05-29</code> or <code>isPublic=true</code>, in the URL or a request header would be very useful. There is still plenty of work to be done until the REST Handler is complete but it will be a valuable tool as we make architectural, storage and communication changes in watershed.co.uk and dshed.net.</p><p class="blog-entry-tags">Tags: <a href="tag-webobjects.html" title="WebObjects" rel="tag">WebObjects</a>, <a href="tag-web-development.html" title="web development" rel="tag">web development</a>, <a href="tag-java.html" title="Java" rel="tag">Java</a>, <a href="tag-xml.html" title="XML" rel="tag">XML</a>, <a href="tag-rest.html" title="REST" rel="tag">REST</a></p><div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_40_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_40_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_40=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_40_page0'); </script></a></div></div></div><div id="unique-entry-id-32" class="blog-entry"><h1 class="blog-entry-title"><a href="8d2685d0af016efbc8199b309331154f-32.html" class="blog-permalink">Off to WWDC</a></h1><div class="blog-entry-date">08/06/07 08:23 </div><div class="blog-entry-body"><p>I'm catching a flight, from Bristol to San Francisco, Saturday morning to attend Apple's <a href="http://developer.apple.com/wwdc/">WWDC</a>. Although I've been a number of times, I'm still excited as ever about the technical content of the sessions and the chance to meet other Cocoa & WebObjects developers.</p> <p class="blog-entry-tags">Tags: <a href="tag-wwdc.html" title="WWDC" rel="tag">WWDC</a></p><div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_32_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_32_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_32=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_32_page0'); </script></a></div></div></div><div id="unique-entry-id-22" class="blog-entry"><h1 class="blog-entry-title"><a href="communicate.html" class="blog-permalink">Communicate</a></h1><div class="blog-entry-date">16/07/06 08:50 </div><div class="blog-entry-body"><p class="summary">This is essentially a glossary entry for Communicate. I often write about Communicate's development in the blog and thought a quick overview would be useful.</p> <p>Communicate is a desktop application that Watershed staff use to administer the data that is used in the creation of the Web site. Communicate is also used to control display aspects of the web site. Communicate runs on Apple Macintosh computers running MacOS X 10.3 or later and is written in Cocoa by Watershed's Communications department.</p> <p>Watershed's Web site contains constantly changing content and it was decided to build a desktop application to administer the data. A desktop application has many advantages of a web application, especially in 2002 when there was no AJAX, widget toolkits, Javascript libraries… etc. A desktop application has speed, ease of use and a familiar interface. The disadvantages include platform dependence, version concurrency and development time. Work began on Communicate at the beginning of January 2003.</p> <p>Communicate is a reasonably modular construction. The application itself uses a number of plug-ins and bundles to provide functionality and sits on frameworks that provide the data and ensure it is available to the Web site. Essentially, Communicate is just a collection of user interface elements.</p> <img class="imageStyle" alt="frameworks" src="page0_blog_entry22_1.gif" width="327" height="219"/> <h2>Database Libraries</h2> <p>The OpenBase frameworks and MySQL C libraries are used in database plug-in modules that provide network access to the databases. The plug-ins are relatively simple and just read and write data over the wire to the database. Currently we have written just the two for accessing OpenBase and MySQL databases, but adding more database plug-ins is relatively straight forward.</p> <h2>FBTFPersistentObjects Framework</h2> <p>The FBTFPersistentObjects framework provides object persistence for business objects. It handles archiving and unarchiving objects to a data store, caching and uniquing. The persistence framework can be thought of an EOF lite, similar to Ruby on Rails' Active Record or other ORM libraries.</p> <h2>WSHDCoreData Framework</h2> <p>When starting the project, I decided to model Watershed's operations in a business logic framework rather than just build another Content Management System. By modelling the organisation's data and operations we get a framework that accurately describes Watershed but ,more importantly, is reusable. The WSHDCoreData framework can be used in any application Watershed needs to develop, such as a cinema audience tracking, exhibition planning… etc. WSHDCoreData contains the classes that describe Watershed: Events, Exhibits, Media, StaffPositions , Locations and the XML mapping file used by FBTFPersistentObjects to map to the persistence database. There is a companion framework WSWOCoreData, written in Java, that is used by server based web applications. WSHDCoreData has nothing to do with Apple's CoreData persistence technologies. It was named to describe it's purpose at Watershed before we were aware of , and Apple demonstrated CoreData.</p> <h2>Communicate</h2> <p>On top of the frameworks is the desktop application Communicate. Communicate provides access to the data through a series of list windows. Staff can add, edit and remove items from Watershed's programme.</p> <p><a href="/benjamin_miller/odds_ends/assets/communicate/exhibit_media_events_list.jpg" rel="lightbox" class="thumbnailLink" title="Media pane for an Exhibit and the Event list"><img title="Click to view a larger version" align="middle" alt="interface screen shot" src="/benjamin_miller/odds_ends/assets/communicate/exhibit_media_events_list_thumb.jpg" /></a> <a href="/benjamin_miller/odds_ends/assets/communicate/media_list_exhibits_list.jpg" rel="lightbox" class="thumbnailLink" title="Media browser and Exhibits list"><img title="Click to view a larger version" align="middle" alt="interface screen shot" src="/benjamin_miller/odds_ends/assets/communicate/media_list_exhibits_list_thumb.jpg" /></a> <a href="/benjamin_miller/odds_ends/assets/communicate/adverts_articles.jpg" rel="lightbox" class="thumbnailLink" title="Advertisement preview and Articles list"><img title="Click to view a larger version" align="middle" alt="interface screen shot" src="/benjamin_miller/odds_ends/assets/communicate/adverts_articles_thumb.jpg" /></a></p> <p><a href="/benjamin_miller/odds_ends/assets/communicate/descriptions_site_programmes.jpg" rel="lightbox" class="thumbnailLink" title="Descriptions, Site plug-ins and Programme contents"><img title="Click to view a larger version" align="middle" alt="interface screen shot" src="/benjamin_miller/odds_ends/assets/communicate/descriptions_site_programmes_thumb.jpg" /></a> <a href="/benjamin_miller/odds_ends/assets/communicate/update_jobs_prefs.jpg" rel="lightbox" class="thumbnailLink" title="Software Update, Jobs listing and Preferences window"><img title="Click to view a larger version" align="middle" alt="interface screen shot" src="/benjamin_miller/odds_ends/assets/communicate/update_jobs_prefs_thumb.jpg" /></a></p> <p>Communicate will load plug-ins, from inside its own bundle, at launch time to provide control over the Web applications which generate the site data. In effect, these Communicate Site Plug-ins just save their changes into a shared preference database. An abstract plug-in class makes writing these plug-ins a quick and simple task. For most plug-ins, almost all the work can be accomplished in Interface Builder using Cocoa Bindings.</p> <p><a href="/benjamin_miller/odds_ends/assets/communicate/site_cafebar.jpg" rel="lightbox" class="thumbnailLink" title="CaféBar site plug-in"><img title="Click to view a larger version" align="middle" alt="interface screen shot" src="/benjamin_miller/odds_ends/assets/communicate/site_cafebar_thumb.jpg" /></a> <a href="/benjamin_miller/odds_ends/assets/communicate/site_message.jpg" rel="lightbox" class="thumbnailLink" title="Message site plug-in"><img title="Click to view a larger version" align="middle" alt="interface screen shot" src="/benjamin_miller/odds_ends/assets/communicate/site_message_thumb.jpg" /></a> <a href="/benjamin_miller/odds_ends/assets/communicate/site_caches.jpg" rel="lightbox" class="thumbnailLink" title="Caches site plug-in"><img title="Click to view a larger version" align="middle" alt="interface screen shot" src="/benjamin_miller/odds_ends/assets/communicate/site_caches_thumb.jpg" /></a></p> <h2>Further Development</h2> <p>Communicate is being developed and extended and this will continue into the future. This includes new features to support new site and Web application features, UI and workflow enhancements and performance optimisations We learnt in the past that creating a new shiny site and just sitting back is guaranteed way to create lots of work in the future and a repetition of the process in an endless loop. We have made an internal commitment to constantly improve the watershed.co.uk sites, gradually over time, for an indefinite period. Some of the features we would like to add to Communicate in the near future are:</p> <ul> <li>Comprehensive Applescript support.</li> <li>A plug-in architecture for import, export and actions. Probably allowing plug-ins to be Cocoa bundles or AppleScript files.</li> <li>XML Import & Export plug-ins.</li> <li>A more user friendly and intuitive UI centred around a publishing paradigm and not requiring the user to have a knowledge of the underlying frameworks data structure.</li> </ul> <p class="blog-entry-tags">Tags: <a href="tag-cocoa.html" title="Cocoa" rel="tag">Cocoa</a></p><div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_22_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_22_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_22=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_22_page0'); </script></a></div></div></div><div id="unique-entry-id-20" class="blog-entry"><h1 class="blog-entry-title"><a href="scriptable_internal_tools.html" class="blog-permalink">Scriptable Applications</a></h1><div class="blog-entry-date">11/07/06 18:19 </div><div class="blog-entry-body"><p class="summary"><a href="http://www.apple.com/applescript/">AppleScript</a> is thought of as an 'added extra' for desktop applications but for internal tools it should be an essential on the 1.0 feature list.</p> <div class="essay"> <p>Many shrink-wrapped, commercial applications do not add AppleScript support until later versions and with internal tools developed in-house it is an even lower priority. Many of these internal tools have a quick and dirty UI and none, or little, online help so being scriptable is not very likely.</p> <p>With Cocoa applications adding AppleScript support is relatively easy and quick to implement. This is certainly the case in applications that are strongly based around a business logic model with data objects, as many internal tools are, and are already Key Value Coding compliant. Cocoa AppleScript support will do lots of the work for you.</p> <p>So why should an in-house developer make their applications scriptable?</p> <p>AppleScript support will free up developer time. If I write a scriptable application, the staff members who use the app or Oliver, Alex or Paddy of our ICT department can add new features or work around bugs without waiting for me to deploy the next version. I can spend my time implementing features that really need to be done rather than a feature to 'duplicate every cinema screening in Cinema One on every second Wednesday in the month'.</p> </div> <p>Watershed will gain from increased productivity from a scripted workflow. We could save so much time with a script that created the web data from the brochure:</p> <ol class="code"> <li><code>tell application "Quark XPress"</code></li> <li class="indent1"><code>repeat with every text box in the front document</code></li> <li class="indent2"><code>tell application "Communicate"</code></li> <li class="indent3"><code>set newExhibit to make new Exhibit</code></li> <li class="indent3"><code>set the title of newExhibit to the first line of text box</code></li> <li class="indent3"><code>save newExhibit</code></li> <li class="indent2"><code>end tell</code></li> <li class="indent1"><code>end repeat</code></li> <li><code>end tell</code></li> </ol> <div class="essay"> <p>AppleScript support is a springboard or stepping stone to an Automator Action that even more users can make use of with even less training. Actions can be built by the users or by our ICT department.</p> <p>If I ever left the Watershed, being able to implement some new features using AppleScript would give the users and ICT a breathing gap before a new developer could be employed and was up to speed with the application.</p> <p>There are many other reasons to make any application scriptable that are often just applied to commercial development. These are just some reasons to add AppleScript support to internal tools.</p> </div> <p class="blog-entry-tags">Tags: <a href="tag-applescript.html" title="AppleScript" rel="tag">AppleScript</a>, <a href="tag-cocoa.html" title="Cocoa" rel="tag">Cocoa</a></p><div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_20_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_20_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_20=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_20_page0'); </script></a></div></div></div><div id="unique-entry-id-9" class="blog-entry"><h1 class="blog-entry-title"><a href="b3471bda39c70bc1c70f0f8f114dd7d2-9.html" class="blog-permalink">Server Side Stills from QuickTime</a></h1><div class="blog-entry-date">18/05/06 08:42 </div><div class="blog-entry-body"><p class="summary">Generating still JPEG images from QuickTime movies using Java running on the server.</p> <div class="essay"> <p>Often it is useful to generate preview thumbnail images of QuickTime video movies on the server. CMS type applications that allow an admin user to upload and display video would be the most obvious use of this.</p> <p>Having said that, generating still images on the desktop is far quicker and more efficient. Opening 80MB video files, over the network, in the web application's RAM in not the best use of resources. This solution requires Java 1.4.1 to be running on the server and most importantly requires QuickTime for Java. QuickTime for Java is essentially a series of JNDI hooks into the native C libraries of QuickTime. What this means is that this can only be deployed on MacOS X or MS Windows servers.</p> <p>Watershed uses something like the code below in WebObjects applications as part of the QuickTime generation and manipulation classes used in web sites such as watershed.co.uk, electricdecember.org and dshed.net. These are expensive operations and the results should be aggressively cached either in RAM or on disk.</p> <p>The code below has been a bit mangled to fit the narrow columns and still has a lot of commented-out logging statements. </p> </div> <ol class="code"> <li><code>import quicktime.*;</code></li> <li><code>import quicktime.app.*;</code></li> <li><code>import quicktime.app.players.*;</code></li> <li><code>import quicktime.app.display.*;</code></li> <li><code>import quicktime.io.*;</code></li> <li><code>import quicktime.std.*;</code></li> <li><code>import quicktime.std.movies.*;</code></li> <li><code>import quicktime.qd.*;</code></li> <li><code>import quicktime.std.movies.media.*;</code></li> <li><code></code></li> <li><code>public class GetImage {</code></li> <li class="indent1"><code>public GetImage() {</code></li> <li class="indent1"><code>}</code></li> <li><code></code></li> <li class="indent1"><code>public void getImageFromMovie(String url, String offsetTime) {</code></li> <li><code></code></li> <li class="indent2"><code>try {</code></li> <li class="indent3"><code>QTSession.open();</code></li> <li class="indent3"><code>DataRef ref = new DataRef(url);</code></li> <li class="indent3"><code>System.out.println("DataRef: " + ref);</code></li> <li class="indent3"><code>if (ref == null) return;</code></li> <li><code></code></li> <li class="indent3"><code>Movie theMovie = Movie.fromDataRef(ref, 0);</code></li> <li class="indent3"><code>System.out.println(theMovie.getBounds());</code></li> <li><code></code></li> <li class="indent3"><code>int theTime = Integer.parseInt(offsetTime) * theMovie.getTimeScale();</code></li> <li class="indent3"><code>System.out.println("theTime: " + theTime);</code></li> <li class="indent3"><code>Pict pictImage = theMovie.getPict(theTime);</code></li> <li><code></code></li> <li class="indent3"><code>System.out.println("PICT: " + pictImage);</code></li> <li><code></code></li> <li class="indent3"><code class="comment">// Optionally you could just use the poster image if it has been set by the person encoding the video</code></li> <li class="indent3"><code class="comment">//pictImage = theMovie.getPosterPict();</code></li> <li><code></code></li> <li class="indent3"><code class="comment">// Now encode the Macintosh PICT as a JPEG or PNG or ....</code></li> <li class="indent3"><code>quicktime.std.image.GraphicsExporter graphicsExporter = new quicktime.std.image.GraphicsExporter(StdQTConstants.kQTFileTypeJPEG);</code></li> <li class="indent3"><code>graphicsExporter.setInputPicture(pictImage);</code></li> <li class="indent3"><code>QTFile exportFile = new QTFile("StillImage.jpg");</code></li> <li class="indent3"><code>graphicsExporter.setOutputFile(exportFile);</code></li> <li class="indent3"><code>System.out.println("graphicsExporter: " + graphicsExporter);</code></li> <li><code></code></li> <li class="indent3"><code class="comment">// Do the export</code></li> <li class="indent3"><code>int size = graphicsExporter.doExport();</code></li> <li class="indent3"><code>byte[] jpegBytes = graphicsExporter.GraphicsExportReadOutputData(0, size);</code></li> <li class="indent3"><code>System.out.println("jpegBytes: " + jpegBytes);</code></li> <li><code></code></li> <li class="indent3"><code>QTSession.close();</code></li> <li class="indent2"><code>} catch (Exception e) {</code></li> <li class="indent3"><code>QTSession.close();</code></li> <li class="indent3"><code>System.out.println(e);</code></li> <li class="indent3"><code>System.exit(0);</code></li> <li class="indent2"><code>}</code></li> <li class="indent1"><code>}</code></li> <li><code></code></li> <li class="indent1"><code>public static void main(String args[]) {</code></li> <li class="indent2"><code>if (args.length < 2) {</code></li> <li class="indent3"><code>System.out.println("You need to specify a URL and a location time in seconds when launching");</code></li> <li class="indent3"><code>return; </code></li> <li class="indent2"><code>}</code></li> <li class="indent2"><code>GetImage instance = new GetImage();</code></li> <li class="indent2"><code>instance.getImageFromMovie(args[0], args[1]);</code></li> <li class="indent1"><code>}</code></li> <li><code>}</code></li> </ol> <h2>Using the code</h2> <p>The following instructions assume you are using a Macintosh but are almost identical for other platforms. First, ensure that you have QuickTime for Java installed. Copy the code above into a text file and save the file as GetImage.java on the Desktop, or alternatively <a href="/benjamin_miller/odds_ends/assets/QTImageStills/GetImage.java" title="Download the source file">download the class</a> in a .java file. Next, assuming you have a JDK installed, compile the source. Installing Apple's Xcode Tools will give you a Java compiler. Open the Terminal application and compile by typing:</p> <p><code>javac -classpath /System/Library/Java/Extensions/QTJava.zip GetImage.java</code></p> <p>A file named GetImage.class will be produced. Alternatively <a href="/benjamin_miller/odds_ends/assets/QTImageStills/GetImage.class.zip" tttle="Download the compiled binary">download the compiled application</a>. To run the application, navigate to the directory containing the GetImage.class and type something like:</p> <p><code>java -classpath /System/Library/Java/Extensions/QTJava.zip: GetImage http://localhost/Cremaster.mov 22</code></p> <p>The application will save the result as a file named StillImage.jpg in the same directory. The image produced is not scaled but this is fairly easy to add in. The code sample above contains lots of debug logging so remove the System.out.println lines.</p> <p class="blog-entry-tags">Tags: <a href="tag-quicktime.html" title="QuickTime" rel="tag">QuickTime</a>, <a href="tag-java.html" title="Java" rel="tag">Java</a></p><div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_9_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_9_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_9=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_9_page0'); </script></a></div></div></div><div id="unique-entry-id-12" class="blog-entry"><h1 class="blog-entry-title"><a href="web_screensaver.html" class="blog-permalink">MacOS X Web Screensaver</a></h1><div class="blog-entry-date">05/04/06 11:09 </div><div class="blog-entry-body"><p class="summary">A screensaver that will display your favorite Web page.</p> <p>Whilst doing some graphics and layout work on Watershed's public display system (the screens at the top of the stairs) I thought it would be useful for staff to have the daily What's On information displayed on their computers. A screensaver looked to be a good idea:</p> <ul> <li>We already had, not only the data, but formatted and layed out for display.</li> <li>The public screens are very simillar a screensaver - non-interactive content is displayed in a sequence.</li> <li>The public screens are built using standard HTML, CSS & Javascript making the content easy to display.</li> <li>The content updates and refreshes itself. The application displaying the content does not have to interact with the content in any way.</li> </ul> <p>One evening, on the train home, I quickly built a screensaver that would load and display the content. <p>The Web content you wish to display must change itself. This can be done usins a meta refresh tag, Javascript timers (AJAX calls work well) or using a simple HTML wrapper around QuickTime, Flash or Shockwave content. Obviously user events, such as mouse & keyboard (this being a screensaver), are not passed to the HTML. <h2>Download</h2> <p>Requires MacOS X 10.3 or later. <a href="http://www.watershed.co.uk/download/web_screensaver/1.0/web_screensaver.dmg">Download .dmg file</a></p> <h2>Redistribution and Usage</h2> <p>Redistribution is permitted but please give Watershed Media Centre with an engineering credit and include a link to the homepage at www.watershed.co.uk in either a splash screen or Read Me file. The software is provided 'as is' and is unsupported. If you use the screen saver, then please send us an email (<a href="mailto:benjamin@watershed.co.uk">benjamin@watershed.co.uk</a>) because we are interested in any use it receives.</p> <h2>How to configure the screensaver</h2> <p>The screensaver just needs a full, absolute URL to operate. It will not currently work with a relative URL for content distributed inside the screensaver bundle. The URL is stored in a plist file inside the .saver bundle.</p> <ol> <li>Open the .saver bundle by ctrl-clicking the icon and select Show Package Contents from the contextual menu that is displayed.</li> <li>Open the Info.plist file, that is inside the Content folder, in either a text editor or Apple's Plist Editor.</li> <li>Change the value of the WSHDWebLocation key to the location of yout content. Ensure you have included the http:// protocol prefix.</li> <li>Save changes and double click to install.</li> </ol> <div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_12_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_12_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_12=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_12_page0'); </script></a></div></div></div><div id="unique-entry-id-3" class="blog-entry"><h1 class="blog-entry-title"><a href="3e2880f6c4c0f8e9e9750052d6f996a6-3.html" class="blog-permalink">Universal Binary Widget</a></h1><div class="blog-entry-date">05/04/06 11:05 </div><div class="blog-entry-body"><p class="summary">The Watershed What‘s OnWidget is now a <a href="http://developer.apple.com/transition/">Universal Binary</a></p> <p>We have a few MacOS X applications, written against the Cocoa frameworks, that are used internally. Until very recently we didn't have any Intel Macintoshes. No problem so far.</p> <p>In his keynote at WWDC 2005, Steve Jobs announced that Intel Macintoshes would ship by summer 2006 and almost all developers assumed this meant shipping in summer 2006. So when Apple announced the new Core Duo iMac and MacBook Pro in January it was a bit of a surprise. Still the only 'native' Macintosh software that was anything near public was a Dashboard Widget. Widgets are primarily HTML, CSS & Javascript. No problem so far.</p> <p>The Widget started out as an experiment/toy not long after the first developer previews of Tiger were released in 2004. Almost all of the work was done and the Widget had been hanging around for over a year with no work done on it. A link(commented out) has been present in the Accessing Calendar Data page in the Watershed site since the site's redevelopment.</p> <p>Unfortunately, the Widget uses a couple of plug-ins to provide some of its functionality:</p> <ul> <li>a widget plug-in, Watershed Utilities, is used to bridge Javascript into the Address Book;</li> <li>a webkit browser plug-in, RippleSelector, displays the thumbnails and draws the Core Image ripple transitions.</li> </ul> <p>The Widget, not being a 'standard' desktop application, had escaped my attention when looking for projects that needed to be re-built as Universal Binaries.</p> <p>I didn't envision any problems building the Universal versions of the plug-ins as Apple had advised that in most cases it was just a checkbox tick. The WatershedUtilities plug-in re-built and worked as advertised. The RippleSelector plug-in was a little more trouble. I should explain that the build-run-debug cycle was slightly more convoluted than usal:</p> <ol> <li>compile and link on my PPC PowerBook</li> <li>install the plugin into a Widget bundle in my public folder</li> <li>walk across the office to Maddie's Core Duo iMac</li> <li>launch Dashboard and the Widget from my mounted public folder</li> <li>wait for the Widget to launch, get the SOAP results and crash</li> </ol> <p>Eventually I tracked down the problem to a method that dealt with calculating the layout and positions of the thumbnails in the view. The method -(int)batchCount; calculates the number of batches required to display all the thumbnails and.contains a line something like:</p> <p><code>if (batchVolume % [items count])...</code></p> <p>The number of events is rarely 0, apart from Christmas Day, Boxing Day... and the time window between when the plug-in is first drawn to the screen and when the results of the Javascript XMLHttpRequest SOAP call are returned. This isn't a problem because zero events means zero batches required. Exept when running on an Intel chip; zero batches means crash.</p> <p>So the moral of this story is:<br/> /0<br/> ppc - OK<br/> i386 - BAD<br/></p> <p>The Universal Binary version of thc Watershed widget is fixed now and can now be <a href="http://www.watershed.co.uk/cgi-bin/WebObjects/Watershed.woa/wa/news?object=69">downloaded</a>.</p> <p class="blog-entry-tags">Tags: <a href="tag-web-kit.html" title="Web Kit" rel="tag">Web Kit</a>, <a href="tag-core-image.html" title="Core Image" rel="tag">Core Image</a></p><div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_3_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_3_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_3=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_3_page0'); </script></a></div></div></div><div id="unique-entry-id-11" class="blog-entry"><h1 class="blog-entry-title"><a href="d8bb4c705c42f374792478433a928807-11.html" class="blog-permalink">Bonjour WebObjects</a></h1><div class="blog-entry-date">28/02/06 10:06 </div><div class="blog-entry-body"><p class="summary">Instances of Watershed.woa are now talking to each other directly.</p> <p>Watershed.woa, the WebObjects application that generates the pages for www.watershed.co.uk, was developed some time ago. Until this point, only a single instance, on a single XServe has been handling all the requests. A single instance will happily handle all the requests but there was no redundancy against application failure or server failure. Watershed.woa relied upon code that assumed only a single instance was running.</p> <p>Images generated (the ones with the rounded corners or small magnifying glass icons) are heavily cached and were accessed through the WOResourceManger cache. This only works when the browser returns to the same instance (that generated the page) to fetch the images. With Component Actions, everything works as expected but Direct Action requests can only be sent to the instance that created the page.</p> <p>Watershed.woa caches many resources, especially generated images and configuration XML read in from disk. The caching mechanisms are crude; the application provides a fixed amount of memory to cache images - once the cache is full older images are dropped. Other data is cached with a time to live and once the time limit has expired the cache is nuked.</p> <p>Admin messages are sent from Communicate (a Cocoa desktop application) to Watershed.woa to configure the site and control the running web application. With a single instance running, these admistrative messages always reach the instance but with a number of instances deployed then only one instance will receive the request and act upon it.</p> <p>The solution decided upon was to build a 'Replicated Request Handler'. When the RRH receives a request, it then forwards that request on to all the other running instances. The request is modified to appear as a regular DirectAction. It's not a good idea to forward a replicated request on to other instances that will then forward each request on to other instances... It's amazing how quickly the adapter queues become backed up.</p> <p>There are some drawbacks to this method:</p> <ul> <li>It only works for DirectActions. Sending Component Actions to multiple instances makes very little sense.</li> <li>Any data returned from the Direct Action must be inconsequencial. The instance that receives the replicated request returns data but responses from the other instances are discarded.</li> <li>DirectConnect must be enabled for the deployed instances to be targeted. Other communication and messaging solutions were looked at including RMI & JMS but the were either too complex to deploy or not suitable to implement.</li> </ul> <h3>Finding them</h3> <p>Finding and identifying instances became the next problem. Instances are stagger scheduled, so at any point in time it is difficult to determine which intances are up and running. The problem will be further exaserbated in the near future when different instances of different applications are run on different days of the week.</p> <p>The watershed.co.uk data is provided by a number of applications that share a common business logic framework. These applications respond to sets of administrative actions simlar to an interface or protocol. Cache control functions are the most common. So, in effect, the applications provide service groups they need to advertise.</p> <p>When working from the Watershed office I almost always open iTunes and instead of listening to my own music I browse other people's music collections and listen to music that is not avallable to me the other five days of the week. As a bonus for providing free Internet access in the CaféBar, customer's own music is availablo to listen to. iTunes music sharing is a very neat and trouble free experience. It doen't matter which network the user is on to iTunes. As users come and go, the list gf music collections changes as if by magic.</p> <p>This was the solution we needed for WebObjects' instance discovery. The depIoyment Xserves are on same subnet (yes I know) so Bonjour works very well for us.</p> <p>Since <a href="http://developer.apple.com/opensource/internet/bonjour.html">Bonjour</a>'s, née Rendezvous, introduction SDKs have become available for Windows and Java and the C source is part of the <a href="http://developer.apple.com/opensource/">Darwin open source project</a>. We downloaded the source for mDNS responder from Apple's Darwin site and a quick build of the Xcode project results in a MacOS X folder. Located inside this is the Java build products. lib-mdns-jni.lib was copied over to an Xserve in /usr/bin/lib/ and the mdns.jar was added to the WebObjects project's classpath.</p> <p>A simple, thread safe, singleton class wraps this all up neatly and provides a NSArray of NSDictionaries. Each dictionary contains host name, IP address, port and other information for each provider of the service. The class also registers its own instance on the network when it is created.</p> <p>So now we have instances running that are aware of other instances and can share requests with them. This new communications channel will allow much greater control of the web applications such as finer granularity of the caches.</p> <p class="blog-entry-tags">Tags: <a href="tag-webobjects.html" title="WebObjects" rel="tag">WebObjects</a></p><div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_11_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_11_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_11=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_11_page0'); </script></a></div></div></div><div id="unique-entry-id-10" class="blog-entry"><h1 class="blog-entry-title"><a href="e020c0ba27fe10eddba252e8a2a102cf-10.html" class="blog-permalink">Oops</a></h1><div class="blog-entry-date">18/01/06 08:59 </div><div class="blog-entry-body"> <p>Below is a graph of the processor loads on one of the Xserves we use for WebObjects deployment. I guess those threading locks were not quite right.</p> <img class="imageStyle" alt="aGraph" src="page0_blog_entry10_1.jpg" width="320" height="196"/> <p>Even with the two processors flat-out for three days, over the weekend, and a significant temperature rise on the processors and in the enclosure, Apple's Server Software has no way to send an alert via email.</p> <div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_10_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_10_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_10=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_10_page0'); </script></a></div></div></div><div id="unique-entry-id-5" class="blog-entry"><h1 class="blog-entry-title"><a href="75b107dfe6d137d935ae406e05ff3fe3-5.html" class="blog-permalink">Transparent Room Screen Saver</a></h1><div class="blog-entry-date">18/01/06 08:38 </div><div class="blog-entry-body"><p>Been working on updating the Transparent Room screen saver for MacOS X recently. QuickTime <a href="/benjamin_miller/odds_ends/assets/trssAVC_Lg_Prog.mp4">movie of the screensaver</a> (requires QuickTime 7). The screen saver uses Core Image, a Quartz Composition and OpenGL, making the minimum system requirement 10.4 with a graphics card.</p><p class="blog-entry-tags">Tags: <a href="tag-cocoa.html" title="Cocoa" rel="tag">Cocoa</a>, <a href="tag-core-image.html" title="Core Image" rel="tag">Core Image</a></p><div class="blog-entry-comments"><a class="blog-comment-link" href="javascript:HaloScan('rw_unique_entry_id_5_page0');"><script type="text/javascript">postCount('rw_unique_entry_id_5_page0');</script></a> | <a class="blog-trackback-link" href="javascript:HaloScanTB('rw_unique_entry_id_5=page0');"><script type="text/javascript">postCountTB('rw_unique_entry_id_5_page0'); </script></a></div></div></div> </div> </div> <!-- End Content --> <div id="controls"> <h2><span class="t-down">odds</span>&<span class="t-up">ends…</span><br /><span class="subTitle">Benjamin Miller</span></h2> <div id="mainNavigation"><ul><li><a href="../" rel="self" id="current">Blog</a></li><li><a href="../links/" rel="self">Links</a></li><li><a href="../me/" rel="self">Me</a></li></ul></div> <div id="blog-categories"><div class="blog-category-link-disabled">Personal</div><a href="category-watershed.html" class="blog-category-link-enabled">Watershed</a><br /><a href="category-other-work.html" class="blog-category-link-enabled">Other Work</a><br /><a href="category-dshed.html" class="blog-category-link-enabled">dShed</a><br /><div class="blog-category-link-disabled">Church Hall</div><a href="category-this-site.html" class="blog-category-link-enabled">This Site</a><br /><a href="category-software.html" class="blog-category-link-enabled">Software</a><br /><div class="blog-category-link-disabled">Web de</div></div><div id="blog-archives"><a class="blog-archive-link-enabled" href="archive-feb-2009.html">Feb 2009</a><br /><a class="blog-archive-link-enabled" href="archive-jan-2009.html">Jan 2009</a><br /><a class="blog-archive-link-enabled" href="archive-aug-2008.html">Aug 2008</a><br /><a class="blog-archive-link-enabled" href="archive-jul-2008.html">Jul 2008</a><br /><a class="blog-archive-link-enabled" href="archive-jun-2008.html">Jun 2008</a><br /><a class="blog-archive-link-enabled" href="archive-jan-2008.html">Jan 2008</a><br /><a class="blog-archive-link-enabled" href="archive-jul-2007.html">Jul 2007</a><br /><a class="blog-archive-link-enabled" href="archive-jun-2007.html">Jun 2007</a><br /><a class="blog-archive-link-enabled" href="archive-jan-2007.html">Jan 2007</a><br /><a class="blog-archive-link-enabled" href="archive-jul-2006.html">Jul 2006</a><br /><a class="blog-archive-link-enabled" href="archive-jun-2006.html">Jun 2006</a><br /><a class="blog-archive-link-enabled" href="archive-may-2006.html">May 2006</a><br /><a class="blog-archive-link-enabled" href="archive-apr-2006.html">Apr 2006</a><br /><a class="blog-archive-link-enabled" href="archive-feb-2006.html">Feb 2006</a><br /><a class="blog-archive-link-enabled" href="archive-jan-2006.html">Jan 2006</a><br /><a class="blog-archive-link-enabled" href="archive-dec-2005.html">Dec 2005</a><br /><a class="blog-archive-link-enabled" href="archive-nov-2005.html">Nov 2005</a><br /></div><ul class="blog-tag-cloud"><li><a href="tag-3gp.html" title="3GP" class="blog-tag-size-6" rel="tag">3GP</a></li> <li><a href="tag-applescript.html" title="AppleScript" class="blog-tag-size-7" rel="tag">AppleScript</a></li> <li><a href="tag-art.html" title="Art" class="blog-tag-size-5" rel="tag">Art</a></li> <li><a href="tag-bluetooth.html" title="Bluetooth" class="blog-tag-size-5" rel="tag">Bluetooth</a></li> <li><a href="tag-cocoa.html" title="Cocoa" class="blog-tag-size-10" rel="tag">Cocoa</a></li> <li><a href="tag-core-audio.html" title="Core Audio" class="blog-tag-size-4" rel="tag">Core Audio</a></li> <li><a href="tag-core-data.html" title="Core Data" class="blog-tag-size-4" rel="tag">Core Data</a></li> <li><a href="tag-core-image.html" title="Core Image" class="blog-tag-size-6" rel="tag">Core Image</a></li> <li><a href="tag-eof.html" title="EOF" class="blog-tag-size-4" rel="tag">EOF</a></li> <li><a href="tag-iphone.html" title="iPhone" class="blog-tag-size-3" rel="tag">iPhone</a></li> <li><a href="tag-java.html" title="Java" class="blog-tag-size-8" rel="tag">Java</a></li> <li><a href="tag-plug-in.html" title="Plug-In" class="blog-tag-size-8" rel="tag">Plug-In</a></li> <li><a href="tag-print.html" title="Print" class="blog-tag-size-3" rel="tag">Print</a></li> <li><a href="tag-quicktime.html" title="QuickTime" class="blog-tag-size-9" rel="tag">QuickTime</a></li> <li><a href="tag-rapidweaver.html" title="RapidWeaver" class="blog-tag-size-10" rel="tag">RapidWeaver</a></li> <li><a href="tag-rest.html" title="REST" class="blog-tag-size-7" rel="tag">REST</a></li> <li><a href="tag-safari.html" title="Safari" class="blog-tag-size-2" rel="tag">Safari</a></li> <li><a href="tag-unfuddle.html" title="unfuddle" class="blog-tag-size-2" rel="tag">unfuddle</a></li> <li><a href="tag-web-development.html" title="web development" class="blog-tag-size-10" rel="tag">web development</a></li> <li><a href="tag-web-kit.html" title="Web Kit" class="blog-tag-size-1" rel="tag">Web Kit</a></li> <li><a href="tag-webobjects.html" title="WebObjects" class="blog-tag-size-9" rel="tag">WebObjects</a></li> <li><a href="tag-wwdc.html" title="WWDC" class="blog-tag-size-1" rel="tag">WWDC</a></li> <li><a href="tag-xml.html" title="XML" class="blog-tag-size-7" rel="tag">XML</a></li> </ul> <div id="blog-rss-feeds"><a class="blog-rss-link" href="feed.xml" rel="alternate" type="application/rss+xml" title="Odds & Ends">RSS Feed</a><br /><a class="blog-comments-rss-link" href="http://www.haloscan.com/members/rss.php?user=benjaminmiller">Comments Feed</a></div> <!--<script type="text/javascript" src="http://www.makepovertyhistory.org/whiteband_small_right.js"> </script><noscript><a href="http://www.makepovertyhistory.org/"> http://www.makepovertyhistory.org</a></noscript> <p>Posts that contain <a href="http://technorati.com/search/%22watershed+media+centre%22">"watershed Media Centre"</a> per day for the last 30 days.<br /><a href="http://technorati.com/search/%22watershed+media+centre%22"><img src="http://technorati.com/chartimg/%28%22watershed%20media%20centre%22%29?totalHits=23&size=s&days=30" style="border:0" alt="Technorati Chart" /> </a></p>--> </div> <!-- End Controls --> <div id="footer"><p><a href="/benjamin_miller/odds_ends/files/feed.xml" title="Subscribe to the Rss News Feed">rss feed</a> & <a href="/benjamin_miller/odds_ends/colophon/index.html" title="How and what is used to make this site">colophon…</p></div> </div> <script type="text/javascript" src="http://cetrk.com/pages/scripts/0008/8709.js"> </script> <!-- Start Google Analytics --> <script type="text/javascript"> var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); </script> <script type="text/javascript"> var pageTracker = _gat._getTracker("UA-200933-2"); pageTracker._initData(); pageTracker._trackPageview(); </script><!-- End Google Analytics --></body> </html>