WebObjects 5: Saving an Enterprise Object with NSArray or NSMutableArray as a Custom Value Class Causes Exception

This document provides an example to workaround an issue in WebObjects 5 where saving an Enterprise Object (EO) Class that has either a NSArray or a NSMutableArray as a custom value class for the attribute in an entity creates an exception.
Symptom

Using an NSArray or an NSMutableArray as a custom value class for an EOAttribute in an EOEntity with the conversion method "archiveData" causes the following exception when saving:


and the conversion exception is :

Solution

In WebObjects 4.0 - 4.5 there is an Objective-C category to NSObject that implemented the method "archiveData". This category is not available in WebObjects 5. To get the same function, you must create a subclass of NSMutableArray and add the conversion methods and user's implemention to that class. The example below shows how a custom class called CustomMutableArray is created which extends NSMutableArray and implements the archiving method "archiveData".

//
// CustomMutableArray.java
//

package com.mycompany.utilities;

import java.io.*;
import java.util.*;
import com.webobjects.foundation.*;

/**
* This class demonstrates how to create a custom value class.
* This particular class is a subclass of NSMutableArray which can be used
* within EOModeler.
*/

public class CustomMutableArray extends NSMutableArray {
    /** This helps create the ByteArrayOutputStream with a good space estimate. */
    private static final int kOverheadAdjustment = 512;

    /** This also helps create the ByteArrayOutputStream with a good space estimate. */
    private static final int kCountBytesFactor = 16;

    /** This determines, when an error occurs, if we should throw an NSForwardException or just return null. */
    private static final boolean kThrowOnError = true;

    /**
    * This is the factory method.  Rename as appropriate from your EOModel.
    * It uses java Serialization to turn bytes from an NSData into a reconstituted Object.
    *
    * @param data This is the NSData holding the previously serialized bytes.
    * @return The un-serialized Object.
    */

    public static Object objectWithArchiveData(NSData data) {
ByteArrayInputStream bis = new ByteArrayInputStream(data.bytes());
Object result = null;
Throwable exception = null;
try {
   ObjectInputStream ois = new ObjectInputStream(bis);
   result = ois.readObject();
} catch (IOException ioe) {
   exception = ioe;
} catch (ClassNotFoundException cnfe) {
   exception = cnfe;
}

if (exception != null) {
   if
(NSLog.debugLoggingAllowedForLevelAndGroups(NSLog.DebugLevelCritical,
NSLog.DebugGroupArchiving)) {
NSLog.debug.appendln(exception);
   }

   if (kThrowOnError) {
throw new NSForwardException(exception);
   }
}

return result;
    }

    /**
    * This is the conversion method.  Rename as appropriate from your EOModel.
    * It uses java Serialization to serialize this object into bytes within an NSData.
    *
    * @return An NSData object containing the serialized bytes of this object.
    */

    public NSData archiveData() {
ByteArrayOutputStream bos = new
ByteArrayOutputStream(kOverheadAdjustment + count() * kCountBytesFactor);
NSData result = null;
try {
   ObjectOutputStream oos = new ObjectOutputStream(bos);
   oos.writeObject(this);
   oos.flush();
   oos.close();
   result = new NSData(bos.toByteArray());
} catch (IOException ioe) {
   if
(NSLog.debugLoggingAllowedForLevelAndGroups(NSLog.DebugLevelCritical,
NSLog.DebugGroupArchiving)) {
NSLog.debug.appendln(ioe);
   }

   if (kThrowOnError) {
throw new NSForwardException(ioe);
   }
}

return result;
    }
}

Published Date: Feb 18, 2012