Thursday, April 17, 2008

This Week in Wonder

This Week in Wonder's schedule is all thrown off, but to finally catch us back up:

ERXFetchSpecification
This is still new and under development, but ERXFetchSpecification extends the concepts of a fetch spec and adds:

  • support for caching
  • type safety via generics
  • user info for tracking additional information as you pass a fetch spec around
  • support for grouping results by keypaths

One of the primary features here is caching. In EOF, if you traverse a to-many relationship, the GIDs of the members of that relationship are cached, which ensures that you don't have to do a db roundtrip every time you want to traverse your relationship. Unfortunately, if you use a fetch spec, there is no such cache provided and you always hit the database. ERXFetchSpecification allows fetch spec results to be cached to avoid repeatedly hitting the database. Again, this is still being developed, but Anjo has some very cool ideas for integrating fetch caching with memcached.

WOOGNL
The WOOGNL template parser now supports registering custom association prefixes. You can now set WOOgnl.setAssociationClassForPrefix(associationClass, prefix). This allows you to implement associations like i18n:value="title", which could load the "title" key from your localized strings. We haven't yet provided any out-of-the-box association classes yet, but we have several ideas (happily stolen from other frameworks :) ) that we will be implementing.

JNDI/LDAP
ERXModelGroup has always allowed replacing connection dictionaries on a model based on Properties values, but it only supported JDBC adaptor models. JNDI models are now supported with the properties:


[modelName].serverUrl the per-model server URL to set
[modelName].user the per-model username to set
[modelName].password the per-model password to set
[modelName].authenticationModel the per-model authenticationMethod to set
JNDI.global.serverUrl the global JNDI serverUrl to use by default
JNDI.global.username the global JNDI username to use by default
JNDI.global.password the global JNDI password to use by default
JNDI.global.authenticationMethod the global JNDI authenticationMethod to use by default


Misc
The new ERXModelDoc component provides a convenient way to display model documentation for a model, entity, attribute, and relationship.

ERXStyleSheet is more versatile now and supports optionally inlining the stylesheet vs injecting into the head tag.

You can now provide a custom subclass of the WOOGNL template parser by setting the property "ognl.parserClassName" to your custom subclass.

Response compression now supports stream-based responses.

ERXInOrQualifier is no longer on by default. We had to remove it as the default in 5.4, anyway, because of compatibility problems. If you want to re-enable it in your application, add EOQualifierSQLGeneration.Support.setSupportForClass(new ERXInOrQualifierSupport(), EOOrQualifier._CLASS); to your Application constructor.

ERXQualifierTraversal provides an implementation of the visitor pattern on qualifiers.

ERXProxyAssociation allows you to wrap an existing WOAssociation and inject a fixed prefix or suffix to the existing value.

ERXExpiringCache now supports timeouts per-key rather than just a global timeout for all cache entries.

ERDLinkToEditObject can link to to-one's now.

ERXEnterpriseObjectChangeListener provides a base class for implementing cache invalidation or other classes that need to be notified of changes to specific entities.

Saturday, April 12, 2008

JavaRESTAdaptor

I know ... There was no "This Week in Wonder" this week. Mostly because I had "This Week in Real Job". But here's a new framework in the Wonder family to tide you over till the Next Week in Wonder:

JavaRESTAdaptor
Note: My first post called this ERRESTAdaptor, but it's actually JavaRESTAdaptor, because all adaptors have to be named JavaXxxAdaptor. Whoops.

Overview
JavaRESTAdaptor is an EOF adaptor implementation on top of a RESTful web service, similar to ActiveResource in Rails. Currently JavaRESTAdaptor is read-only, but should be sufficient for consuming RESTful services.

Usage
The usage of JavaRESTAdaptor is best explained in an example. This example is adapted from an ActiveResource tutorial, but it provides several examples of common techniques. The service we're going to communicate with is a Beast installation. Beast is a simple Rails forum application. We'll use http://beast.caboo.se as our example service provider.

