Headers when sync deep a DataCollection

2011-11-28
2013-04-29
  • Nono Carballo

    Nono Carballo - 2011-11-28

    Headers are a very useful feature of DataCollection, but headers are only passed to the java back end on fill and sync(false) -the default- invocations.

    I recently needed to pass headers on sync(true) invocation. After taking a look at the source code of DataCollection and BatchService I did the folowwing changes:

    - Added the headers property to BatcService class, with the proper getter and setter.
    - In sendBatch method, added the headers to the remote object and the operation.

    The fragment modified in sendBatch method look like this

    createRemoteObject();
    ro.headers = headers; 
    var operation:AbstractOperation = ro.getOperation(transaction?"executeTransactionBatch":"executeBatch");
    operation.arguments = [batch];
    
    if(operation is IHeaders)
    {
        var co:IHeaders = IHeaders(operation);
        co.headers = headers;
    }
    

    Then in the sync method of DataCollection I added the headers to the BatchService instance

    var bs:BatchService = BatchService(batchServiceFactory.newInstance());
    bs.headers = headers;
    

    With all these changes I got the headers in the java back end on a sync(true) invocation.
    I use the headers content when inserting elements of the  master collection as well as the child collections.

    Why passing headers when sync deep a DataCollection was not taken into account?
    Is there another way of getting the same result?

    Nono

     
  • VictorRasputnis

    VictorRasputnis - 2011-11-28

    Hi Nono,
    Your solution is good, and we can add it to the main code with one reservation : are we sure that potential headers of the subordinate data collections do not matter (you are passing only top-level headers)? In general we would need to pass array of header arrays via BatchService and provide a more generic way of accessing "proper" headers on Java. This would include memoriziing the whole bunch of headers before starting the BatchGateway loop and sneaking in the relevant headers prior to each destination call. Let me know what do you think. WBR, Victor

     
  • Nono Carballo

    Nono Carballo - 2011-11-30

    Hi Victor,

    I completely agree with you, that would be the perfect way to pass headers on BatchService calls, in my case I just needed a quick solution, but definitely you are right. I think this need to be taken into account for the … next release? ;-)

    WBR

    Nono

     
  • Nono Carballo

    Nono Carballo - 2011-12-12

    Victor

    This could be a possible solution for passing headers (from parent and child DataCollections) when synchronizing deep a collection.

    First we need to modify the method sendBatch in BatchService class, so all headers (parent and child collections) be added to the AbstractOperation. We can concatenate the destination property with the dictionary key to form a unique key to identify a header from a collection. The sendBatch method would look like this:

            public function sendBatch(batch:Array, transaction:Boolean=true):AsyncToken {
                collectionMap = new Dictionary();
                stateMap = new Dictionary();
    
                // This dictionary will hold all registered DataCollection headers
                var headers:Dictionary = new Dictionary();
    
                if( __registry!=null ) {
                    for (var i:int=0; i< __registry.length; i++ ) {
                        var dataCollection:DataCollection = __registry[i].collection;
                        var key:String =  getKey(dataCollection.destination, dataCollection.fillMethod, dataCollection.argumentString);
                        collectionMap[key] = dataCollection;
                        var token:AsyncToken = new AsyncToken(new RemotingMessage());
                        token.method = DataCollection.SYNC;
    
                        // For each registered DataCollection its headers are added to the main dictionary
                        // using it destination+"."+header key as a key
                        for (var dict_key:Object in dataCollection.headers)
                        {
                            headers[dataCollection.destination+"."+dict_key.toString()] = dataCollection.headers[dict_key];
                        }
    
                        // If datacollection is not going to clean it's state with returned or pushed changeObjects,
                        // we clear in in advance. If problem happens, we will restoreState()
                        dataCollection.cx_internal::saveState(token);   
                        if (!dataCollection.autoSyncEnabled && !dataCollection.roundTripSync)
                            dataCollection.resetState();
                        stateMap[key] = token;
                    }
                }
                createRemoteObject();
                var operation:AbstractOperation = ro.getOperation(transaction?"executeTransactionBatch":"executeBatch");
                operation.arguments = [batch];
    
                // We add the headers to the AbstractOperation
                if(operation is IHeaders)
                {
                    var co:IHeaders = IHeaders(operation);
                    co.headers = headers;
                }
    
                var act:AsyncToken = operation.send();
                act.arguments = batch;
                return setupCancellableToken(operation, act, onCancelSendBatch);
            }
    

    When a DataCollection is generated it destination property is computed from the cannonical name of the interface annotated with @CXService annotation, in which the @GenerateDataCollection was included, so, if in any method involved in the sync process we want to get a header we need to find first the destination property of the DataCollection invoking the operation.

    We can use a class like this to do that:

    package clear.utils;
    import clear.cdb.annotations.CX_Service;
    import clear.messaging.ThreadLocals;
    import flex.messaging.messages.Message;
    public class HeaderUtils {
        public static Object getHeader(String key)
        {
            Message message = ThreadLocals.getMessage();
            if(message != null)
            {
                return message.getHeader(key);
            }
            return null;
        }
        public static Object getHeader(Class<?> owner, String key)
        {
            Message message = ThreadLocals.getMessage();
            if(message != null)
            {
                String destination = findDestination(owner);
                return message.getHeader(destination+"."+key);
            }
            return null;
        }
    
        private static String findDestination(Class<?> owner)
        {
            Class<?> cxAnnotatedInterface = lookForCXAnnotatedInterface(owner);
            if(cxAnnotatedInterface != null)
            {
                return cxAnnotatedInterface.getCanonicalName();
            }
            return null;
        }
    
        private static Class<?> lookForCXAnnotatedInterface(Class<?> root)
        {
            if(root == null)
            {
                return null;
            }
    
            Class<?>[] interfaces = root.getInterfaces();
            Class<?> result =  null;
    
            if(interfaces.length == 0)
            {
                result = lookForCXAnnotatedInterface(root.getSuperclass());
            }else
            {
                result = findCXAnnotatedInterface(interfaces);
                if(result == null)
                {
                    result = lookForCXAnnotatedInterface(root.getSuperclass());
                }
            }
            return result;
        }
    
        private static Class<?> findCXAnnotatedInterface(Class<?>[] interfaces)
        {
            Class<?> result = null;
            for (int i = 0; i < interfaces.length; i++) {
                Boolean isCXAnnotationPresent = interfaces[i].isAnnotationPresent(CX_Service.class);
                if(!isCXAnnotationPresent)
                {
                    result = findCXAnnotatedInterface(interfaces[i].getInterfaces());
                    if(result != null)
                    {
                        return result;
                    }
                }else
                {
                    return interfaces[i];
                }
            }
            return result;
        }
    }
    

    Then if we have a collection of type CompanyCollection and we put a header with key "someKey" of type String on it, we can call

    String header = (String) HeaderUtils.getHeader(CompanyService.class, "someKey");

    in any method involved in the sync process to get the header.

    The same class can also be used to get a header passed when invoking sync().

    I have done a little proof of concept and it works fine.

    WBR

    Nono

     

Get latest updates about Open Source Projects, Conferences and News.

Sign up for the SourceForge newsletter:





No, thanks