Manu
Manu

Reputation: 545

Requestfactory clone proxy to new context

I'm facing the gwt issue 5794 : http://code.google.com/p/google-web-toolkit/issues/detail?id=5794

I have seen there is a 8 month old patch for it but it has not been included in gwt 2.5 RC1 http://gwt-code-reviews.appspot.com/1620804/

Does anyone know if this patch will be included in gwt 2.5 rc2 or final release ?

if not, could someone please explain me what would be the best workaround for that problem.

Thanks in advance.

Upvotes: 2

Views: 980

Answers (3)

Jorge P.
Jorge P.

Reputation: 303

Well I think Chris Hinshaw had made a great code to clone a proxy but I will change one line just to make it better, avoiding an infinite loop on line 129

public class ProxyUtils {

public static <T extends BaseProxy> AutoBean<T> cloneBeanProperties(final T original, final T clone, final RequestContext toContext) {

    AutoBean<T> originalBean = AutoBeanUtils.getAutoBean(original);
    AutoBean<T> cloneBean = AutoBeanUtils.getAutoBean(clone);

    return cloneBeanProperties(originalBean, cloneBean, toContext);
}

/**
 * Shallow-clones an autobean and makes duplicates of the collection types.
 * A regular {@link AutoBean#clone} won't duplicate reference properties.
 */
public static <T extends BaseProxy> AutoBean<T> cloneBeanProperties(final AutoBean<T> toClone, final AutoBean<T> clone, final RequestContext toContext) {
    // NOTE: This may be a bad idea, i don't know if the STABLE_ID is the type or if it is the
    // actual server side reference to this object. Don't want it to update my original object.
    // I added this back in because I was getting an InstantionException in the locator I am
    // pretty sure this is because request factory server side could not resolve the class type. 
    // Maybe someone could shed some light on this one, if you know what the stable id is really
    // used for. 
    clone.setTag(STABLE_ID, clone.getTag(STABLE_ID));
    clone.setTag(Constants.VERSION_PROPERTY_B64, toClone.getTag(Constants.VERSION_PROPERTY_B64));
    clone.accept(new AutoBeanVisitor() {
        final Map<String, Object> values = AutoBeanUtils.getAllProperties(toClone);

        @Override
        public boolean visitCollectionProperty(String propertyName, AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
            // javac generics bug
            value = AutoBeanUtils.<Collection<?>, Collection<?>> getAutoBean((Collection<?>) values.get(propertyName));
            if (value != null) {
                Collection<Object> collection;
                if (List.class == ctx.getType()) {
                    collection = new ArrayList<Object>();
                } else if (Set.class == ctx.getType()) {
                    collection = new HashSet<Object>();
                } else {
                    // Should not get here if the validator works correctly
                    throw new IllegalArgumentException(ctx.getType().getName());
                }

                if (isValueType(ctx.getElementType()) || isEntityType(ctx.getElementType())) {
                    /*
                     * Proxies must be edited up-front so that the elements
                     * in the collection have stable identity.
                     */
                    for (Object o : value.as()) {
                        if (o == null) {
                            collection.add(null);
                        } else {
                            collection.add(editProxy(toContext, (Class<T>) ctx.getType(), (T) o));
                        }
                    }
                } else {
                    // For simple values, just copy the values
                    collection.addAll(value.as());
                }

                ctx.set(collection);
            }
            return false;
        }

        @Override
        public boolean visitReferenceProperty(String propertyName, AutoBean<?> value, PropertyContext ctx) {
            value = AutoBeanUtils.getAutoBean(values.get(propertyName));
            if (value != null) {
                if (isValueType(ctx.getType()) || isEntityType(ctx.getType())) {
                    /*
                     * Value proxies must be cloned upfront, since the value
                     * is replaced outright.
                     */
                    @SuppressWarnings("unchecked")
                    AutoBean<BaseProxy> valueBean = (AutoBean<BaseProxy>) value;
                    ctx.set(editProxy(toContext, (Class<T>) ctx.getType(), (T) valueBean.as()));
                } else {
                    ctx.set(value.as());
                }
            }
            return false;
        }

        @Override
        public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
            ctx.set(values.get(propertyName));
            return false;
        }
    });
    return clone;
}



/**
 * Take ownership of a proxy instance and make it editable.
 */