Creating the Model
Note that the instructions here assume that you're using the build of WOLips that builds tonight (to be able to pick "REST" from the adaptor list).


  1. Create a Wonder project.

  2. Create an EOModel (of any name) and choose "None" from the list of adaptors in the wizard.

  3. In the default database config (assuming you're using a recent WOLips), select the prototype "EORESTPrototypes" and select the Adaptor "REST".

  4. In the URL, enter "http://beast.caboo.se" and leave the username and password blank (currently JavaRESTAdaptor does not support HTTP authentication).


Creating Entites
Beast provides four entities that we want to interact with: Forum, Topic, Post, User.

  1. Create an entity named Forum.

  2. The "table name" of Forum will tell JavaRESTAdaptor the URL extensions and XML tags that can be used to access the entity. In the case of Forum, the XML tag name comes in two variants -- a singular and a plural form. For the Forum entity, set the table name to "forum,forums".

    Table name for JavaRESTAdaptor comes in several forms:
    • If you do not set a table name, JavaRESTAdaptor will simply use the entity name as the singular form, and use ERXLocalizer to generate a plural form.

    • If you only set a single value (i.e. "forum"), the adaptor will use that as the singular form and use ERXLocalizer to generate a plural form.

    • If you specify two values (i.e. "forum,forums"), the adaptor will use the first value as the singular form and the second value as the plural form.

    • Additionally, as you will see later, you can specify a set of possibly URL prefixes to use to access the entity. When you do not specify a URL prefix (as in our Forum example), the adaptor will assume that it can go to /[plural form].xml and /[plural form]/[id].xml to retrieve objects of this entity type.

  3. We need to determine the attributes of Forum, so open http://beast.caboo.se/forums.xml in your browser and view the source. ActiveRecord (or more importantly Ruby) can one-up us here, because they automatically determine the attributes of records based on queries to the service. We ultimately want to generate Java code, so we need to create an EOModel with attributes.

  4. Forum has the attributes: description, description-html, id, name, position, posts-count, and topics-count. Create attributes in your entity with the names converted to camelCase (description-html becomes descriptionHtml, etc). For each attribute, select a representative prototype, and set the external name to be the original name from the XML. For example, descriptionHtml is a "varcharLarge" prototype (we could pick one with a smaller restriction if we want to limit the values) and its external name is "description-html". postsCount is an "intNumber" and its external name is "posts-count". Do this for the rest of the attributes of Forum.

  5. In Beast, forums contain topics, so let's create the Topic entity. Topics present an interesting issue with modeling, because Beast does not allow you to select topics from the top level, rather you can only select topics through a Forum. For example, there is no /topics/1.xml there is only /forums/1/topics/1.xml.

    This oddity does cause some problems for EOF, because EOF expects to be able to fetch objects with only a primary key. JavaRESTAdaptor provides the ability to model fetching these entities, but you should be careful of places where EOF may cause faults for individual objects. You may find it safer to model relationships to entities of this type in code with fetch specs, or if you're using the Wonder eogen templates, traverse these kind of relationships with forum.topics(null, true), which will force a refetch of the entities.

  6. Request http://beast.caboo.se/forums/1/topics.xml to see an example of what topics look like. Follow the same procedure as for Forum, creating attributes from the XML. For dates, use the prototype "dateTime". For foreign keys, use the attribute "id" and mark them as non-class attributes.

  7. Now to address the URL issue for Topics. Because there is no /topics.xml, we need to tell JavaRESTAdaptor how to find topic objects. Edit the Topic entity and set the table name to "/forums/[forumID]/topics,topic,topics". This says that to fetch a topic, you must use the URL "/forums/[forumID]/topics" where forumID is a variable that corresponds to the Topic attribute of the same name. Any time a fetch is performed on a Topic, a forumID must appear in the qualifier or an exception will be thrown from JavaRESTAdaptor. When traversing a to-many relationship from a Forum to a Topic, this happens automatically inside of EOF. However, if you are trying to fetch a particular topic, you must provide its forum in your qualifier to prevent an error.

  8. Now create the Post entity. Posts are similar to Topics in that you cannot access them from the top level of the service, but they can be accessed in several different ways. It turns out that you can get posts for a user, for a forum, or for a topic (which requires a forum). JavaRESTAdaptor allows you to model these methods as alternative URLs in the table name by separating the URLs with a pipe. For instance, the table name for Post is "/users/[userID]/posts|/forums/[forumID]/posts|/forums/[forumID]/topics/[topicID]/posts,post,posts". I admit this is ugly, and ideally these definitions would be defined on the relationships instead of the entity, but unfortunately at the adaptor level, the EORelationship that was used to fetch the objects is long gone. What ERREST does in this case is that it looks at the qualifier provided and finds the URL that matches the most variables from your qualifier keys and uses that as the fetching URL. For instance, if your qualifier only has a userID in it, the /users/[userID]/posts URL will be used. However, if you qualifier has a forumID and a topicID in it, the /forums/[forumID]/topics/[topicID]/posts will be used. An exception will be thrown if a suitable URL cannot be found given the provided qualifier.

  9. Lastly, create your User entity. Nothing fancy here, and top-level fetches are allowed, so take a look at http://beast.caboo.se/users.xml to create your attributes, and use the table name "user,users" on this entity.


Relationships
You have created all of the entities for reading information from Beast. You can now create the relationships between these entities. For entities that can be fetched from the top level, or when modeling relationships that will provide enough context for the fetch, relationships can be modeled just like a normal eomodel.

For instance, from post.user() is a completely normal to-one relationship because User can be fetched from the top level, and post will provide the "id" necessary to execute the fetch. Similarly, forum.topics() is a normal to-many relationship, because while Topic does not have a topic level fetch URL, traversing the relationship from Forum will provide the "forumID" necessary to complete the fetch.

The "problem child" relationships are, for instance, topic.posts(). Traversing the posts relationship on Topic will only provide a topicID. Unfortunately Posts requires a topicID AND a forumID. It turns out that this can be modeled by putting both topicID and forumID in the joins of the to-many relationship definition. The downside of this is that it will not be symmetric with the other side of the relationship, and will not be updated automatically when write ability is added to JavaRESTAdaptor. The other problem is that, while this technique works on to-many relationships, it does not work on to-one relationships. EOF does not allow a to-one relationship to be defined with a join on anything expect a primary key attribute.

In this situation, you will need to construct the relationship using a fetch spec, and not using normal relationship definitions in the model. Your fetch spec will work just like a normal fetch spec, but you must provide all variables necessary to complete a fetch. As an example, if you want to fetch a particular topic, you must fetch it like:


Topic singleTopic = Topic.fetchRequiredTopic(editingContext, Topic.FORUM.eq(aForum).and(ERXQ.equals("id", 633)));


or


aForum.topics(ERXQ.equals("id", 633), true)


Note that JavaRESTAdaptor allows fetching on non-class attributes.

Fetching Notes
When fetching against the remote service, only topic level EOKeyValueQualifiers, EOAndQualifiers, and EOOrQualifiers with a single entry will be processed for constructing the remote URL. Complex qualifiers can be constructed, but they will be evaluated in-memory against the most restrictive URL that could be constructed given your qualifier attributes.

Examples

EOEditingContext editingContext = ERXEC.newEditingContext();

NSArray forums = Forum.fetchAllForums(editingContext);
System.out.println("Application.Application: Fetching all forums");
for (Forum forum : forums) {
System.out.println("Application.Application: " + forum.name() + ", " + forum.postsCount() + ", " + forum.topicsCount());
}

System.out.println("Application.Application: Fetching forum w/ PK");
Forum singleForum = (Forum) EOUtilities.objectWithPrimaryKeyValue(editingContext, Forum.ENTITY_NAME, "3");
System.out.println("Application.Application: " + singleForum.name());

System.out.println("Application.Application: Fetching topics for " + singleForum.name());
NSArray topics = singleForum.topics();
for (Topic topic : topics) {
System.out.println("Application.Application: " + topic.title() + " created " + topic.createdAt());
}

System.out.println("Application.Application: Fetching posts for forum");
NSArray forumPosts = singleForum.posts();
for (Post post : forumPosts) {
System.out.println("Application.Application: " + post.createdAt());
}

System.out.println("Application.Application: Refetching single topic w/ PK");
Topic singleTopic = Topic.fetchRequiredTopic(editingContext, Topic.FORUM.eq(singleForum).and(ERXQ.equals("id", 633)));
System.out.println("Application.Application: " + singleTopic.title());

System.out.println("Application.Application: Fetching topic user");
User user = singleTopic.user();
System.out.println("Application.Application: " + user.displayName());

System.out.println("Application.Application: Fetching posts for topic (composite pk, which is kind of interesting)");
NSArray topicPosts = singleTopic.posts();
for (Post post : topicPosts) {
System.out.println("Application.Application: " + post.createdAt());
}

Post randomPost = topicPosts.lastObject();
System.out.println("Application.Application: Fetching the topic for a post (this will break if topic is not already fetched)");
System.out.println("Application.Application: " + randomPost.topic().title());

System.out.println("Application.Application: Fetch author of post");
User postedByUser = randomPost.user();
System.out.println("Application.Application: " + postedByUser.displayName());

System.out.println("Application.Application: Fetching posts by author");
NSArray userPosts = postedByUser.posts();
for (Post post : userPosts) {
System.out.println("Application.Application: " + post.createdAt());
}


Check out JavaRESTAdaptorExample project to see the model described here fully completed.

Wednesday, April 2, 2008

This Week In Wonder

The big one for this week is much better 5.4.x support. We've fixed quite a few outstanding issues and we should be in a pretty good place when the next 5.4.x release comes out (which is looking good).

Wonder/5.4 Changes
ERXWOForm
ERXWOForm works properly with the new 5.4 context.isSecure setting (defaulting to secure if context.isSecure())

ERXWOConditional/ERXElse
ERXWOConditional/ERXElse simply cannot play nicely with 5.4 wo:if/wo:else. In the 5.4 build of Wonder, a notable change is that you need to pick your conditional horse. In 5.4, WOConditional will no longer be automatically replaced by ERXWOConditional. WOOgnl's wo:if and wo:else will still resolve to the ERX variant, but if you use WOConditional directly and you want the Wonder behavior, you will need to search for all ":\s*WOConditional" and change them to ": ERXWOConditional". We looked at this quite a bit, but the semantic of the "else" implementation in 5.4 is very different than Wonder's, and continuing to replace WOConditional in the way that we do would break any 3rd party 5.4 components (like Apple's) that are written to expect if/else to work like 5.4's.

[edit: Oh -- I forgot to mention. If you DO NOT intend to use 5.4's if/else and you just want to switch to 5.4 without having to change your components, you can set er.extensions.WOConditional.patch=true and it will override the default disabled patching of WOConditional to be ERXWOConditionl. At the moment this should be OK, because none of the Apple components use their if/else, but in the longer term, you should switch over to ERXWOConditional if you intend to use ERXElse]

ERMemoryAdaptor
This .... actually builds now in 5.4.

ERXWOContext
Through a small bit of trickery, ERXWOContext URL rewriting fully works with 5.4's new context API's.

Foundation
The 5.3 Wonder NS* collections classes better match the 5.4 versions so there should be fewer compatibility issues (which was an issue if you were running 5.4 code with the ERExtensions project checked out).

FrontBasePlugIn and PostgresqlPlugIn
Both of these plugins have been updated/slightly mangled to properly generate SQL under both 5.3 and 5.4 (as long as you aren't calling the new 5.4 schema sync API's). Pierre has merged nearly all (except the latest feature where you can generate SQL without a running database) of the Wonder FrontBasePlugIn features into 5.4, and it fully implements both the 5.3 and 5.4 schema sync API's, so Apple's should be the preferred one under 5.4. Wonder still maintains the primary PG plugin, though, so you will be limited to 5.3 schema sync for now. If you're doing anything with schema sync/sql gen, you need to be very careful of what API's you are using -- it's a bit of a minefield in 5.4 when you're using anything except the built-in plugins.

Source
Almost all of Wonder runs under both 5.3 and 5.4 without mucking with your build path. However, because of some refactorings in WOOgnl, if you are checking out the Wonder source in Eclipse, you will have to remove the Sources_WO53 from your WOOgnl build path and add Sources_WO54 to your build path if you intend to use 5.4. There was unfortunately no obvious way around one.

Other Misc Things
Ajax/ERXFlickrBatchNavigation now support pagination of plain java arrays without using a display group, which should make it easier to paginate non-WOish collections.

ERXKey supports an easier API for filtering arrays. For instance:

NSArray filteredCompanies = Company.NAME.is("Mike").filter(arrayOfCompanies);

WOLips.framework and Click2Open now work under FireFox and IE.

Wednesday, March 26, 2008

This week in Wonder

AjaxModalContainer updated

The AjaxModalContainer has been updated to ibox 2.17. It now has an example with the various options it supports, like images, inline content, external URLs etc.

Wednesday, March 19, 2008

This Week In Wonder

Misc Stuff
AjaxTabbedPanel tracks selected tabs and allows you track the selection. Check out AjaxTabbedPanelDemo for an example.

ERXStyleSheet generates a <link> tag. For non-XHTML, browsers expect the link tag to not have a closing tag. You can now set "er.extensions.ERXStyleSheet.xhtml=false" to make your link tags not generate a closing tag.

ERXWOContext now has a bunch of static directActionUrl(..) generation methods that allow you to override various combinations of host name, port, path, directaction name, http/https, and query string params.

JSON
The big one for this week is the enhanced JSON features (with a lot more surprises coming). We've had page-based JSON proxy support almost since the beginning, but it has been enhanced quite a bit along with the addition of a JSON Request Handler for JSON web services. We've upgraded our implementation from the old metaparadigm.com JSON library to the new jabsorb.org library (which metaparadigm turned into). We also spent some time to add some new JSON EO features. One of the problems we had previously was the complexity of the demo app for JSON. There are now much easier examples that show just how simple it is to pass data around. You can hand an NSArray of EO's to your browser client and the browser can make changes and hand them back -- more to come on this front.

Stateful Page Clients
Do you need access to stateful objects from your component in the browser? Just bind:


AjaxProxy : AjaxProxy {
name = "json";
proxyName = "example";
proxy = proxy;
}

make a "proxy" method that returns an object that you want to be able to message (if you leave the proxy binding off, it will bind to your component directly):

public SomeProxyObject proxy() {
return _proxy;
}

public class SomeProxyObject {
public NSArray people() { ... } // these can be EO's!
public void doSomethingWithAPerson(Person person) { ... }
}

and then in javascript:

var people = json.example.people();
people.nsarray.each(function(person, index) { alert(person.firstName); }
json.example.doSomethingWithAPerson(people[0]);

It's pretty cool how easy it is.

JSON Web Services
Or you can use JSON for web services instead of SOAP/WOWebServices:

In your Application:

JSONRequestHandler requestHandler = JSONRequestHandler.register();
requestHandler.registerService("exampleService", new ExampleService());

Your service class is similar to a registered WOWebService class:

public class ExampleService {
public void printThisString(String string) { .. }
}

JSON services also support "local args", which allow the server to fill in parameters to your method for you based on the request. For instance, if your method is stateful and you need a session, you can just add a WOSession parameter to your method:

public class ExampleService {
public void printThisStringStateful(WOSession session, String string) { .. }
}

And you will be given a session, and the session will be maintained with cookies. The external signature of the method does not change. This works for WOSession, WORequest, WOResponse, and WOContext.

JSON Browser Client
If you want to call your JSON service from a component:

<webobject name = "AjaxJSONClient"/>.exampleService.printThisString('hi');


AjaxJSONClient : AjaxJSONClient {
}

JSON Java Client
If you need JSON in a Java client app:

Client client = JavaJSONClient.create("http://yourhost/cgi-bin/WebObjects/YourApp.woa/-yourPort/json", true);
IExampleService exampleService = (IExample) client.openProxy("exampleService", IExampleService.class);
exampleService.printhisString("hi");

public interface IExampleService {
public void printThisString(String str);
}

(the "true" means you want to use HttpClient as the backing impl, which requires you have that installed in your classpath). Currently there is no stubs generator, so you have to write the stub classes yourself. For most classes it's pretty straightforward (just write the accessor methods). For EO's, it's the same, but you should extend JSONEnterpriseObject on the client. JSONEnterpriseObject just exposes a set/getGlobalID method for tracking purposes. Note that EO's in a Java JSON client app do not track changes -- they only support pass-by-reference right now. That may change at some point.

Check out JSONExample in the AjaxExample app for a bunch of examples that show various ways to use the feature.

Wednesday, March 12, 2008

This Week in Wonder

Automatic Inverse Relationship Updating
This feature was partially implemented a while back, but it didn't work in all cases. This now should work all the time (Chuck! It has test cases!). Essentially what it means is that you no longer have to think about addObjectToBothSidesOfBlah or removeObjectFromBothSidesBlah, you can just use the normal EOF methods and it will automatically call the necessary methods to update your relationships.

The EOGenerator templates that ship with WOLips (and that were inherited from some long-since-paste original version) have always had methods that made this easier -- the person.setCompanyRelationship(company) and company.addToEmployeesRelationship(Person person) methods, and these are nice in code, but you are vulnerable in certain cases (like binding in components) that don't know about those methods and can leave you with only one side being updated properly.

There are new Wonder templates in WOLips that provide overrides for the core setXxx method variants, and they check to see if inverse updating is enabled. If it is, then the new way will be used, if not, the old way will be. So in the above example, when auto-updating of inverse relationships is enabled, you can can person.setCompany(company) and company.addToEmployees(person), and it will Do The Right Thing™.

To enable it, in your Properties file, just set:

er.extensions.ERXEnterpriseObject.updateInverseRelationships=true

Misc API
ERXJavaScript now has the same binding naming conventions as ERXStyleSheet (and the rest of WO) -- framework = "xxx" filename = "yyy", though the old bindings are still maintained.

Utilities
ERXUtilities.deepClone (for anything -- it will call the corresponding object, array, or dictionary methods), ERXArrayUtilities.deepClone (for arrays), and ERXDictionaryUtilities.deepClone (for dictionaries) have been added. If you ever run into a situation where cloning a dictionary is not enough, and you need to clone arrays and dictionaries INSIDE that dictionary, these are the methods for you. deepClone will attempt to recursively clone all of the objects in the graph. You can optionally have it clone just the collections or the leaf nodes as well. Leaf nodes will only clone if they implement Cloneable and provide a clone method -- Currently this will not properly clone EO's, for instance.

Database PlugIns
There's been a long-standing totally annoying (what I consider a) bug in EOF that you have to open a database connection to be able to retrieve jdbc2Info, which is required to do things like SQL generation. If you've ever noticed when you made a new model in EOModeler, or when you try to generate SQL in Entity Modeler, it would yell at you if it couldn't connect to your DB, that's why. jdbc2Info contains things like database type information, which is almost always static with your database version.

Thanks to Andrew Lindesay, the plugins in Wonder (Postgresql and FrontBase right now) both now provide overrides for loading jdbc2Info from a static resource in the plugin jar, which means you can fully create a model and generate SQL without needing a database connection.

For postgresql, make your connection string: jdbc:postgresql://yourhost/yourdb?useBundledJdbcInfo=true

For FrontBase, make your connection string: jdbc:FrontBase://yourhost/yourdb/useBundledJdbcInfo=true

Click-To-Open
Chuck documented Click-To-Open support here.

Ajax
Ajax framework has been upgraded to Prototype 1.6 and Scriptaculous 1.8. Check your apps accordingly.

Wednesday, March 5, 2008

This Week in Wonder

Every two weeks might make more sense, but as soon as I say that, they'll be a flurry of commits to Wonder and it will be a huge post :)

Migrations
Migrations were made more compatible for people not using ERPrototypes. One of the tricky parts of migrations is converting the API request into a database type (for instance, getting newStringColumn to turn into a VARCHAR in your db). EOF doesn't do a very good job at guessing these values, and in some cases it's impossible to guess. When you use ERPrototypes, we can secretly cheat and lookup some known prototype values. If you don't, though, we have to guess effectively. The guessing code was made better, as well as an explicit override for an unguessable type in Postgresql. So migrations should work properly for PG and FB (at least) even if you don't use ERPrototypes now.

Why aren't you using ERPrototypes again? When you use ERPrototypes, Entity Modeler will work more efficiently as well. For instance, when you create a new entity, it will automatically give you an "id" column that is of the "id" prototype.

Property Operators (Properators?)
There are certain cases where you want to be able to have conditional Properties, or properties that get processed in some special way at load time. Wonder now offers support for "property operators". Similar to NSArray operators, you can register operators that will allow you to convert, extend, modify, and add properties during the loading process.

As an example, you can register and use the "Encrypted" operator in your Application's static block (these have to load very early):

static {
ERXProperties.setOperatorForKey(new ERXProperties.EncryptedOperator(), ERXProperties.EncryptedOperator.Key);
}

and you can then specify properties like:

com.mdimension.somesecretvalue.@encrypted=akxb)*@HASBC*#@$&%&@(NSJKX&XZ#

which will use Wonder's default crypter to decrypt the value when it loads.

Or, if you happen to have, say, a really really big WO deployment, you may want to override properties based on instance range, so you can register the InRange operator named "forInstance":

static {
ERXProperties.setOperatorForKey(new ERXProperties.InRangeOperator(ERXProperties.intValueForKey("yourInstanceNumber")), ERXProperties.InRangeOperator.ForInstanceKey);
}

and make a property:
com.mdimension.somethingNotReadyForFullDeployment.@forInstance.100-500,1000-1500=sometestvalue

Note that instance number is a per-request concept, generally, so it's up to you to specify what "instance number" means to your app (so you would have to define and pass in instance number in Java Monitor). This is a pretty specialized feature, but if you have the problem, this should help.

You can write and register your own operators, though, so if you ever have to perform some processing on keys and values, you know where to look.