When Betwixt encounters an IDREF it looks for the matching bean using the IdStoringStrategy implementation. If the bean is not found, then control passes from the IdrefChainedBeanCreator to the DerivedChainedBeanCreator and finally the ElementTypeChainedBeanCreator, which ultimately results in the creating of an empty bean that is never populated or linked to the element that is referenced.

This approach inserts a special ChainedBeanCreator that uses CGLIB to create a proxy object. Method calls on the proxy are queued up until the proxy can be resolved. The proxy invocation handler stores a reference to the ReadContext (and IdStoringStrategy) so that any time a method is invoked on the proxy it can attempt to resolve the target.

IdentityIdStoringStrategy.java

import java.util.*;
import org.apache.commons.betwixt.expression.*;
import org.apache.commons.betwixt.strategy.*;
import net.sf.cglib.proxy.*;


/**
 *
 * @author Jesse Sweetland
 */
public class IdentityIdStoringStrategy extends IdStoringStrategy {
    private class Reference {
        private int _identity;
        private String _id;
        private Object _entity;
        
        public int getIdentity() {
            return _identity;
        }
        
        public String getId() {
            return _id;
        }
        
        public Object getEntity() {
            return _entity;
        }
        
        public Reference(Object entity, String id) {
            _identity = System.identityHashCode(entity);
            _entity = entity;
            _id = id;
        }
    }
    
    private HashMap<Integer, Reference> _identityRefs = new HashMap<Integer, Reference>();
    private HashMap<String, Reference> _idRefs = new HashMap<String, Reference>();
    private boolean _reset = false;
    
    public boolean isReset() {
        return _reset;
    }

    public IdentityIdStoringStrategy() {
        System.out.println("Boo");
    }
    
    public void setReference(Context context, Object bean, String id) {
        if(_reset) doReset();
        Reference ref = new Reference(bean, id);
        _identityRefs.put(ref.getIdentity(), ref);
        _idRefs.put(ref.getId(), ref);
    }
    
    public String getReferenceFor(Context context, Object bean) {
        int identity = System.identityHashCode(bean);
        Reference ref = _identityRefs.get(identity);
        return ref == null ? null : ref.getId();
    }
    
    public Object getReferenced(Context context, String id) {
        Reference ref = _idRefs.get(id);
        return ref == null ? null : ref.getEntity();
    }
    
    public void reset() {
        // Delay the actual reset until this instance IdentityIdStoringStrategy
        // is used again.  The references needs to persist as long as possible
        // after unmarshalling so that the forward reference proxies can resolve.

        // Called when unmarshalling is complete.  Set a flag to trigger cleanup
        // if and when this instance is used again.  This flag also serves as an
        // indicator to ForwardReferenceInvocationHandler that unmarshalling is
        // complete, meaning that if a proxy target does not resolve then it
        // never will.
        _reset = true;
    }

    private void doReset() {
        _identityRefs.clear();
        _idRefs.clear();
    }
}

ForwardReferenceChainedBeanCreator.java

import net.sf.cglib.proxy.*;
import org.apache.commons.betwixt.*;
import org.apache.commons.betwixt.io.read.*;

/**
 *
 * @author Jesse Sweetland
 */
public class ForwardReferenceChainedBeanCreator implements ChainedBeanCreator {
    public Object create(ElementMapping elementMapping, ReadContext context, BeanCreationChain chain) {
        if(context.getMapIDs()) {
            String idref = elementMapping.getAttributes().getValue("idref");
            if(idref != null){
                context.getLog().trace("Found IDREF");
                Object bean = context.getBean( idref );
                if(bean != null) {
                    if(context.getLog().isTraceEnabled()) {
                        context.getLog().trace("Matched bean " + bean);
                    }
                    return bean;
                }
                
                Class beanClass = null;
                ElementDescriptor descriptor = elementMapping.getDescriptor();
                if(descriptor != null) {
                    // check for polymorphism 
                    if (descriptor.isPolymorphic()) {
                        beanClass = context.getXMLIntrospector().getPolymorphicReferenceResolver()
                            .resolveType(elementMapping, context);
                    }

                    if(beanClass == null) {
                        // created based on implementation class
                        beanClass = descriptor.getImplementationClass();
                    }
                }

                if(beanClass == null) {
                    // create based on type
                    beanClass = elementMapping.getType();
                }
                
                if(beanClass != null) {
                    return Enhancer.create(beanClass, new Class[0], new ForwardReferenceInvocationHandler(beanClass, idref, context));
                }
            }
        }
        return chain.create(elementMapping, context);
    }
}

ForwardReferenceInvocationHandler.java

import java.util.*;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.*;
import org.apache.commons.betwixt.io.read.*;
import org.apache.commons.betwixt.strategy.*;

/**
 *
 * @author jessesw
 */
public class ForwardReferenceInvocationHandler implements InvocationHandler {
    private class QueuedCall {
        public Method method;
        public Object[] args;
        
        public QueuedCall(Method method, Object[] args) {
            this.method = method;
            this.args = args;
        }
    }
    
    private Class _type;
    private String _id;
    private ReadContext _context;
    private IdStoringStrategy _references;
    private List<QueuedCall> _queue = new ArrayList<QueuedCall>();
    private Object _target;
    
    public Object getTarget() {
        if(_target == null) {
            _target = _references.getReferenced(_context, _id);
            if((_target == null) && isFinishedUnmarshalling(_references)) {
                throw new RuntimeException("IDREF " + _id + " can not be resolved");
            }
        }
        return _target;
    }
    
    public ForwardReferenceInvocationHandler(Class type, String id, ReadContext context) {
        _type = type;
        _id = id;
        _context = context;
        _references = context.getIdMappingStrategy();
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object target = getTarget();
        if(target == null) {
            if("getClass".equals(method.getName())) {
                return _type;
            } else if("hashCode".equals(method.getName())) {
                return System.identityHashCode(proxy);
            } else {
                _queue.add(new QueuedCall(method, args));
                return null;
            }
        } else {
            for(QueuedCall call: _queue) {
                call.method.invoke(target, call.args);
            }
            _queue.clear();
            return method.invoke(target, args);
        }
    }

    private boolean isFinishedUnmarshalling(IdStoringStrategy _references) {
        if(_references instanceof IdentityIdStoringStrategy) {
            return ((IdentityIdStoringStrategy)_references).isReset();
        }
        return false;
    }
}

To use, set the custom IdStoringStrategy and insert the chained bean creator:

BeanReader br = new BeanReader();
ReadConfiguration readCfg = br.getReadConfiguration();
XMLIntrospector xmlIntro = br.getXMLIntrospector();
IntrospectionConfiguration introCfg = xmlIntro.getConfiguration();
BindingConfiguration bindCfg = br.getBindingConfiguration();
BeanCreationChain chain = readCfg.getBeanCreationChain();
bindCfg.setIdMappingStrategy(new IdentityIdStoringStrategy());
((BeanCreationList)chain).insertBeanCreator(1, new ForwardReferenceChainedBeanCreator());
  • No labels