private static <T extends BaseProxy> T editProxy(RequestContext ctx, Class<T> clazz, T object) {
    AutoBean<T> toClone = AutoBeanUtils.getAutoBean(object);

    // Create editable copies
    AutoBean<T> parent = toClone;

    AutoBean<T> clone = (AutoBean<T>) ctx.create(clazz);
    AutoBean<T> cloned = cloneBeanProperties(toClone, clone, ctx);

    cloned.setTag(Constants.PARENT_OBJECT, parent);
    return cloned.as();
}


private static boolean isEntityType(Class<?> clazz) {
    return isAssignableTo(clazz, EntityProxy.class);
}

private static boolean isValueType(Class<?> clazz) {
    return isAssignableTo(clazz, ValueProxy.class);
}

public static boolean isAssignableTo(Class<?> thisClass, Class<?> assignableTo ) {
    if(thisClass == null || assignableTo == null) {
      return false;
    }

    if(thisClass.equals(assignableTo)) {
        return true;
    }

    Class<?> currentSuperClass = thisClass.getSuperclass();
    if(currentSuperClass != null) {
        if(currentSuperClass.equals(assignableTo)) {
            return true;
        }
    }
    return false;
}

}

Upvotes: 2

Chris Hinshaw
Chris Hinshaw

Reputation: 7285

I came up with this from the AbstractRequestContext's copyBeanAndCollections method. It seems to do the job that I want of cloning a proxy to another proxy with it's new context. This essentially requires you to create a new proxy first or edit the proxy using a new context but I no longer have to use a copy constructor to iteratively copy properties to the new proxy. I am still testing but it I created a simple GWTTestCase and it seems to work correctly.

Quick Update

I resolved an issue with the fire that the request factory entity locator was unable to resolve the type, so I added the stableid and version tags back in to be cloned. This may be a bad idea and I will update when I figure out if the stable id is use for the class type of the actual reference to the original object. My guess is that it is used to resolve the type on the server side.

public class ProxyUtils {

    public static <T extends BaseProxy> AutoBean<T> cloneBeanProperties(final T original, final T clone, final RequestContext toContext) {

        AutoBean<T> originalBean = AutoBeanUtils.getAutoBean(original);
        AutoBean<T> cloneBean = AutoBeanUtils.getAutoBean(clone);

        return cloneBeanProperties(originalBean, cloneBean, toContext);
    }

