001    /*
002      Copyright (C) 2002-2003 Laurent Martelli <laurent@aopsys.com>
003    
004      This program is free software; you can redistribute it and/or modify
005      it under the terms of the GNU Lesser General Public License as
006      published by the Free Software Foundation; either version 2 of the
007      License, or (at your option) any later version.
008    
009      This program is distributed in the hope that it will be useful,
010      but WITHOUT ANY WARRANTY; without even the implied warranty of
011      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012      GNU Lesser General Public License for more details.
013    
014      You should have received a copy of the GNU Lesser General Public License
015      along with this program; if not, write to the Free Software
016      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
017    
018    package org.objectweb.jac.aspects.persistence;
019    
020    import java.util.Arrays;
021    import java.util.Collection;
022    import java.util.HashMap;
023    import java.util.HashSet;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Set;
028    import java.util.Vector;
029    import org.aopalliance.intercept.ConstructorInvocation;
030    import org.aopalliance.intercept.MethodInvocation;
031    import org.apache.log4j.Logger;
032    import org.objectweb.jac.core.AspectComponent;
033    import org.objectweb.jac.core.Interaction;
034    import org.objectweb.jac.core.NameRepository;
035    import org.objectweb.jac.core.Wrappee;
036    import org.objectweb.jac.core.Wrapping;
037    import org.objectweb.jac.core.rtti.ClassItem;
038    import org.objectweb.jac.core.rtti.CollectionItem;
039    import org.objectweb.jac.core.rtti.FieldItem;
040    import org.objectweb.jac.core.rtti.MethodItem;
041    import org.objectweb.jac.util.ExtArrays;
042    
043    /**
044     * This wrapper defines persistence extensions for objects that a
045     * defined persitent by a persistent aspect component. */
046    
047    public class PersistenceWrapper extends AbstractPersistenceWrapper {
048        static final Logger loggerRef = Logger.getLogger("persistence.ref");
049        static final Logger loggerCol = Logger.getLogger("persistence.col");
050        static final Logger loggerWrap = Logger.getLogger("persistence.wrap");
051    
052        public final static String[] toWrapForLists =
053        new String[] {
054            "add",
055            "addAll",
056            "clear",
057            "contains",
058            "get",
059            "remove",
060            "removeRange",
061            "set",
062            "size",
063            "indexOf",
064            "lastIndexOf",
065            "isEmpty",
066            // -- preloads --
067            "toArray", "iterator", "clone" /*"equals"*/
068        };
069    
070        public final static String[] toWrapForSets =
071        new String[] { "add", "clear", "contains", "remove", "size", "isEmpty",
072                       // -- preloads --   
073                       "toArray", "iterator", "clone" /*"equals"*/
074        };
075    
076        public final static String[] toWrapForMaps =
077        new String[] {
078            "clear",
079            "isEmpty",
080            "size",
081            "containsKey",
082            "containsValue",
083            "get",
084            "put",
085            "remove",
086            // -- preloads --       
087            "keySet", "entrySet", "values", "clone" /*"equals"*/
088        };
089    
090        public PersistenceWrapper(AspectComponent ac) {
091            super(ac);
092        }
093    
094        boolean doStatics = false;
095    
096        public PersistenceWrapper(AspectComponent ac, Boolean doStatics) {
097            super(ac);
098            this.doStatics = doStatics.booleanValue();
099        }
100    
101        //public static final String ATTR_ADDED = "persistence.added";
102    
103        // names of already loaded collections
104        private HashSet loadedVectors = new HashSet();
105        // names of already loaded references
106        private HashSet loadedReferences = new HashSet();
107        // oids of not yet loaded references (FieldItem -> oid)
108        private HashMap notloadedReferences = new HashMap();
109    
110        static boolean isWrapped(Wrappee wrappee, FieldItem field) {
111            Object value = field.get(wrappee);
112            if (value instanceof Wrappee
113                && Wrapping.isExtendedBy(
114                    (Wrappee) value,
115                    null,
116                    CollectionWrapper.class)) {
117                logger.debug(field + " is wrapped");
118                return true;
119            } else {
120                logger.debug(field + " is not wrapped");
121                return false;
122            }
123        }
124    
125        /**
126         * This wrapping method is called on static objects constructors as
127         * defined by the pointcut of <code>PersistenceAC</code>.
128         *
129         * @see PersistenceAC#registerStatics 
130         */
131        public Object handleStatic(Interaction interaction) {
132            logger.debug("handle static "+interaction.wrappee.getClass().getName());
133            Object ret = interaction.proceed();
134            //wrap(null,true);
135            try {
136                String name = NameRepository.get().getName(interaction.wrappee);
137                ClassItem cli = cr.getClass(interaction.wrappee);
138                Storage storage = getStorage(cli);
139                OID oid = storage.getOIDFromName(name);
140                if (oid == null) {
141                    oid = storage.createObject(cli.getName());
142                    setOID(interaction.wrappee, oid);
143                    storage.bindOIDToName(oid, name);
144                    logger.debug(interaction.wrappee.getClass().getName() + " isNew");
145                    CollectionItem[] array = new CollectionItem[0];
146                    wrapCollections(interaction.wrappee, oid, true);
147                    initAllFields(interaction.wrappee, oid);
148                } else {
149                    setOID(interaction.wrappee, oid);
150                    // Call loadAllFields before wrapCollections so that it
151                    // preloads the OID of collections
152                    loadAllFields(interaction.wrappee, oid);
153                    wrapCollections(interaction.wrappee, oid, false);
154                }
155            } catch (Exception e) {
156                logger.error("handleStatic "+interaction,e);
157            }
158            return ret;
159        }
160    
161        /**  
162         * Makes an object persistent if it is not already. Is is assigned
163         * an OID, and its members are saved in the storage.
164         * @param wrappee the object to make persistent 
165         */
166        public OID makePersistent(Wrappee wrappee) throws Exception {
167            OID oid = null;
168                
169            logger.debug("makePersistent(" + wrappee + ")");
170            oid = getOID(wrappee);
171            if (oid!=null) {
172                logger.debug(wrappee + " is already persistent");
173            } else {
174                ClassItem cli = cr.getClass(wrappee);
175                Storage storage = getStorage(cli);
176                oid = storage.createObject(cli.getName());
177                setOID(wrappee, oid);
178                storage.bindOIDToName(
179                    oid,
180                    NameRepository.get().getName(wrappee));
181                logger.debug(wrappee + " isNew");
182    
183                // Initialize loadedVectors and loadedReferences.
184                // It's important to do this as early as possible, in case
185                // someone accesses them.
186    
187                FieldItem[] refs = cli.getReferences();
188                for (int i = 0; i < refs.length; i++) {
189                    loadedReferences.add(refs[i].getName());
190                }
191                CollectionItem[] colls = cli.getCollections();
192                for (int i = 0; i < colls.length; i++) {
193                    loadedVectors.add(colls[i].getName());
194                }
195    
196                wrapCollections(wrappee, oid, true);
197                initAllFields(wrappee, oid);
198            }
199    
200            return oid;
201        }
202    
203        /**
204         * Load all the fields of an object from the storage and initialize
205         * the object.
206         *
207         * @param wrappee the object
208         */
209    
210        public void loadAllFields(Wrappee wrappee, OID oid) throws Exception {
211            logger.debug("loadAllFields(" + oid + ")");
212            String lClassID = wrappee.getClass().getName();
213    
214            // Load all fields from the storage
215            ClassItem cli = cr.getClass(lClassID);
216    
217            // this should be faster because only one call to the storage is done
218            FieldItem[] fields = cli.getFields();
219            if (fields.length > 0) {
220                Storage storage = oid.getStorage();
221                StorageField[] values = storage.getFields(oid, cli, fields);
222                for (int i = 0; i < values.length; i++) {
223                    if (values[i] != null) {
224                        FieldItem field = values[i].fieldID;
225                        if (field.getActualField() != null) {
226                            try {
227                                boolean force =
228                                    PersistenceAC.isFieldPreloaded(field);
229                                if (field.isPrimitive()
230                                    && values[i].value != null) {
231                                    // TODO: preload for primitive fields here
232                                    if (field.setConvert(wrappee, values[i].value)) {
233                                        logger.warn(oid+"."+field.getName()+
234                                                    " value converted from "+values[i].value.getClass()+
235                                                    " to "+field.getType());
236                                        storage.updateField(oid, field, field.get(wrappee));
237                                    }
238                                } else if (field.isReference()) {
239                                    if (!field.hasAccessingMethods()) {
240                                        // load a reference if it has no getter
241                                        getReference(wrappee, oid, field);
242                                    } else {
243                                        // test if reference must be loaded here
244                                        // instead of being loaded when accessed
245                                        if (force) {
246                                            getReference(wrappee, oid, field);
247                                        } else {
248                                            logger.debug("storing OID for reference "
249                                                         + field
250                                                         + "="
251                                                         + values[i].value);
252                                            notloadedReferences.put(
253                                                field,
254                                                values[i].value);
255                                        }
256                                    }
257                                } else if (field instanceof CollectionItem) {
258                                    if (!((CollectionItem) field)
259                                        .isWrappable(wrappee)) {
260                                        logger.debug("loadAllFields -> invoking getCollection for "
261                                                     + field);
262                                        Wrapping.invokeRoleMethod(
263                                            wrappee,
264                                            "getCollection",
265                                            new Object[] { field });
266                                    } else {
267                                        // test if collection must be loaded here
268                                        // instead of being loaded when accessed
269                                        if (force) {
270                                            getCollection(
271                                                wrappee, oid,
272                                                (CollectionItem) field);
273                                        } else {
274                                            logger.debug("storing OID for collection "
275                                                         + field
276                                                         + "="
277                                                         + values[i].value);
278                                            notloadedReferences.put(
279                                                field,
280                                                values[i].value);
281                                        }
282                                    }
283                                }
284                            } catch (Exception e) {
285                                logger.error(
286                                    "Failed to load field "+ field.getLongName()
287                                    + " for " + oid,
288                                    e);
289                            }
290                        }
291                    }
292                }
293            }
294        }
295    
296        /**
297         * This role method wraps all the collection of the current wrappee
298         * so that they will trigger the needed loads from the storage when
299         * they are accessed.
300         *
301         * @param wrappee the object whose collections to wrap
302         * @param oid the oid of wrappee
303         * @param isNew must true if the object is newly created, false if
304         * loaded from storage 
305         */
306        public void wrapCollections(Wrappee wrappee, OID oid, boolean isNew) {
307            logger.debug("wrapCollections("
308                         + wrappee.getClass().getName()
309                         + "), isNew=" + isNew);
310            // Wrap collections
311            try {
312                ClassItem cli = cr.getClass(wrappee);
313                Vector collectionsToInit = new Vector();
314                CollectionItem[] collections = cli.getCollections();
315                Storage storage = oid.getStorage();
316                for (int i = 0; i < collections.length; i++) {
317                    CollectionItem collection = collections[i];
318                    if (collection.isTransient())
319                        continue;
320                    Object coll_value = collections[i].get(wrappee);
321                    Class collType = collections[i].getType();
322                    if (coll_value == null) {
323                        logger.warn(
324                            "uninitialized collection " +collections[i].getLongName()+
325                            ". Do you have a constructor with no parameters?");
326                        // uninitialized field
327                        if (collType.isArray()) {
328                            coll_value =
329                                java.lang.reflect.Array.newInstance(
330                                    collType.getComponentType(),
331                                    0);
332                        } else {
333                            try {
334                                // This will fail if the declared type of the
335                                // attribute is an interface
336                                if (!collType.isInterface()) {
337                                    coll_value = collType.newInstance();
338                                }
339                            } catch (Exception e) {
340                                logger.error("wrapCollections "+wrappee+"("+oid+")",e);
341                            }
342                        }
343                        collections[i].set(wrappee, coll_value);
344                    }
345                    Class actualCollType = coll_value.getClass();
346                    if (//isWrapped(collections[i]) || 
347                    actualCollType
348                        == org.objectweb.jac.lib.java.util.Vector.class
349                        || actualCollType
350                            == org.objectweb.jac.lib.java.util.HashMap.class
351                        || actualCollType
352                            == org.objectweb.jac.lib.java.util.Hashtable.class
353                        || actualCollType
354                            == org.objectweb.jac.lib.java.util.HashSet.class) {
355                        OID coll_oid;
356                        if (isNew) {
357                            coll_oid =
358                                storage.createObject(
359                                    collections[i].getType().getName());
360                        } else {
361                            coll_oid =
362                                (OID) notloadedReferences.get(collections[i]);
363                            if (coll_oid == null)
364                                coll_oid =
365                                    (OID) storage.getField(
366                                        oid,
367                                        collections[i]);
368                            else
369                                notloadedReferences.remove(collections[i]);
370                            // classes may change after objects were created and
371                            // made persistent
372                            if (coll_oid == null) {
373                                coll_oid = initCollection(wrappee, oid, collections[i]);
374                                logger.debug(
375                                          "OID of new collection "+collections[i]+": "+coll_oid);
376                            }
377                        }
378    
379                        logger.debug(collections[i] + " -> " + coll_oid);
380    
381                        boolean preloaded =
382                            PersistenceAC.isFieldPreloaded(collections[i]);
383                        String[] toWrap;
384                        CollectionWrapper wrapper;
385                        if (collections[i].isList()) {
386                            // lists
387                            logger.debug(
388                                "wrapping List "+ collections[i].getName()
389                                + " " + System.identityHashCode(coll_value));
390                            wrapper =
391                                new ListWrapper(getAspectComponent(), 
392                                                wrappee, 
393                                                collection, 
394                                                preloaded);
395                            toWrap = toWrapForLists;
396                        } else if (collections[i].isSet()) {
397                            // sets
398                            logger.debug("wrapping Set " + collections[i].getName());
399                            wrapper =
400                                new SetWrapper(getAspectComponent(), 
401                                               wrappee, 
402                                               collection, 
403                                               preloaded);
404                            toWrap = toWrapForSets;
405                        } else if (collections[i].isMap()) {
406                            // maps
407                            logger.debug("wrapping Map " + collections[i].getName());
408                            wrapper =
409                                new MapWrapper(getAspectComponent(), 
410                                               wrappee,
411                                               collection, 
412                                               preloaded);
413                            toWrap = toWrapForMaps;
414                        } else {
415                            throw new Exception("unsuported collection type");
416                        }
417    
418                        Wrappee coll = (Wrappee) coll_value;
419                        setOID(coll, coll_oid);
420                        if (coll != null) {
421                            Wrapping.wrap(coll, null, wrapper);
422                            Wrapping.wrap(coll, wrapper, toWrap);
423                            //Wrapping.wrap(map,wrapper,"memorizeUseDate",preloads);
424                        } else {
425                            logger.warn(
426                                "Collection "
427                                    + cli.getName() + "." + collections[i].getName()
428                                    + " is null, not wrapped");
429                        }
430                    }
431                }
432            } catch (Exception e) {
433                logger.error("wrapCollections "+wrappee+"("+oid+")",e);
434            }
435        }
436    
437        /**
438         * This wrapping method apply the persistence aspect for a given
439         * call on a persistent object.
440         *
441         * <p>It uses the RTTI aspect to determine the accessed and written
442         * field types.<p>
443         *
444         * Default behavior is:<p>
445         *
446         * <ul>
447         * <li>get the fields that are accessed for reading by this wrapped
448         * method
449         * <li>if the field is a collection, get the collection from the
450         * storage
451         * <li>if the field is a reference, get the referenced object from
452         * the storage
453         * <li>proceed the wrappee (and other wrappers if needed)
454         * <li>get the fields that were written by this wrapped method
455         * <li>if the field is a collection, update the storage depending
456         * on the wrapped method type (adder or remover)
457         * <li>if the field is a reference, update the storage to change
458         * the referenced object
459         * <li> if the field is a simple field, just update the storage
460         * with its value.
461         * </ul>
462         *
463         * @return the value returned by the wrapped method
464         *
465         * @see org.objectweb.jac.core.rtti.MethodItem#getAccessedFields()
466         * @see #getCollection(Wrappee,OID,CollectionItem)
467         * @see #getReference(Wrappee,OID,FieldItem)
468         * @see #addToCollection(Interaction,OID,CollectionItem,Object)
469         * @see #removeFromCollection(Interaction,OID,CollectionItem,Object)
470         * @see #setReference(Wrappee,OID,FieldItem,Wrappee)
471         * @see #setField(Wrappee,OID,FieldItem)
472         * @see org.objectweb.jac.core.rtti.MethodItem#getWrittenFields()
473         * @see org.objectweb.jac.core.rtti 
474         */
475        public Object applyPersistence(Interaction interaction) throws Exception {
476            if (interaction.method.isStatic()) {
477                return proceed(interaction);
478            }
479            OID oid = getOID(interaction.wrappee);
480            logger.debug(
481                "applyPersistence on "
482                    + oid + "(" + interaction.wrappee + ")."
483                    + interaction.method);
484            MethodItem method = (MethodItem) interaction.method;
485            FieldItem[] fields;
486    
487            // the object may be wrapped but not persistent yet
488            if (oid != null) {
489    
490                ClassItem cli =
491                    cr.getClass(interaction.wrappee.getClass());
492                // preload accessed fields and collections
493                fields = method.getAccessedFields();
494                for (int i = 0; fields != null && i < fields.length; i++) {
495                    FieldItem field = fields[i];
496    
497                    if (!field.isTransient() && !field.isFinal()) {
498                        if (field instanceof CollectionItem
499                            && !isWrapped(interaction.wrappee, field)) {
500                            getCollection(
501                                interaction.wrappee, oid,
502                                (CollectionItem) field);
503                        } else if (field.isReference()) {
504                            getReference(interaction.wrappee, oid, field);
505                        }
506                    }
507                }
508            }
509    
510            Object result = proceed(interaction);
511    
512            oid = getOID(interaction.wrappee);
513            if (oid != null) {
514    
515                // save written fields
516                fields = method.getWrittenFields();
517                for (int i = 0; fields!=null && i<fields.length; i++) {
518                    FieldItem field = fields[i];
519                    if (!field.isTransient()) {
520                        logger.debug("Save written field in "
521                                + interaction.method + " : "
522                                + field.getName());
523                        if (field.isReference()) {
524                            setReference(
525                                interaction.wrappee, oid,
526                                field,
527                                (Wrappee) field.get(interaction.wrappee));
528                        } else if (field instanceof CollectionItem) {
529                            // Nothing to do if the collection is wrapped
530                            // BUT THE COLLECTION MUST BE SAVED IN THE CASE OF ARRAYS
531                            //     storage.updateCollection(collection);
532                            CollectionItem collection = (CollectionItem) field;
533                            if (collection.isArray()
534                                && ((MethodItem) interaction.method)
535                                    .getCollectionIndexArgument()
536                                    != -1) {
537                                updateCollection(
538                                    collection,
539                                    ((MethodItem) interaction.method)
540                                        .getCollectionIndexArgument());
541                            }
542                        } else {
543                            setField(interaction.wrappee, oid, field);
544                        }
545                    }
546                }
547    
548                // handle added collections (adder)
549                CollectionItem[] collections = method.getAddedCollections();
550                for (int i = 0;
551                    collections != null && i < collections.length;
552                    i++) {
553                    if (!collections[i].isTransient()
554                        && !isWrapped(interaction.wrappee, collections[i])) {
555                        addToCollection(
556                            interaction,
557                            oid,
558                            collections[i],
559                            interaction.args[0]);
560                    }
561                }
562                // handle removed collections (remover)
563                collections = method.getRemovedCollections();
564                for (int i = 0;
565                    collections != null && i < collections.length;
566                    i++) {
567                    if (!collections[i].isTransient()
568                        && !isWrapped(interaction.wrappee, collections[i])) {
569                        removeFromCollection(
570                            interaction,
571                            oid,
572                            collections[i],
573                            interaction.args[0]);
574                    }
575                }
576    
577                // UNCOMMENT THIS WHEN UNWRAP WILL BE THREAD-SAFE!!!
578                // unwrap the method if it was only a getter
579                //if ( ! method.hasAddedCollections() &&
580                //     ! method.hasRemovedCollections() &&
581                //     ! method.hasWrittenFields() )
582                //{
583                //   logger.debug("persistence.ref","unwrapping "+getOID()+" "+method);
584                //   Wrapping.unwrap(interaction.wrappee,this,"applyPersistence",method);
585                //}
586            }
587            return result;
588        }
589    
590        /**
591         * This method loads alls the persistent objects that are contained
592         * in this collection from the storage.
593         *
594         * <p>If the collection has already been loaded from the storage,
595         * do nothing.<p>
596         *
597         * @param collection the collection to load */
598    
599        public void getCollection(Wrappee wrappee, OID oid, CollectionItem collection)
600            throws Exception 
601        {
602            logger.debug(oid + ".getCollection(" + collection.getName() + ")");
603            if (!loadedVectors.contains(collection.getName())) {
604                String classID = collection.getParent().getName();
605                List vector = null;
606                Map map = null;
607                Storage storage = oid.getStorage();
608                OID cid = storage.getCollectionID(oid, collection);
609                loggerCol.debug("cid=" + cid);
610                if (cid == null) {
611                    // Handle the case of a new collection added in the model
612                    initCollection(wrappee, oid, collection);
613                }
614                if (collection.isList() || collection.isArray()) {
615                    vector = storage.getList(oid, collection);
616                } else if (collection.isSet()) {
617                    vector = storage.getSet(oid, collection);
618                } else if (collection.isMap()) {
619                    map = storage.getMap(oid, collection);
620                } else {
621                    logger.error(
622                        "unhandled collection type : " + collection.getType());
623                    logger.error(
624                        "please see the JAC programming guidelines for more details");
625                    //new Exception().printStackTrace();
626                    return;
627                }
628    
629                collection.clear(wrappee);
630    
631                if (collection.isMap()) {
632                    Iterator it = map.entrySet().iterator();
633                    while (it.hasNext()) {
634                        Map.Entry entry = (Map.Entry) it.next();
635                        attrdef(ATTR_ADDED, "true");
636                        collection.add(
637                            wrappee,
638                            normalizeOutput(entry.getValue()),
639                            normalizeOutput(entry.getKey()));
640                        attrdef(ATTR_ADDED, null);
641                    }
642                } else {
643                    for (int i = 0; i < vector.size(); i++) {
644                        attrdef(ATTR_ADDED, "true");
645                        loggerCol.debug(
646                            "adding "
647                                + normalizeOutput(vector.get(i))
648                                + " to " + collection
649                                + "(wrappee=" + wrappee + ")");
650                        collection.add(
651                            wrappee,
652                            normalizeOutput(vector.get(i)),
653                            null);
654                        attrdef(ATTR_ADDED, null);
655                    }
656                }
657                loadedVectors.add(collection.getName());
658            }
659        }
660    
661        /**
662         * This method stores the persistent object that is added to the
663         * collection into the storage.<p>
664         *
665         * @param interaction the current interaction
666         * @param oid the oid of the object holding the collection
667         * @param collection the collection to add to
668         * @param value the value to add to the collection
669         *
670         * @see #removeFromCollection(Interaction,OID,CollectionItem,Object)
671         * @see #getCollection(Wrappee,OID,CollectionItem)
672         */
673        protected void addToCollection(Interaction interaction,
674                                       OID oid,
675                                       CollectionItem collection,
676                                       Object value)
677            throws Exception 
678        {
679            logger.debug(oid + ".addToCollection: "
680                         + interaction.method + Arrays.asList(interaction.args));
681    
682            if (interaction.args.length == 1) {
683                // the method's arguments look correct
684                value = normalizeInput(value);
685                Storage storage = oid.getStorage();
686                OID cid =
687                    storage.getCollectionID(oid,collection);
688                if (collection.isList() || collection.isArray()) {
689                    storage.addToList(cid, value);
690                } else if (collection.isSet()) {
691                    storage.addToSet(cid, value);
692                } else {
693                    logger.error(
694                        "unhandled collection type : " + collection.getType(),new Exception());
695                }
696    
697            } else {
698                logger.error("NOT IMPLEMENTED YET !!!");
699                // the method's arguments are weird, so let's diff
700                //         storage.updateCollection(collection);
701            }
702        }
703    
704        /**
705         * This method delete the persistent object that is removed from
706         * the collection from the storage.<p>
707         *
708         * @param interaction the current interaction
709         * @param oid the oid of the object holding the collection
710         * @param collection the collection to remove from
711         * @param value the value to remove from the collection
712         *
713         * @see #addToCollection(Interaction,OID,CollectionItem,Object)
714         * @see #getCollection(Wrappee,OID,CollectionItem) 
715         */
716        protected void removeFromCollection(Interaction interaction,
717                                            OID oid,
718                                            CollectionItem collection,
719                                            Object value)
720            throws Exception 
721        {
722            logger.debug(oid + ".removeFromCollection " + interaction.method);
723    
724            value = normalizeInput(value);
725            Storage storage = oid.getStorage();
726            OID cid =
727                storage.getCollectionID(oid, collection);
728            if (collection.isList() || collection.isArray()) {
729                storage.removeFromList(cid, value);
730            } else if (collection.isSet()) {
731                storage.removeFromSet(cid, value);
732            } else {
733                logger.error(
734                    "unhandled collection type : " + collection.getType(), new Exception());
735            }
736        }
737    
738        /**
739         * Update the element of a collection at a gievn position
740         */
741        public void updateCollection(CollectionItem collection, int position)
742            throws Exception {
743            /*
744            checkOid();
745            logger.debug(
746                      getOID()+".updateCollection: "+method()+Arrays.asList(args()));
747            
748            if (isPersistent())
749            {
750               // the method's arguments look correct
751               value = normalizeInput(value);
752               OID cid = getStorage().getCollectionID(getOID(), collection);
753               if ( collection.isList() || collection.isArray() ) {
754                  Object value = collection.get(wrappee(),position);
755                  getStorage().setListItem(cid, position, value);
756               } else {
757                  logger.error("unhandled collection type : "+collection.getType());
758               }
759            }     
760            */
761        }
762    
763        /**
764         * This method loads the persistent object that is pointed by the
765         * reference from the storage.<p>
766         *
767         * If the reference has already been loaded from the storage, do
768         * nothing.<p>
769         *
770         * @param reference the reference to load
771         *
772         * @see #setReference(Wrappee,OID,FieldItem,Wrappee) 
773         */
774        public void getReference(Wrappee wrappee, OID oid, FieldItem reference)
775            throws Exception 
776        {
777            try {
778    
779                loggerRef.debug(
780                    oid + "[" + this + "].getReference("
781                    + reference.getName() + ")");
782                //System.out.println("GETREF: "+loadedReferences+" : "+notloadedReferences);
783    
784                if (!loadedReferences.contains(reference.getName())) {
785                    OID lOid = (OID) notloadedReferences.get(reference);
786                    if (lOid == null)
787                        lOid = (OID)oid.getStorage().getField(oid,reference);
788                    else
789                        notloadedReferences.remove(reference);
790                    loggerRef.debug(
791                        oid + "." + reference.getName()
792                        + " -> " + lOid
793                        + " (" + wrappee.getClass().getName() + ")");
794                    Object obj = null;
795                    if (lOid != null) {
796                        // What the hell is this ???  It bugs if a reference
797                        // is initialized by the default constructor
798                        //obj=getAC().getObject(lOid,reference.get(wrappee));
799                        obj = getAC().getObject(lOid, null);
800                    }
801                    attrdef(ATTR_ADDED, "true");
802                    try {
803                        reference.set(wrappee, obj);
804                    } catch (IllegalArgumentException illegal) {
805                        // This may happen if the type of the reference has been changed
806                        logger.error(
807                            "Incompatible types "
808                            + reference.getType() + " and " + obj);
809                    } finally {
810                        attrdef(ATTR_ADDED, null);
811                    }
812                    loadedReferences.add(reference.getName());
813                } else {
814                    loggerRef.debug(
815                        oid + "." + reference.getName()
816                        + " already loaded");
817                }
818            } catch (Exception e) {
819                logger.error("getReference "+oid+"."+reference.getName(),e);
820            }
821        }
822    
823        /**
824         * This method stores the persistent object that is pointed by the
825         * reference into the storage.<p>
826         *
827         * @param reference the reference to store
828         *
829         * @see #getReference(Wrappee,OID,FieldItem)
830         */
831        protected void setReference(Wrappee wrappee,
832                                    OID oid,
833                                    FieldItem reference,
834                                    Wrappee value)
835            throws Exception 
836        {
837            if (value != null) {
838                OID valoid = (OID)Wrapping.invokeRoleMethod(
839                    value,
840                    "makePersistent",
841                    ExtArrays.emptyObjectArray);
842                oid.getStorage().updateField(oid,reference,valoid);
843            } else {
844                oid.getStorage().updateField(oid, reference, null);
845            }
846            // We won't need to load the reference now
847            loadedReferences.add(reference.getName());
848            notloadedReferences.remove(reference);
849        }
850    
851        /**
852         * This method stores the field into the storage.<p>
853         *
854         * @param field the field to store 
855         */
856        protected void setField(Wrappee wrappee, OID oid, FieldItem field)
857            throws Exception 
858        {
859            Object value = field.get(wrappee);
860            if (value instanceof Wrappee) {
861                value = Wrapping.invokeRoleMethod(
862                    (Wrappee)value,
863                    "makePersistent",
864                    ExtArrays.emptyObjectArray);
865                
866            } 
867            oid.getStorage().updateField(oid,field,value);
868        }
869    
870        public void initCollections(Wrappee wrappee, OID oid, CollectionItem[] collections)
871            throws Exception 
872        {
873            logger.debug("wrappee = " + wrappee.getClass().getName());
874            logger.debug("collections = " + Arrays.asList(collections));
875            for (int i=0; i<collections.length; i++) {
876                if (!collections[i].isTransient()) {
877                    logger.debug("collections[" + i + "] = " + collections[i]);
878                    initCollection(wrappee, oid, collections[i]);
879                }
880            }
881        }
882    
883        OID initCollection(Wrappee wrappee, OID oid, CollectionItem collection)
884            throws Exception 
885        {
886            logger.debug("Init collection " + collection.getName());
887            loadedVectors.add(collection.getName());
888            Storage storage = oid.getStorage();
889            OID cid = null;
890            if (collection.isList()
891                || collection.isSet()
892                || collection.isArray()) 
893            {
894                if (isWrapped(wrappee, collection)) {
895                    cid = getOID((Wrappee) collection.get(wrappee));
896                } else {
897                    cid = storage.createObject(collection.getType().getName());
898                    logger.debug("  new id = " + cid);
899                }
900                storage.setField(oid, collection, cid);
901                Collection coll = collection.getActualCollection(wrappee);
902                logger.debug("  Coll value = " + coll);
903                if (coll == null) {
904                    logger.warn(
905                        "uninitialized collection "+collection.getLongName()+
906                        ". Do you have a constructor with no parameters?");
907                    return cid;
908                }
909                // iterator() is wrapped method, so it triggers the
910                // loading of the collection from the storage, which is bad
911                Iterator it = coll.iterator();
912                while (it.hasNext()) {
913                    Object value = normalizeInput(it.next());
914                    logger.debug("  Coll elt value = " + value);
915                    if (value != null) {
916                        if (collection.isList() || collection.isArray()) {
917                            storage.addToList(cid, value);
918                        } else {
919                            storage.addToSet(cid, value);
920                        }
921                    }
922                }
923            } else if (collection.isMap()) {
924                if (isWrapped(wrappee, collection)) {
925                    cid = getOID((Wrappee) collection.get(wrappee));
926                } else {
927                    cid = storage.createObject(collection.getType().getName());
928                }
929                storage.setField(oid, collection, cid);
930                Map map = (Map) collection.get(wrappee);
931                Set entries = map.entrySet();
932                Iterator it = entries.iterator();
933                while (it.hasNext()) {
934                    Map.Entry entry = (Map.Entry) it.next();
935                    storage.putInMap(
936                        cid,
937                        normalizeInput(entry.getKey()),
938                        normalizeInput(entry.getValue()));
939                }
940            } else {
941                logger.error("unhandled collection type : " + collection.getType(), 
942                             new Exception());
943            }
944            logger.debug("  End of init collection " + collection.getName());
945            return cid;
946        }
947    
948        /**
949         * Initialize all fields of an object in the storage.
950         *
951         * <p>This method is called when a new object is made persistent. The
952         * references and collections are also recursively saved into the
953         * storage.<p>
954         *
955         * @param wrappee the initialized object
956         */
957        public void initAllFields(Wrappee wrappee, OID oid) throws Exception {
958            if (oid==null) 
959                throw new InvalidOidException("oid is NULL");
960            String classID = wrappee.getClass().getName();
961            logger.debug("initAllfields(" + oid + ") : " + classID);
962            ClassItem cli = cr.getClass(classID);
963            FieldItem[] fields = cli.getPrimitiveFields();
964            Storage storage = oid.getStorage();
965            storage.startTransaction();
966            try {
967                // fields
968                for (int i = 0; i < fields.length; i++) {
969                    if (!fields[i].isTransient()
970                        && !fields[i].isStatic()
971                        && !fields[i].isFinal()) 
972                    {
973                        Object fieldValue = fields[i].get(wrappee);
974                        logger.debug("Init field "
975                                + fields[i].getName()+ "->" + fieldValue);
976                        if (fieldValue != null) {
977                            if (fieldValue instanceof Wrappee)
978                                storage.setField(oid,fields[i],getOID((Wrappee)fieldValue));
979                            else 
980                                storage.setField(oid,fields[i],fieldValue);
981                        }
982                    }
983                }
984    
985                FieldItem[] references = cli.getReferences();
986    
987                // references
988                for (int i = 0; i < references.length; i++) {
989                    if (!references[i].isTransient()
990                        && !references[i].isStatic()
991                        && !references[i].isFinal()) {
992                        logger.debug("Init reference " + references[i].getName());
993                        Wrappee obj =
994                            (Wrappee) references[i].getActualField().get(wrappee);
995                        logger.debug("Ref value = "
996                                + (obj != null ? obj.getClass().getName() : "null"));
997    
998                        loadedReferences.add(references[i].getName());
999                        if (obj != null) {
1000                            OID objoid = 
1001                                (OID)Wrapping.invokeRoleMethod(
1002                                    obj,
1003                                    "makePersistent",
1004                                    ExtArrays.emptyObjectArray);
1005                            if (objoid == null) {
1006                                // this should never happen
1007                                logger.error(
1008                                    "wrapper.oid is NULL in initAllFields, reference = "
1009                                        + references[i].getName());
1010                            } else {
1011                                logger.debug("Ref value = " + objoid);
1012                                storage.setField(oid,references[i],objoid);
1013                                logger.debug("Ref saved");
1014                            }
1015                        }
1016                    }
1017    
1018                    // UNCOMMENT THIS WHEN UNWRAP WILL BE THREAD-SAFE!!!
1019                    // unwrap the reference's getter because it won't need to be
1020                    // reloaded from the storage
1021                    //MethodItem getter = references[i].getGetter();
1022                    //if (getter!=null) {
1023                    //   Wrapping.unwrap(interaction.wrappee,this,"applyPersistence",getter);            
1024                    //}
1025                }
1026    
1027                initCollections(wrappee, oid, cli.getCollections());
1028            } catch (Exception e) {
1029                storage.rollback();
1030            }
1031            storage.commit();
1032        }
1033    
1034        public static class InvalidOidException extends RuntimeException {
1035            public InvalidOidException(String msg) {
1036                super(msg);
1037            }
1038        }
1039    
1040        public Object invoke(MethodInvocation invocation) throws Throwable {
1041            return applyPersistence((Interaction) invocation);
1042        }
1043    
1044        public Object construct(ConstructorInvocation invocation)
1045            throws Throwable {
1046            return handleStatic((Interaction) invocation);
1047        }
1048    }
1049    
1050    // Local Variables: ***
1051    // c-basic-offset:4 ***
1052    // End: ***