You rarely need to implement the EOEnterpriseObject interface from scratch. The Framework provides default implementations of all its methods. In Java, the class EOCustomObject implements the interface, so your custom classes should inherit from it. In Objective-C, categories on the root class, NSObject, provide default implementations. Regardless of what language you choose, some of the default method implementations are for custom classes to override, but most are meant to be used as defined by the Framework. Many methods are used internally by the Framework and are rarely invoked by your application code. For more information on EOEnterpriseObject and its methods, see the interface specification in Enterprise Objects Framework Reference.
The remainder of this chapter describes implementing custom classes.
You can generate the source code in Java (Generate Java Files) or Objective-C (Generate ObjC Files). The Generate Client Java Files command creates source code for classes that will be used in the client side of a distributed WebObjects application.
If you choose Java as your language, EOModeler creates a .java file for your class. If you choose Objective-C, it creates both a header (.h) and an implementation (.m) file. No matter what the language, the source files created by EOModeler are completely functional.
protected String city;In Objective-C (from Member.h)
protected NSGregorianDate memberSince;
protected String phone;
protected String state;
protected String streetAddress;
protected String zip;
protected Number customerID;
protected String firstName;
protected String lastName;
protected CreditCard creditCard;
protected NSMutableArray rentals;
NSString *city;All of the instance variables correspond to attributes in the Member entity except for creditCard and rentals, which correspond to relationships.
NSCalendarDate *memberSince;
NSString *phone;
NSString *state;
NSString *streetAddress;
NSString *zip;
NSNumber *customerID;
NSString *firstName;
NSString *lastName;
CreditCard *creditCard;
NSMutableArray *rentals;
The default mechanism for assigning unique primary keys is provided with the EOAdaptorChannel method primaryKeyForNewRowWithEntity (or primaryKeyForNewRowWithEntity: in Objective-C). If you need to provide a custom mechanism for assigning primary keys, you can implement the EODatabaseContext delegate method databaseContextNewPrimaryKey (or databaseContext:newPrimaryKeyForObject:entity: in Objective-C). Using either of these two techniques means you don't need to store the primary key in your enterprise object. For more discussion of primary keys, see the chapter "Answers to Common Design Questions".
public NSGregorianDate memberSince() {In Objective-C (from Member.m)
willChange();
return memberSince;
}
public void setMemberSince(NSGregorianDate value) {
willChange();
memberSince = value;
}
public CreditCard creditCard() {
willChange();
return creditCard;
}
public void setCreditCard(CreditCard value) {
willChange();
creditCard = value;
}
public NSArray rentals() {
willRead();
return rentals;
}
public void setRentals(NSMutableArray value) {
willChange();
rentals = value;
}
public void addToRentals(Rental object) {
willChange();
rentals.addObject(object);
}
public void removeFromRentals(Rental object) {
willChange();
rentals.removeObject(object);
}
- (void)setMemberSince:(NSCalendarDate *)valueFeatures introduced by these code excerpts are discussed in the following sections.
{
[self willChange];
[memberSince autorelease];
memberSince = [value retain];
}
- (NSCalendarDate *)memberSince { return memberSince; }
- (void)setCreditCard:(CreditCard *)value
{
// a to-one relationship
[self willChange];
[creditCard autorelease];
creditCard = [value retain];
}
- (CreditCard *)creditCard { return creditCard; }
- (void)addToRentals:(Rental *)object
{
// a to-many relationship
[self willChange];
[rentals addObject:object];
}
- (void)removeFromRentals:(Rental *)object
{
// a to-many relationship
[self willChange];
[rentals removeObject:object];
}
- (NSArray *)rentals { return rentals; }
Note: You don't have to provide accessor methods for your object's properties. The Framework can access your object's properties directly (through its instance variables). For more information, see the section "Accessing an Enterprise Object's Data"."
In Enterprise Objects Framework, objects that need to know about changes to an enterprise object register as observers for change notifications. When an enterprise object is about to change, it has the responsibility of posting a change notification so that registered observers are notified. To do this, enterprise objects should invoke the method willChange prior to altering their state. This is invoked by default in generated source code's "set" methods, but whenever you add your own methods that change the object's state, you need to remember to include an invocation.
An implementation of willChange is provided in EOCustomObject (or NSObject in Objective-C), so you don't have to implement it yourself. For more information on change notification, see the specification for the control layer's EOObserverCenter class in Enterprise Objects Framework Reference.
Note: If you're using EOGenericRecord, you don't have to worry about invoking willChange. As the default enterprise object class, EOGenericRecord handles change notifications as part of its normal behavior.
In Java, the willRead method and a handful of others are defined in the Framework's EOFaulting interface. EOFaulting is a part of EOEnterpriseObject, so your custom classes inherit a default implementation of it. The default implementation of willRead checks to see if the receiver has already been fully initialized. If it hasn't been, it fills the object with values fetched from the database. Before your application attempts to message an object, you must ensure that it has its data. To do this, enterprise objects invoke the method willRead prior to any attempt to access the object's state, most typically in "get" methods. Enterprise Objects don't have to invoke willRead in "set" methods, because the willChange method invokes willRead for you.
For more information on faulting, see the interface specification for EOFaulting in Enterprise Objects Framework Reference. For more information on Objective-C's faulting mechanism, see the EOFault class specification.
For example, Customer can use its creditCard relationship to traverse the object graph and change values in the CreditCard object. If you want to access information about a Customer's credit card, your code can do something like this:
customer.creditCard().limit();In Objective-C:
[[member creditCard] limit];You can also modify attributes in the object graph regardless of what table they came from. For example, suppose that Customer's streetAddress attribute was flattened from an Address entity. Customer could have a setStreetAddress method that modified the attribute, even though it's not actually stored in the Customer table.
This data transport mechanism is defined by the EOKeyValueCoding interface (or informal protocol in Objective-C). It specifies that properties of an object are accessed indirectly by name (or key), rather than directly through invocation of an accessor method or as instance variables. EOKeyValueCoding is incorporated in the EOEnterpriseObject interfaces, so your custom classes automatically inherit key-value coding behavior that is sufficient for most purposes.
The basic methods for accessing an object's values are takeValueForKey and valueForKey (takeValue:forKey: and valueForKey: in Objective-C), which set or return the value for the specified key, respectively. The default implementations use the accessor methods normally implemented by objects (or to access instance variables directly if need be), so that you don't have to write special code simply to integrate your objects into the Enterprise Objects Framework.
There are corresponding methods takeStoredValueForKey and storedValueForKey (takeStoredValue:forKey: and storedValueForKey: in Objective-C) are similar, but they're considered to be private to the Framework and to the enterprise object class that provides them. The stored value methods are for use by the Framework for transporting data to and from trusted sources. For example, takeStoredValueForKey is used to initialize an object's properties with values fetched from the database, whereas takeValueForKey is used to modify an object's properties to values provided by a user.
Public Access with the Basic Methods
When accessing an object's class properties, the default implementations of the basic key-value coding methods use the class definition. They look for "set" methods in the following sequence:
setProperty _setPropertywhere property is the name of the property as in lastName. Note that the first letter of the property name is made uppercase as in setLastName.
Similarly, the key-value coding methods look for "get" methods in the following sequence:
getProperty property _getProperty _propertyIf no accessor methods can be found, the key-value coding methods look for instance variables containing the property's name and sets or retrieves their value directly. The search order for instance variables is as follows:
property _propertywhere property is the name of the property as in lastName.
By using the accessor methods you normally define for your objects, the default implementations of the key-value coding methods allow you to concentrate on implementing custom behavior. They also allow your objects to determine how their properties are accessed. For example, your Employee class can define a salary method that just returns an employee's salary directly or calculates it from another value.
Note: You shouldn't use "set" methods to perform validation. Rather, you should use the validation methods, described in "Performing Validation".
Private Access with the Stored Value Methods
The stored value methods, takeStoredValueForKey and storedValueForKey, are used by the framework to store and restore an enterprise object's properties, either from the database or from an in-memory snapshot (for undoing changes to an object and for synchronizing objects between child and parent editing contexts in a nested configuration, for example).This access is considered private to the enterprise object and is invoked by the Framework to effect persistence on the object's behalf. On the other hand, the basic key-value coding methods are the public API to an enterprise object. They are invoked by clients external to the object, such as for interactions with the user interface or with other enterprise objects.
Like the basic key-value coding methods, the stored value methods access an object's properties by invoking property-specific accessor methods or by directly accessing instance variables. However, the stored value methods use a different search order for resolving the property key: they search for a private accessor first (a method beginning with an underbar), then for an instance variable, and finally for a public accessor. Enterprise object classes can take advantage of this distinction to simply set or get values when properties are accessed through the private API (on behalf of a trusted source) and to perform additional processing when properties are accessed through the public API. Put another way, the stored value methods allow you bypass the logic in your public accessor methods, whereas the basic key-value coding methods execute that logic.
The stored value methods are especially useful in cases where property values are interdependent. For example, suppose you need to update a total whenever an object's bonus property is set:
void setBonus(double newBonus) {In Objective-C:
willChange();
_total += (newBonus - _bonus);
_bonus = newBonus;
}
- (void)setBonus:(double)newBonus {This total-updating code should be activated when the object is updated with values provided by a user (through the user interface), but not when the bonus property is restored from the database. Since the Framework restores the property using takeStoredValueForKey and since this method accesses the _bonus instance variable in preference to calling the public accessor, the unnecessary (and possibly expensive and harmful) recomputation of _total is avoided. If the object actually wants to intervene when a property is set from the database, it has two options:
[self willChange];
_total += (newBonus - _bonus);
_bonus = newBonus;
}
One kind of behavior you might want to add to your enterprise object class is the ability to apply a filter to an object's to-many relationship property and return a subset of the destination objects. For example, you could have an overdueRentals method in the Customer class that returns the subset of a customer's rentals that are overdue:
public NSArray overdueRentals() {In Objective-C:
EOQualifier qualifier;
qualifier = new EOKeyValueQualifier(
"isOverdue",
EOQualifier.QualifierOperatorEqual,
new Boolean(true));
return EOQualifier.filteredArrayWithQualifier(
allRentals(),
qualifier);
}
- (NSArray *)overdueRentals {The overdueRentals method creates a qualifier to find all of a customer's overdue rentals. The qualifier is performed in memory against the complete set of a customer's rentals.
EOQualifier *qualifier;
qualifier = [[[EOKeyValueQualifier alloc]
initWithKey:@"isOverdue"
operatorSelector:EOQualifierOperatorEqual
value:[NSNumber numberWithBool:YES]autorelease];
return [[self allRentals]
filteredArrayUsingQualifier:qualifier];
}
With the addition of this method, Customer has a "get" accessor method with no corresponding "set" method. A setOverdueRentals method doesn't make sense because overdueRentals is derived. However, since Enterprise Objects Framework invokes accessor methods automatically, you may wonder if the absence of a setOverdueRentals method can cause problems. The answer is no, you don't have to define a corresponding set method. Enterprise Objects Framework will not attempt to invoke setOverdueRentals unless you bind the overdueRentals property to an editable user interface object.
The EOValidation interface (informal protocol in Objective-C) defines the way that enterprise objects validate their values. The validation methods can check for illegal value types, values outside of established limits, illegal relationships, and so on. Like EOKeyValueCoding, EOValidation is incorporated in the EOEnterpriseObject interface, so your custom classes automatically inherit some basic validation behavior. The default implementations of these methods check the object's state against constraints and rules defined in your model.
There are two kinds of validation methods. The first validates an entire object to see if it's ready for a specific operation (inserting, updating, and deleting) and the second validates individual properties. The two different types are discussed in more detail in the following sections. For more a more detailed discussion, see the EOValidation interface specification in the Enterprise Objects Framework Reference.
You might want to override these methods to perform additional validation to what can be specified in a model. For example, your application should never allow a fee to be deleted if it hasn't been paid. You could implement this in a Fee class as follows:
public void validateForDelete() throws EOValidation.ExceptionIn Objective-C:
{
EOValidation.Exception superException = null;
EOValidation.Exception myException = null;
try {
super.validateForDelete()
} catch (EOValidation.Exception s) {
superException = s;
}
if (!isPaid())
myException = new EOValidation.Exception(
"You can't remove an unpaid fee.");
if (superException && myException) {
NSMutableArray exceptions = new NSMutableArray();
exceptions.addObject(superException);
exceptions.addObject(myException);
throw
EOValidation.Exception.aggregateExceptionWithExceptions(
exceptions);
} else if (superException) {
throw(superException);
} else if (myException) {
throw(myException);
}
}
- (NSException *)validateForDeleteThe default implementation of validateForDelete inherited by your custom classes performs basic checking based on referential integrity rules specified in your model. Your custom classes should therefore invoke super's implementation before performing their own validation as and should combine any exception thrown by super's implementation with their own as shown above.
{
NSException *superException = [super validateForSave];
NSException *myException = nil;
if (![self isPaid])
myException = [NSException validationExceptionWithFormat:
@"You can't remove an unpaid fee."];
if (superException && myException) {
return [NSException aggregateExceptionWithExceptions:
[NSArray arrayWithObjects:
superException, myException, nil];
} else if (superException) {
return superException;
} else if (myException) {
return myException)
}
return nil;
}
Note that the Java and Objective-C validation methods work slightly differently. The Java methods throw exceptions to indicate a validation failure whereas the Objective-C methods create and return exceptions for the Framework to raise.
The advantage of using one of the validateFor... methods is that they allow you to perform both "inter-" and "intra-" object validation before a particular operation occurs. This is useful if you have an enterprise object whose properties have interdependencies. For example, if you're saving rental data, you might want to confirm that the check out date precedes the return date before committing the changes.
public void validateStreetAddress(String address)In Objective-C:
throws EOValidation.Exception
{
if (/** address is invalid */)
throw new EOValidation.Exception("Invalid address.");
}
- (NSException *)validateStreetAddress:(NSString *)addressIf a user tries to assign a value to streetAddress, the default implementation of validateValueForKey invokes the validateStreetAddress method. Based on the result, the operation is either accepted or refused.
{
if (/* address is invalid...*/)
return [NSException validationExceptionWithFormat:
@"Invalid address."];
return nil;
}
Prior to invoking your validateKey methods, the default implementation of validateValueForKey validates the property against constraints specified in your model (such as NULL constraints).
Note that the default implementations of validateForSave, validateForInsert, and validateForUpdate provided by EOCustomObject (NSObject in Objective-C) invoke validateValueForKey for each of an object's class properties.
Additionally, you can design your Application Kit and Java client applications so that the validation in your enterprise objects is performed as soon as a user attempts to leave an editable field in the user interface. There are two steps to doing this:
Note that in this scenario, validateValueForKey is invoked with values directly from the user interface. When validateValueForKey is invoked from validateForSave, validateForInsert, or validateForUpdate, it's invoked with values that are already in the object.
To create an instance of a custom Java enterprise object class, you invoke a constructor of the following form:
public MyCustomEO (Typically you invoke this constructor with null arguments as follows:
EOEditingContext anEOEditingContext,
EOClassDescription anEOClassDescription,
EOGlobalID anEOGlobalID)
rental = new MyCustomEO(null, null, null);You can provide real values, but most enterprise object classes don't make use of the arguments. The only common exception to this is EOGenericRecord, which requires the arguments to identify the object's class description. EOGenericRecord is handled in a different way, as described later in this section.
In Objective-C, you use alloc and init like you would with any other object, so you don't have to worry about providing arguments.
Once you create an object, you insert it into an EOEditingContext using EOEditingContext's insertObject method (insertObject: in Objective-C). For example, the following code excerpts for the Customer class create Fee and Rental enterprise objects as the by-product of a customer renting a unit at a video store. Once the objects have been created, they're inserted into the current enterprise object's EOEditingContext.
public void rentUnit(Unit unit)In Objective-C:
{
EOEditingContext ec;
Fee fee;
Rental rental;
ec = editingContext();
// Create new objects and insert them into the editing context
fee = new Fee(null, null, null);
ec.insertObject(fee);
rental = new Rental(null, null, null);
ec.insertObject(rental);
// Manipulate relationships
rental addObjectToBothSidesOfRelationshipWithKey(fee, "fees");
rental.addObjectToBothSidesOfRelationshipWithKey(unit, "unit");
addObjectToBothSidesOfRelationshipWithKey(rental, "rentals");
}
- (void)rentUnit:(Unit *)unitEOEditingContext's insertObject method has the effect of registering the specified enterprise object with the editing context. In other words, it registers the object to be inserted in the database the next time the editing context saves changes. Inserting a new object into an editing context also has the side effect of further initializing the object, so you should always insert new objects into an editing context before modifying them (such as by manipulating their relationships). For more information, see the section "Setting Defaults for New Enterprise Objects".
{
EOEditingContext *ec;
Fee *fee;
Rental *rental;
ec = [self editingContext];
// Create new objects and insert them into the editing context
fee = [[[Fee alloc] init] autorelease];
[ec insertObject:fee];
rental = [[[Rental alloc] init] autorelease];
[ec insertObject:rental];
[rental addObject:fee
toBothSidesOfRelationshipWithKey:@"fees"];
[rental addObject:unit
toBothSidesOfRelationshipWithKey:@"unit"];
[self addObject:rental
toBothSidesOfRelationshipWithKey:@"rentals"];
}
String entityName; // Assume this exists.In Objective-C:
EOEditingContext editingContext; // Assume this exists.
EOClassDescription classDescription;
EOEnterpriseObject eo;
classDescription = EOClassDescription.classDescriptionForEntityName(entityN ame);
eo = classDescription.createInstanceWithEditingContext(
editingContext, null);
if (eo) editingContext.insertObject(eo);
NSString *entityName; // Assume this exists.The EOClassDescription method createInstanceWithEditingContext is preferable to using an EOGenericRecord constructor (or to using EOGenericRecord's init... method in Objective-C) because the same code works if you later use a custom enterprise object class instead of EOGenericRecord. Strictly speaking, it's better to use EOClassDescription's createInstanceWithEditingContext for all types of enterprise objects-EOGenericRecords as well as your custom classes. The createInstanceWithEditingContext method is the most general way to create an enterprise object-it works regardless of whether the object is represented by a custom class or an EOGenericRecord.
EOEditingContext *editingContext; // Assume this exists.
EOClassDescription classDescription;
id eo;
classDescription = [EOClassDescription
classDescriptionForEntityName:entityName];
eo = [classDescription
createInstanceWithEditingContext:editingContext
globalID:nil
zone:[editingContext zone]];
if (eo) [editingContext insertObject:eo];
addObjectToBothSidesOfRelationshipWithKey is used to manage reciprocal relationships, in which the destination entity of a relationship has a back reference to the source. For example, Fee and Unit both have back references to Rental, and Rental has a back reference to Customer. In other words, not only does the model define a relationship from Customer to Fee and Rental, it also defines a relationship from Rental back to Customer and from Fee to Rental. When you insert an object into a relationship (such as adding a new Rental object to Customer's rentals relationship property, which is an NSArray) and the inserted object has a back reference to the enterprise object, you need to be sure to add the object to both sides of the relationship. Otherwise, your object graph will get out of sync-your Customer object's rentals array will contain the Rental object, but the Rental object won't know the Customer who rented it.
You can update object references explicitly-that is, you can directly update the Rental object's customer property, which represents its relationship to the Customer object. But it's simpler to just use addObjectToBothSidesOfRelationshipWithKey. This method is safe to use regardless of whether the source relationship is to-one or to-many, whether the reciprocal relationship is to-one or to-many, or whether the relationship is reciprocal at all.
You should observe the following guidelines in working with relationships:
This method follows the same pattern as addObjectToPropertyWithKey. That is, it looks for a selector of the form removeFromProjects, and then for a property called projects. If neither of these can be found, it throws an exception. If it finds the property but it doesn't contain the specified object, this method simply returns.
The method removeObjectFromBothSidesOfRelationshipWithKey (removeObject:fromBothSidesOfRelationshipWithKey: in Objective-C) removes a specified object from both sides of a relationship property with the specified name. Like addObjectToBothSidesOfRelationshipWithKey, this method is safe to use regardless of whether the source relationship is to-one or to-many, or whether the reciprocal relationship is to-one or to-many.
To assign default values to newly created enterprise objects, you use the method awakeFromInsertionInEditingContext (awakeFromInsertionInEditingContext: in Objective-C). This method is automatically invoked immediately after your enterprise object class creates a new object and inserts it into the EOEditingContext.
The following implementation of awakeFromInsertion in the Member class sets the current date as the value of the memberSince property:
public void awakeFromInsertion(EOEditingContext ec)In Objective-C:
{
super.awakeFromInsertion(ec);
// Assign current date to memberSince
if (memberSince == null)
memberSince = new NSGregorianDate();
}
- (void)You use the awakeFromInsertion method to set default values for enterprise objects that represent new data. For enterprise objects that represent existing data, the Enterprise Objects Framework provides the method awakeFromFetch (awakeFromFetchInEditingContext: in Objective-C), which is sent to an enterprise object that has just been created from a database row and initialized with database values. Your enterprise objects can implement this method to provide additional initialization. Because the Framework is still busy fetching when an enterprise object receives awakeFromFetch, the object must be careful about sending messages to other enterprise objects that might need to be faulted in. For more information about faulting, see the chapter "Behind the Scenes" and the EOFaulting interface specification in the Enterprise Objects Framework Reference.
awakeFromInsertionInEditingContext:(EOEditingContext *)ec
{
[super awakeFromInsertionInEditingContext:ec];
// Assign current date to memberSince
if (!memberSince)
memberSince = [[NSCalendarDate date] retain];
}
However you can take advantage of extra information available at the time your enterprise object is initialized. In Java, instances of custom classes must provide a constructor of the following form:
public MyCustomEO (The Framework uses such a constructor to create your objects, so you can use the provided arguments to influence the creation. In Objective-C, enterprise objects can be created with either init or initWithEditingContext:classDescription:globalID:. If an enterprise object implements the initWithEditingContext:classDescription:globalID: method, the Framework uses this method instead of init, allowing the object to affect its creation based on the data provided.
EOEditingContext anEOEditingContext,
EOClassDescription anEOClassDescription,
EOGlobalID anEOGlobalID)
public void pay()In Objective-C:
{
setDatePaid(new NSGregorianDate());
// Notify the rental that this fee has been paid.
rental.feePaid();
}
- (void)payWhen you implement a method such as this in your enterprise object class, you don't have to be concerned with registering the changes or updating the values displayed in the user interface. An Enterprise Objects Framework application revolves around the graph of enterprise objects, and the Framework assumes responsibility for making sure that all parts of your application are notified when an object's value changes. Thus, all parts of your application have the same view of the data and remain in sync.
{
[self setDatePaid:[[NSCalendarDate calendarDate] retain];
// Notify the rental that this fee has been paid.
[rental feePaid];
}
Table of Contents Next Section