    /**
     * Shallow-clones an autobean and makes duplicates of the collection types.
     * A regular {@link AutoBean#clone} won't duplicate reference properties.
     */
    public static <T extends BaseProxy> AutoBean<T> cloneBeanProperties(final AutoBean<T> toClone, final AutoBean<T> clone, final RequestContext toContext) {
        // NOTE: This may be a bad idea, i don't know if the STABLE_ID is the type or if it is the
        // actual server side reference to this object. Don't want it to update my original object.
        // I added this back in because I was getting an InstantionException in the locator I am
        // pretty sure this is because request factory server side could not resolve the class type. 
        // Maybe someone could shed some light on this one, if you know what the stable id is really
        // used for. 
        clone.setTag(STABLE_ID, toClone.getTag(STABLE_ID));
        clone.setTag(Constants.VERSION_PROPERTY_B64, toClone.getTag(Constants.VERSION_PROPERTY_B64));
        clone.accept(new AutoBeanVisitor() {
            final Map<String, Object> values = AutoBeanUtils.getAllProperties(toClone);

            @Override
            public boolean visitCollectionProperty(String propertyName, AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
                // javac generics bug
                value = AutoBeanUtils.<Collection<?>, Collection<?>> getAutoBean((Collection<?>) values.get(propertyName));
                if (value != null) {
                    Collection<Object> collection;
                    if (List.class == ctx.getType()) {
                        collection = new ArrayList<Object>();
                    } else if (Set.class == ctx.getType()) {
                        collection = new HashSet<Object>();
                    } else {
                        // Should not get here if the validator works correctly
                        throw new IllegalArgumentException(ctx.getType().getName());
                    }

                    if (isValueType(ctx.getElementType()) || isEntityType(ctx.getElementType())) {
                        /*
                         * Proxies must be edited up-front so that the elements
                         * in the collection have stable identity.
                         */
                        for (Object o : value.as()) {
                            if (o == null) {
                                collection.add(null);
                            } else {
                                collection.add(editProxy(toContext, (Class<T>) ctx.getType(), (T) o));
                            }
                        }
                    } else {
                        // For simple values, just copy the values
                        collection.addAll(value.as());
                    }

                    ctx.set(collection);
                }
                return false;
            }

            @Override
            public boolean visitReferenceProperty(String propertyName, AutoBean<?> value, PropertyContext ctx) {
                value = AutoBeanUtils.getAutoBean(values.get(propertyName));
                if (value != null) {
                    if (isValueType(ctx.getType()) || isEntityType(ctx.getType())) {
                        /*
                         * Value proxies must be cloned upfront, since the value
                         * is replaced outright.
                         */
                        @SuppressWarnings("unchecked")
                        AutoBean<BaseProxy> valueBean = (AutoBean<BaseProxy>) value;
                        ctx.set(editProxy(toContext, (Class<T>) ctx.getType(), (T) valueBean.as()));
                    } else {
                        ctx.set(value.as());
                    }
                }
                return false;
            }

            @Override
            public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
                ctx.set(values.get(propertyName));
                return false;
            }
        });
        return clone;
    }



    /**
     * Take ownership of a proxy instance and make it editable.
     */
    private static <T extends BaseProxy> T editProxy(RequestContext ctx, Class<T> clazz, T object) {
        AutoBean<T> toClone = AutoBeanUtils.getAutoBean(object);

        // Create editable copies
        AutoBean<T> parent = toClone;

        AutoBean<T> clone = (AutoBean<T>) ctx.create(clazz);
        AutoBean<T> cloned = cloneBeanProperties(toClone, clone, ctx);

        cloned.setTag(Constants.PARENT_OBJECT, parent);
        return cloned.as();
    }


    private static boolean isEntityType(Class<?> clazz) {
        return isAssignableTo(clazz, EntityProxy.class);
    }

    private static boolean isValueType(Class<?> clazz) {
        return isAssignableTo(clazz, ValueProxy.class);
    }

    public static boolean isAssignableTo(Class<?> thisClass, Class<?> assignableTo ) {
        if(thisClass == null || assignableTo == null) {
          return false;
        }

        if(thisClass.equals(assignableTo)) {
            return true;
        }

        Class<?> currentSuperClass = thisClass.getSuperclass();
        while(currentSuperClass != null) {
            if(currentSuperClass.equals(assignableTo)) {
                return true;
            }
            currentSuperClass = thisClass.getSuperclass();
        }
        return false;
    }
}

Here is my simple unit test that completed successfully. I did have some error that I will have to investigate in the isAssignableFrom method.

   public void testCloneProxy() {
        DaoRequestFactory requestFactory = GWT.create(DaoRequestFactory.class);
        RequestContext fromContext = requestFactory.analyticsTaskRequest();

        AnalyticsOperationInputProxy from = fromContext.create(AnalyticsOperationInputProxy.class);

        from.setDisplayName("DISPLAY 1");
        from.setInputName("INPUT 1");


        RequestContext toContext = requestFactory.analyticsTaskRequest();

        AnalyticsOperationInputProxy to = toContext.create(AnalyticsOperationInputProxy.class);


        ProxyUtils.cloneBeanProperties(from, to, toContext);

        System.out.println("Cloned output " + to.getDisplayName());
        System.out.println("Cloned output " + to.getInputName());

        Assert.assertTrue("Display name not equal" , from.getDisplayName().equals(to.getDisplayName()));
        Assert.assertTrue("Input name not equal" , from.getInputName().equals(to.getInputName()));

    }

Upvotes: 3

Thomas Broyer
Thomas Broyer

Reputation: 64561

It won't.

From the first review (which happens to be by myself):

In brief: this patch is not enough, it breaks a lot of rules ("crossed streams"), and the test is broken.

In case you cannot use the serialize/deserialize workaround suggested in the issue, I believe there's a way to clone things using an AutoBeanVisitor.

The current "only one RequestContext can edit a given proxy –identified by its stableId– at a given time" is really annoying, and not really justified (no longer at least; it was in early iterations of Request Factory). This is something I'd like to get rid of in the future, but we have some other big things to do first.

Upvotes: 1

Related Questions