Issue Details (XML | Word | Printable)

Key: GDS-630
Type: New Feature New Feature
Status: Open Open
Priority: Major Major
Assignee: William Draï
Reporter: heyoulin
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
GraniteDS

add tide spring In characteristic similar tide seam

Created: 09/Feb/10 05:28 PM   Updated: 22/Dec/10 07:32 PM
Component/s: Spring services, Tide
Affects Version/s: None
Fix Version/s: 3.0.0_beta1


 Description  « Hide
Sometimes i want get results from spring component's property similar tide seam

In(myComponent.myProperty)
myComponent.myMethod(param);

or

myComponent.myProperty;
myComponent.myMethod(param);

Can this seam characteristic realize in spring?

William Draï added a comment - 15/Feb/10 05:24 PM
This is not necessarily very difficult to do, it's mostly a copy/paste from the Seam integration, but it does not fit very well with the stateless nature of Spring and adds more complexity. I'm not even sure we'll implement this for CDI.
Do you have a use case where it would be useful ?

William Draï added a comment - 04/Mar/10 04:53 PM
This will be part of a refactoring to unify all client-side Tide integration plugins, so probably for 2.2.

heyoulin added a comment - 18/Mar/10 04:48 AM
I think this is similar with JSF usage. eg:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.granite.tide.annotations.TideEnabled;
import org.granite.tide.data.DataEnabled;
import org.granite.tide.data.DataEnabled.PublishMode;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import test.granite.spring.service.ObserveAllPublishAll;
@Service("mytest")
@Scope("request")//or session
@TideEnabled
@DataEnabled(topic="addressBookTopic", params=ObserveAllPublishAll.class, publish=PublishMode.ON_SUCCESS)
public class Test {
@PersistenceContext
    protected EntityManager entityManager;
private String tt;

public String getTt() {
return tt;
}
public void setTt(String tt) {
this.tt = tt;
}
private ArrayList<Map> mymaps=new ArrayList<Map>();
public ArrayList<Map> getMymaps() {
return mymaps;
}
public void setMymaps(ArrayList<Map> mymaps) {
this.mymaps = mymaps;
}
public void doSometing()
{
Map mymap=new HashMap();
mymap.put("aa", "base"+(tt==null?"":tt));
mymaps.add(mymap);
}

}

client side:

tideContext.mytest.mymaps;
tideContext.mytest.tt="input";
tideContext.mytest.doSometing(
function(event:TideResultEvent):void{
    var list:ArrayCollection=tideContext.mytest.mymaps as ArrayCollection;
    var i:int;
    for (i=0;i<list.length;i++)
       {
var obj:Object=list.getItemAt(i);
Alert.show(obj.aa);
       }
     },
    function(event:TideFaultEvent):void{
Alert.show("error");
    });
}

So i can reuse jsf based spring bean and don't use Model and view Controller. Flex client invoking spring bean method is so flexible.Method's paramaters and results just transfered by spring bean's properties.

I copy/paste from the Seam integration and test it fine.

Below is patch:

### Eclipse Workspace Patch 1.0
#P graniteds
Index: spring/org/granite/tide/spring/TideInvocation.java
===================================================================
--- spring/org/granite/tide/spring/TideInvocation.java (revision 0)
+++ spring/org/granite/tide/spring/TideInvocation.java (revision 0)
@@ -0,0 +1,104 @@
+package org.granite.tide.spring;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.granite.tide.invocation.ContextEvent;
+import org.granite.tide.invocation.ContextUpdate;
+
+
+/**
+ * @author William DRAI
+ */
+public class TideInvocation {
+
+ private static ThreadLocal<TideInvocation> invocation = new ThreadLocal<TideInvocation>() {
+ @Override
+ protected TideInvocation initialValue() {
+ return new TideInvocation();
+ }
+ };
+
+ public static TideInvocation get() {
+ return invocation.get();
+ }
+
+ public static TideInvocation init() {
+ TideInvocation ti = new TideInvocation();
+ invocation.set(ti);
+ return ti;
+ }
+
+ public static void remove() {
+ invocation.remove();
+ }
+
+ private boolean locked = false;
+ private boolean enabled = false;
+ private boolean updated = false;
+ private boolean evaluated = false;
+ private final List<ContextUpdate> updates = new ArrayList<ContextUpdate>();
+ private final List<ContextUpdate> results = new ArrayList<ContextUpdate>();
+ private final List<ContextEvent> events = new ArrayList<ContextEvent>();
+
+
+ public List<ContextUpdate> getUpdates() {
+ return updates;
+ }
+
+ public List<ContextUpdate> getResults() {
+ return results;
+ }
+
+ public List<ContextEvent> getEvents() {
+ return events;
+ }
+
+ public void update(List<ContextUpdate> updates) {
+ this.enabled = true;
+ this.updated = false;
+ this.updates.clear();
+ if (updates != null)
+ this.updates.addAll(updates);
+ }
+ public void updated() {
+ this.updated = true;
+ this.updates.clear();
+ }
+ public boolean isUpdated() {
+ return this.updated;
+ }
+
+ public void evaluate() {
+ this.evaluated = false;
+ this.results.clear();
+ }
+ public void evaluated(List<ContextUpdate> results) {
+ this.evaluated = true;
+// this.results.clear();
+ this.results.addAll(results);
+ this.updated = false;
+ this.updates.clear();
+ }
+ public boolean isEvaluated() {
+ return this.evaluated;
+ }
+
+ public void addEvent(ContextEvent event) {
+ events.add(event);
+ }
+
+ public void lock() {
+ this.locked = true;
+ }
+ public void unlock() {
+ this.locked = false;
+ }
+ public boolean isLocked() {
+ return this.locked;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+}
Index: spring/org/granite/tide/spring/SpringMVCServiceContext.java
===================================================================
--- spring/org/granite/tide/spring/SpringMVCServiceContext.java (revision 3702)
+++ spring/org/granite/tide/spring/SpringMVCServiceContext.java (working copy)
@@ -20,7 +20,9 @@
 
 package org.granite.tide.spring;
 
+
 import java.lang.reflect.Method;
+import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -29,11 +31,13 @@
 import java.util.Map;
 import java.util.Set;
 
+import javax.persistence.Entity;
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
 
+import org.granite.config.GraniteConfig;
 import org.granite.context.GraniteContext;
 import org.granite.logging.Logger;
 import org.granite.messaging.amf.io.convert.Converter;
@@ -45,10 +49,13 @@
 import org.granite.tide.IInvocationResult;
 import org.granite.tide.TidePersistenceManager;
 import org.granite.tide.data.DataContext;
+import org.granite.tide.invocation.ContextResult;
 import org.granite.tide.invocation.ContextUpdate;
 import org.granite.tide.invocation.InvocationCall;
 import org.granite.tide.invocation.InvocationResult;
+import org.granite.tide.spring.security.Identity3;
 import org.granite.util.ClassUtil;
+import org.granite.util.Reflections;
 import org.springframework.beans.TypeMismatchException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.core.MethodParameter;
@@ -73,9 +80,13 @@
     private static final String REQUEST_VALUE = "__REQUEST_VALUE__";
     
     private static final Logger log = Logger.getLogger(SpringMVCServiceContext.class);
+ private ResultsEval resultsEval=new ResultsEval();
+
+ public Map<ContextResult, Boolean> getResultsEval() {
+ return resultsEval.getResultsEval();
+ }
 
-
- @Override
+ @Override
     public Object adjustInvokee(Object instance, String componentName, Set<Class<?>> componentClasses) {
      for (Class<?> componentClass : componentClasses) {
  if (componentClass.isAnnotationPresent(org.springframework.stereotype.Controller.class)) {
@@ -166,10 +177,24 @@
     @Override
     public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) {
      super.prepareCall(context, c, componentName, componentClass);
-
      if (componentName == null)
      return;
+ InvocationCall call = (InvocationCall)c;
+ //List<String> listeners = call.getListeners();
+ List<ContextUpdate> updates = call.getUpdates();
+ Object[] results = call.getResults();
     
+ if (results != null) {
+ Map<ContextResult, Boolean> resultsEval = getResultsEval();
+ for (Object result : results) {
+ ContextResult cr = (ContextResult)result;
+ resultsEval.put(cr, Boolean.TRUE);
+ }
+ }
+ TideInvocation tideInvocation = TideInvocation.init();
+ tideInvocation.update(updates);
+ restoreContext(updates, componentName, null);
+ tideInvocation.updated();
  Object component = findComponent(componentName, componentClass);
 
  if (context.getBean() instanceof HandlerAdapter) {
@@ -201,12 +226,85 @@
  }
     }
     
-
+ public void restoreContext(List<ContextUpdate> updates, String componentName, Object target) {
+ if (updates == null)
+ return;
+
+ GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
+
+ // Restore context
+ for (ContextUpdate update : updates) {
+ if(update.getComponentName()==null)
+ continue;
+ log.debug("Before invocation: evaluating expression #0.#1", update.getComponentName(), update.getExpression());
+
+ Object bean = findComponent(update.getComponentName(),null);
+ //String sourceComponentName = sourceComponent != null ? sourceComponent.getName() : update.getComponentName();
+
+ Object previous = null;
+ Object value=bean;
+ if (update.getExpression() != null) {
+ if (bean != null && config.isComponentTideDisabled(update.getComponentName(), findComponentClasses(update.getComponentName(),bean.getClass()), bean))
+ continue;
+ String[] path = update.getExpression().split("\\.");
+ if (update.getValue() != null) {
+ boolean getPrevious = true;
+ if (update.getValue().getClass().getAnnotation(Entity.class) != null) {
+ org.granite.util.Entity entity = new org.granite.util.Entity(update.getValue());
+ if (entity.getIdentifier() == null)
+ getPrevious = false;
+ }
+ if (getPrevious) {
+ try{
+ try {
+ for (int i = 0; i < path.length; i++) {
+ if (value == null)
+ break;
+ Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
+ value = Reflections.invoke(getter, value);
+ if (i < path.length-1)
+ bean = value;
+ }
+ }
+ catch (IllegalArgumentException e) {
+ // No getter found to retrieve current value
+ log.warn("Partial merge only: " + e.getMessage());
+ value = null;
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Could not get property: " + update.toString(), e);
+ }
+ previous = value;
+ if (bean != null) {
+ Method setter = Reflections.getSetterMethod(bean.getClass(), path[path.length-1]);
+ Type type = setter.getParameterTypes()[0];
+
+ value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), type);
+ // Merge entities into current persistent context if needed
+ value = mergeExternal(value, previous);
+ Reflections.invoke(setter, bean, value);
+ }
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Could not restore property: " + update.toString(), e);
+ }
+ }
+ }
+ }
+ }
+ }
     @Override
     @SuppressWarnings("unchecked")
     public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) {
- List<ContextUpdate> results = null;
-
+ TideInvocation tideInvocation = TideInvocation.get();
+
+ List<ContextUpdate> results = null;
+ if (!tideInvocation.isEvaluated()) {
+ // Do evaluation now if the interceptor has not been called
+ results = evaluateResults(componentName, false);
+ }
+ else
+ results = tideInvocation.getResults();
      if (componentName != null && context.getBean() instanceof HandlerAdapter) {
  Object component = findComponent(componentName, componentClass);
 
@@ -281,19 +379,96 @@
  }
  }
      }
-
+ int scope = 3;
+ boolean restrict = false;
+
      DataContext dataContext = DataContext.get();
  Set<Object[]> dataUpdates = dataContext != null ? dataContext.getDataUpdates() : null;
  Object[][] updates = null;
  if (dataUpdates != null && !dataUpdates.isEmpty())
  updates = dataUpdates.toArray(new Object[dataUpdates.size()][]);
-
+
+ InvocationResult res = new InvocationResult(result, results);
+ res.setScope(scope);
+ res.setRestrict(restrict);
+
+ //res.setUpdates(updates);
+ res.setEvents(tideInvocation.getEvents());
+
         InvocationResult ires = new InvocationResult(result, results);
         ires.setUpdates(updates);
-
+ TideInvocation.remove();
         return ires;
     }
 
+
+ /**
+ * Evaluate results from context
+ *
+ * @param component the target component
+ * @param target the target instance
+ * @param nothing used by initializer to avoid interactions with context sync
+ *
+ * @return list of updates to send back to the client
+ */
+ public List<ContextUpdate> evaluateResults(String componentName,boolean nothing) {
+
+ List<ContextUpdate> resultsMap = new ArrayList<ContextUpdate>();
+
+ if (nothing)
+ return resultsMap;
+
+ GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
+ ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
+
+ for (Map.Entry<ContextResult, Boolean> me : getResultsEval().entrySet()) {
+ if (!me.getValue())
+ continue;
+
+ ContextResult res = me.getKey();
+ if(res.getComponentName()==null)
+ continue;
+ if(!componentName.equals(res.getComponentName()) || componentName.equals("identity"))
+ continue;
+ Object targetComponent = findComponent(res.getComponentName(), null);
+ if(targetComponent==null)
+ continue;
+ boolean add = true;
+ Object value = targetComponent;
+ Boolean restrict = res.getRestrict();
+ if (value != null && config.isComponentTideDisabled(res.getComponentName(), findComponentClasses(res.getComponentName(),value.getClass()), value))
+ add = false;
+
+ if (add) {
+ getResultsEval().put(res, false);
+ String[] path = res.getExpression() != null ? res.getExpression().split("\\.") : new String[0];
+ for (int i = 0; i < path.length; i++) {
+ if (value == null)
+ break;
+ Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
+ try {
+ value = org.granite.util.Reflections.invoke(getter, value);
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ if (value != null && classGetter != null) {
+ classGetter.initialize(null, null, value);
+
+ int scope = 3;
+
+ resultsMap.add(new ContextUpdate(res.getComponentName(), res.getExpression(), value, scope, Boolean.TRUE.equals(restrict)));
+ add = false;
+ }
+ }
+
+ me.setValue(Boolean.FALSE);
+ }
+
+ return resultsMap;
+ }
+
     @Override
     public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
      if (componentName != null && context.getBean() instanceof HandlerAdapter) {
Index: spring/org/granite/tide/spring/ResultsEval.java
===================================================================
--- spring/org/granite/tide/spring/ResultsEval.java (revision 0)
+++ spring/org/granite/tide/spring/ResultsEval.java (revision 0)
@@ -0,0 +1,24 @@
+package org.granite.tide.spring;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.granite.tide.invocation.ContextResult;
+
+
+/**
+ * @author William DRAI
+ */
+public class ResultsEval implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+
+ private Map<ContextResult, Boolean> resultsEval = new HashMap<ContextResult, Boolean>();
+
+
+ public Map<ContextResult, Boolean> getResultsEval() {
+ return resultsEval;
+ }
+}
 
////////////////// as3


### Eclipse Workspace Patch 1.0
#P graniteds
Index: as3/framework/org/granite/tide/spring/Context.as
===================================================================
--- as3/framework/org/granite/tide/spring/Context.as (revision 3702)
+++ as3/framework/org/granite/tide/spring/Context.as (working copy)
@@ -20,419 +20,685 @@
 
 package org.granite.tide.spring {
 
- import flash.events.Event;
- import flash.events.IEventDispatcher;
- import flash.utils.Dictionary;
- import flash.utils.flash_proxy;
- import flash.utils.getQualifiedClassName;
-
- import mx.collections.ArrayCollection;
- import mx.collections.ArrayList;
- import mx.collections.IList;
- import mx.collections.ItemResponder;
- import mx.collections.ListCollectionView;
- import mx.controls.Alert;
- import mx.core.IUID;
- import mx.core.UIComponent;
- import mx.events.CollectionEvent;
- import mx.events.CollectionEventKind;
- import mx.events.FlexEvent;
- import mx.events.PropertyChangeEvent;
- import mx.events.PropertyChangeEventKind;
- import mx.events.ValidationResultEvent;
- import mx.logging.ILogger;
- import mx.logging.Log;
- import mx.messaging.events.ChannelFaultEvent;
- import mx.messaging.messages.ErrorMessage;
- import mx.rpc.AbstractOperation;
- import mx.rpc.AsyncToken;
- import mx.rpc.remoting.mxml.RemoteObject;
- import mx.rpc.events.FaultEvent;
- import mx.rpc.events.ResultEvent;
- import mx.utils.ObjectProxy;
- import mx.utils.ObjectUtil;
- import mx.utils.object_proxy;
- import mx.validators.ValidationResult;
-
- import org.granite.collections.IPersistentCollection;
- import org.granite.events.SecurityEvent;
- import org.granite.meta;
- import org.granite.tide.BaseContext;
- import org.granite.tide.IComponent;
- import org.granite.tide.Component;
- import org.granite.tide.ComponentProperty;
- import org.granite.tide.IEntity;
- import org.granite.tide.IEntityManager;
- import org.granite.tide.IExpression;
- import org.granite.tide.IInvocationCall;
- import org.granite.tide.IInvocationResult;
- import org.granite.tide.IPropertyHolder;
- import org.granite.tide.invocation.InvocationCall;
- import org.granite.tide.invocation.InvocationResult;
- import org.granite.tide.invocation.ContextUpdate;
- import org.granite.tide.collections.PersistentCollection;
- import org.granite.tide.events.TideFaultEvent;
- import org.granite.tide.events.TideResultEvent;
-
-
- use namespace flash_proxy;
- use namespace object_proxy;
- use namespace meta;
-
-
- [Bindable]
+ import flash.events.Event;
+ import flash.events.IEventDispatcher;
+ import flash.utils.Dictionary;
+ import flash.utils.flash_proxy;
+ import flash.utils.getQualifiedClassName;
+ import flash.utils.describeType;
+ import mx.collections.Sort;
+ import mx.collections.ArrayCollection;
+ import mx.collections.ArrayList;
+ import mx.collections.IList;
+ import mx.collections.ItemResponder;
+ import mx.collections.ListCollectionView;
+ import mx.controls.Alert;
+ import mx.core.IUID;
+ import mx.core.UIComponent;
+ import mx.events.CollectionEvent;
+ import mx.events.CollectionEventKind;
+ import mx.events.FlexEvent;
+ import mx.events.PropertyChangeEvent;
+ import mx.events.PropertyChangeEventKind;
+ import mx.events.ValidationResultEvent;
+ import mx.logging.ILogger;
+ import mx.logging.Log;
+ import mx.messaging.events.ChannelFaultEvent;
+ import mx.messaging.messages.ErrorMessage;
+ import mx.rpc.AbstractOperation;
+ import mx.rpc.AsyncToken;
+ import mx.rpc.remoting.mxml.RemoteObject;
+ import mx.rpc.events.FaultEvent;
+ import mx.rpc.events.ResultEvent;
+ import mx.utils.ObjectProxy;
+ import mx.utils.ObjectUtil;
+ import mx.utils.object_proxy;
+ import mx.validators.ValidationResult;
+
+ import org.granite.collections.IPersistentCollection;
+ import org.granite.events.SecurityEvent;
+ import org.granite.meta;
+ import org.granite.tide.BaseContext;
+ import org.granite.tide.IComponent;
+ import org.granite.tide.IIdentity;
+ import org.granite.tide.IEntity;
+ import org.granite.tide.IEntityManager;
+ import org.granite.tide.IExpression;
+ import org.granite.tide.IInvocationCall;
+ import org.granite.tide.IInvocationResult;
+ import org.granite.tide.IPropertyHolder;
+ import org.granite.tide.TypedContextExpression;
+ import org.granite.tide.invocation.InvocationCall;
+ import org.granite.tide.invocation.InvocationResult;
+ import org.granite.tide.collections.PersistentCollection;
+ import org.granite.tide.events.TideFaultEvent;
+ import org.granite.tide.events.TideResultEvent;
+ import org.granite.tide.invocation.ContextEvent;
+ import org.granite.tide.invocation.ContextResult;
+ import org.granite.tide.invocation.ContextUpdate;
+ import org.granite.tide.Tide;
+ import org.granite.tide.Component;
+ import org.granite.tide.ComponentProperty;
+
+
+ use namespace flash_proxy;
+ use namespace object_proxy;
+ use namespace meta;
+
+
+ [Bindable]
  /**
- * Implementation of the Tide context for Spring services
+ * Implementation of the Tide context for EJB3 services
  *
- * @author William DRAI
- */
- public dynamic class Context extends BaseContext {
-
- private static var log:ILogger = Log.getLogger("org.granite.tide.spring.Context");
-
- private var _updates:ArrayCollection = new ArrayCollection();
- private var _pendingUpdates:ArrayCollection = new ArrayCollection();
-
-
+ * @author William DRAI
+ */
+ public dynamic class Context extends BaseContext {
+
+ private static var log:ILogger = Log.getLogger("org.granite.tide.spring.Context");
+
+ private var _updates:ArrayCollection = new ArrayCollection();
+ private var _pendingUpdates:ArrayCollection = new ArrayCollection();
+ [ArrayElementType("org.granite.tide.invocation.ContextResult")]
+ private var _results:Array = new Array();
+ private var _lastResults:ArrayCollection = new ArrayCollection();
+
+
+
  public function Context(tide:Spring, parentContext:BaseContext = null) {
  super(tide, parentContext);
  }
-
-
+
+
  /**
+ * Current conversationId of the context
+ */
+ public function get conversationId(): String {
+ return contextId;
+ }
+
+ /**
+ * Updates the current conversationId of the context
+ *
+ * @param conversationId the new conversationId
+ */
+ public function set conversationId(conversationId:String): void {
+ meta_setContextId(conversationId);
+ }
+
+
+ /**
  * @private
  * @return current list of updates that will be sent to the server
  */
- public function get meta_updates():IList {
- return _updates;
- }
-
- /**
+ public function get meta_updates():IList {
+ return _updates;
+ }
+
+ /**
  * @private
- * Reinitialize the context
- *
- * @param force force complete destruction of context
- */
- public override function meta_clear(force:Boolean = false):void {
- _updates.removeAll();
- _pendingUpdates.removeAll();
-
- super.meta_clear(force);
-
- _updates.removeAll();
- _pendingUpdates.removeAll();
- }
-
-
- /**
+ * @return current list of results that will be requested from the server
+ */
+ public function get meta_results():Array {
+ return _results;
+ }
+
+
+ /**
  * @private
- * Add update to current context
- * Note: always move the update in last position in case the value can depend on previous updates
- *
- * @param componentName name of the component/context variable
- * @param expr EL expression to evaluate
- * @param value value to send to server
+ * Reinitialize the context
+ *
+ * @param force force complete destruction of context
+ */
+ public override function meta_clear(force:Boolean = false):void {
+ _updates.removeAll();
+ _pendingUpdates.removeAll();
+ _results = new Array();
+ _lastResults.removeAll();
+
+ super.meta_clear(force);
+
+ _updates.removeAll();
+ _pendingUpdates.removeAll();
+ _results = new Array();
+ _lastResults.removeAll();
+ }
+
+ /**
+ * @private
+ * Add update to current context
+ * Note: always move the update in last position in case the value can depend on previous updates
+ *
+ * @param componentName name of the component/context variable
+ * @param expr EL expression to evaluate
+ * @param value value to send to server
  * @param typed component name represents a typed component instance
- */
- public override function meta_addUpdate(componentName:String, expr:String, value:*, typed:Boolean = false):void {
- if (!meta_tracking)
- return;
-
- if (!_tide.isComponentRemoteSync(componentName))
- return;
-
- var val:Object = value;
- if (val is IPropertyHolder)
- val = IPropertyHolder(val).object;
-
- var found:Boolean = false;
- for (var i:int = 0; i < _updates.length; i++) {
- var u:ContextUpdate = _updates.getItemAt(i) as ContextUpdate;
- if (u.componentName == componentName && u.expression == expr) {
- u.value = val;
- if (i < _updates.length-1) {
- found = false;
- _updates.removeItemAt(i); // Remove here to add it in last position
- i--;
- }
- else
- found = true;
- }
- else if (u.componentName == componentName && u.expression != null && (expr == null || u.expression.indexOf(expr) == 0)) {
- _updates.removeItemAt(i);
- i--;
- }
- else if (u.componentName == componentName && expr != null && (u.expression == null || expr.indexOf(u.expression) == 0))
- found = true;
- }
-
- if (!found) {
- log.debug("add new update {0}.{1}", componentName, expr);
- _updates.addItem(new ContextUpdate(componentName, expr, val, _tide.getComponentScope(componentName)));
- }
- }
-
-
- /**
- * @private
- *
- * Reset current call context and returns saved context
- *
- * @return saved call context
- */
- public override function meta_saveAndResetCallContext():Object {
- var savedCallContext:Object = {
- updates: new ArrayCollection(_updates.toArray()),
- pendingUpdates: new ArrayCollection(_pendingUpdates.toArray())
- };
-
- _updates.removeAll();
- _pendingUpdates.removeAll();
-
- return savedCallContext;
- }
-
- /**
- * @private
- *
- * Restore call context
- *
- * @param callContext object containing the current call context
- */
- public override function meta_restoreCallContext(callContext:Object):void {
- _updates = callContext.updates as ArrayCollection;
- _pendingUpdates = callContext.pendingUpdates as ArrayCollection;
+ */
+ public override function meta_addUpdate(componentName:String, expr:String, value:*, typed:Boolean = false):void {
+ if (!meta_tracking)
+ return;
+
+ if (!_tide.isComponentRemoteSync(componentName))
+ return;
+
+ var val:Object = value;
+ if (val is IPropertyHolder)
+ val = IPropertyHolder(val).object;
+
+ var found:Boolean = false;
+ for (var i:int = 0; i < _updates.length; i++) {
+ var u:ContextUpdate = _updates.getItemAt(i) as ContextUpdate;
+ if (u.componentName == componentName && u.expression == expr) {
+ u.value = val;
+ if (i < _updates.length-1) {
+ found = false;
+ _updates.removeItemAt(i); // Remove here to add it in last position
+ i--;
+ }
+ else
+ found = true;
+ }
+ else if (u.componentName == componentName && u.expression != null && (expr == null || u.expression.indexOf(expr + ".") == 0)) {
+ _updates.removeItemAt(i);
+ i--;
+ }
+ else if (u.componentName == componentName && expr != null && (u.expression == null || expr.indexOf(u.expression + ".") == 0))
+ found = true;
+ }
+
+ if (!found) {
+ log.debug("add new update {0}.{1}", componentName, expr);
+ _updates.addItem(new ContextUpdate(typed ? null : componentName, expr, val, _tide.getComponentScope(componentName)));
+ }
+ }
+
+ /**
+ * @private
+ * Add result evaluator in current context
+ *
+ * @param componentName name of the component/context variable
+ * @param expr EL expression to evaluate
+ *
+ * @return true if the result was not already present in the current context
+ */
+ public override function meta_addResult(componentName:String, expr:String):Boolean {
+ if (!meta_tracking)
+ return false;
+
+ if (!_tide.isComponentRemoteSync(componentName))
+ return false;
+
+ var component:* = meta_getInstance(componentName, false, true);
+ var alias:String = describeType(component).@alias.toXMLString();
+ var componentClassName:String = alias ? alias : null;
+
+ // Check in existing results
+ for each (var o:Object in _results) {
+ var r:ContextResult = o as ContextResult;
+ if (r.componentName == componentName && r.componentClassName == componentClassName && r.expression == expr)
+ return false;
+ }
+
+ // Check in last received results
+ var e:String = componentName + (expr != null ? "." + expr : "");
+ if (_lastResults.getItemIndex(e) >= 0)
+ return false;
+
+ log.debug("addResult {0}({1}).{2}", componentName, componentClassName, expr);
+ // TODO: should store somewhere if the client componentName is the same as the server bean name
+ var cr:ContextResult = new ContextResult(componentName.indexOf(Tide.TYPED_IMPL_PREFIX) == 0 ? null : componentName, expr)
+ cr.componentClassName = componentClassName;
+ _results.push(cr);
+ return true;
+ }
+
+
+ /**
+ * @private
+ *
+ * Reset current call context and returns saved context
+ *
+ * @return saved call context
+ */
+ public override function meta_saveAndResetCallContext():Object {
+ var savedCallContext:Object = {
+ updates: new ArrayCollection(_updates.toArray()),
+ results: _results.concat(),
+ pendingUpdates: new ArrayCollection(_pendingUpdates.toArray()),
+ lastResults: new ArrayCollection(_lastResults.toArray())
+ };
+
+ _updates.removeAll();
+ _pendingUpdates.removeAll();
+ _results = new Array();
+ _lastResults.removeAll();
+
+ return savedCallContext;
+ }
+
+ /**
+ * @private
+ *
+ * Restore call context
+ *
+ * @param callContext object containing the current call context
+ */
+ public override function meta_restoreCallContext(callContext:Object):void {
+ _updates = callContext.updates as ArrayCollection;
+ _results = callContext.results as Array;
+ _pendingUpdates = callContext.pendingUpdates as ArrayCollection;
+ _lastResults = callContext.lastResults as ArrayCollection;
  }
-
-
- /**
+
+
+ /**
  * @private
- * Calls a remote component
- *
- * @param component the target component
- * @op name of the called metho
- * @arg method parameters
- *
- * @return the operation token
- */
- public override function meta_callComponent(component:IComponent, op:String, args:Array, withContext:Boolean = true):AsyncToken {
- log.debug("callComponent {0}.{1}", component.meta_name, op);
-
- var token:AsyncToken = super.meta_callComponent(component, op, args, withContext);
+ * Logs the current call
+ */
+ private function meta_traceCall():void {
+ log.debug("updates: {0}", toString(_updates));
+ log.debug("results: {0}", toString(_results));
+ }
+
+ /**
+ * @private
+ * Calls a remote component
+ *
+ * @param component the target component
+ * @op name of the called metho
+ * @arg method parameters
+ *
+ * @return the operation token
+ */
+ public override function meta_callComponent(component:IComponent, op:String, args:Array, withContext:Boolean = true):AsyncToken {
+ log.debug("callComponent {0}.{1}", component.meta_name, op);
+ meta_traceCall();
 
- if (withContext) {
- _pendingUpdates = new ArrayCollection(_updates.toArray());
- _updates.removeAll();
- }
-
- return token;
- }
-
-
- public override function meta_prepareCall(operation:AbstractOperation, withContext:Boolean = true):IInvocationCall {
- var call:InvocationCall = null;
- if (withContext)
- call = new InvocationCall(null, _updates, null);
- else
- call = new InvocationCall();
- return call;
- }
-
-
- public override function meta_result(componentName:String, operation:String, ires:IInvocationResult, result:Object, mergeWith:Object = null):void {
- meta_preResult();
-
- _pendingUpdates.removeAll();
-
- log.debug("result {0}", result);
-
- for (var key:String in object['flash']) {
- object['flash'][key] = null;
- delete object['flash'][key];
- }
-
- if (ires != null) {
- var invocationResult:InvocationResult = InvocationResult(ires);
-
- meta_tracking = false;
+ var token:AsyncToken = super.meta_callComponent(component, op, args, withContext);
+
+ if (withContext) {
+ _pendingUpdates = new ArrayCollection(_updates.toArray());
+ _updates.removeAll();
+ }
+
+ return token;
+ }
+
+
+ /**
+ * @private
+ * Calls the server login
+ *
+ * @param identity identity component
+ * @param username user name
+ * @param password user password
+ * @resultHandler result callback
+ * @faultHandler fault callback
+ *
+ * @return the operation token
+ */
+ public override function meta_login(identity:IIdentity, username:String, password:String, resultHandler:Function = null, faultHandler:Function = null):AsyncToken {
+ meta_traceCall();
+
+ // Keep only updates for identity component
+ for (var i:int = 0; i < _updates.length; i++) {
+ var u:ContextUpdate = _updates.getItemAt(i) as ContextUpdate;
+ if (u.componentName != identity.meta_name) {
+ _updates.removeItemAt(i);
+ i--;
+ }
+ }
+
+ var token:AsyncToken = super.meta_login(identity, username, password, resultHandler, faultHandler);
+
+ _updates.removeAll();
+
+ return token;
+ }
+
+ /**
+ * @private
+ * Calls the server login
+ *
+ * @param identity identity component
+ * @param username user name
+ * @param password user password
+ * @resultHandler result callback
+ * @faultHandler fault callback
+ *
+ * @return the operation token
+ */
+ public override function meta_isLoggedIn(identity:IIdentity, resultHandler:Function = null, faultHandler:Function = null):AsyncToken {
+ log.debug("isLoggedIn");
+ meta_traceCall();
+
+ // Keep only updates for identity component
+ for (var i:int = 0; i < _updates.length; i++) {
+ var u:ContextUpdate = _updates.getItemAt(i) as ContextUpdate;
+ if (u.componentName != identity.meta_name) {
+ _updates.removeItemAt(i);
+ i--;
+ }
+ }
+
+ var token:AsyncToken = super.meta_isLoggedIn(identity, resultHandler, faultHandler);
+
+ _updates.removeAll();
+
+ return token;
+ }
+
+
+ public override function meta_prepareCall(operation:AbstractOperation, withContext:Boolean = true):IInvocationCall {
+
+ var call:IInvocationCall = null;
+ if (withContext) {
+ call = new InvocationCall(_tide.newListeners, _updates, _results);
+ _tide.newListeners.removeAll();
+ }
+ else
+ call = new InvocationCall();
+ return call;
+ }
+
+
+ public override function meta_result(componentName:String, operation:String, ires:IInvocationResult, result:Object, mergeWith:Object = null):void {
+ meta_preResult();
+
+ _pendingUpdates.removeAll();
+ _lastResults.removeAll();
+
+ if (ires != null) {
+ var invocationResult:InvocationResult = InvocationResult(ires);
 
  if (invocationResult.updates)
  meta_handleUpdates(null, invocationResult.updates);
-
- var rmap:IList = invocationResult.results;
- if (rmap) {
- for (var k:int = 0; k < rmap.length; k++) {
- var r:ContextUpdate = rmap.getItemAt(k) as ContextUpdate;
- var val:Object = r.value;
-
- log.debug("update expression {0}: {1}", r, val);
-
- var compName:String = r.componentName;
-
- var obj:Object = meta_getInstanceNoProxy(compName);
- var p:Array = r.expression != null ? r.expression.split(".") : [];
- if (p.length > 1) {
- for (var i:int = 0; i < p.length-1; i++)
- obj = obj[p[i] as String];
- }
- else if (p.length == 0)
- _tide.setComponentRemoteSync(compName, true);
-
- var previous:Object = null;
- var propName:String = null;
- if (p.length > 0) {
- propName = p[p.length-1] as String
-
- if (obj is IPropertyHolder)
- previous = IPropertyHolder(obj).object[propName];
- else if (obj != null)
- previous = obj[propName];
- }
- else
- previous = obj;
-
- // Don't merge with temporary properties
- if (previous is ComponentProperty || previous is Component)
- previous = null;
-
- val = meta_mergeExternal(val, previous);
-
- if (propName != null) {
- if (obj is IPropertyHolder) {
- var evt:ResultEvent = new ResultEvent(ResultEvent.RESULT, false, true, val);
- IPropertyHolder(obj).meta_propertyResultHandler(propName, evt);
- }
- else if (obj != null)
- obj[propName] = val;
- }
- else
- this[r.componentName] = val;
- }
- }
- }
-
- result = meta_mergeExternal(result, mergeWith);
- if (ires != null)
- InvocationResult(ires).result = result;
-
- meta_tracking = true;
-
- // Dispatch received data update events
- if (ires != null && InvocationResult(ires).updates)
- meta_handleUpdateEvents(InvocationResult(ires).updates);
-
- super.meta_result(componentName, operation, ires, result, mergeWith);
-
- log.debug("result merged into local context");
- }
-
- /**
+
+ // Handle scope changes
+ if (_tide.isComponentInEvent(componentName) && invocationResult.scope == Tide.SCOPE_SESSION && !meta_isGlobal()) {
+ var instance:Object = this[componentName];
+ this[componentName] = null;
+ _tide.setComponentScope(componentName, Tide.SCOPE_SESSION);
+ this[componentName] = instance;
+ }
+
+ _tide.setComponentScope(componentName, invocationResult.scope);
+ _tide.setComponentRestrict(componentName, invocationResult.restrict ? Tide.RESTRICT_YES : Tide.RESTRICT_NO);
+
+ meta_tracking = false;
+
+ var resultMap:IList = invocationResult.results;
+ if (resultMap) {
+ log.debug("result conversationId {0}", conversationId);
+
+ // Order the results by container, i.e. 'person.contacts' has to be evaluated after 'person'
+ var rmap:ListCollectionView = new ListCollectionView(resultMap);
+ rmap.sort = new Sort();
+ rmap.sort.compareFunction = meta_compareResults;
+ rmap.refresh();
+
+ for (var k:int = 0; k < rmap.length; k++) {
+ var r:ContextUpdate = rmap.getItemAt(k) as ContextUpdate;
+ var val:Object = r.value;
+
+ log.debug("update expression {0}: {1}", r, BaseContext.toString(val));
+ _lastResults.addItem(r.componentName + (r.expression != null ? "." + r.expression : ""));
+
+ var compName:String = r.componentName;
+ if (val != null) {
+ if (_tide.getComponentRestrict(compName) == Tide.RESTRICT_UNKNOWN)
+ _tide.setComponentRestrict(compName, r.restrict ? Tide.RESTRICT_YES : Tide.RESTRICT_NO);
+
+ if (_tide.getComponentScope(compName) == Tide.SCOPE_UNKNOWN)
+ _tide.setComponentScope(compName, r.scope);
+ }
+ _tide.setComponentGlobal(compName, true);
+
+ var obj:Object = meta_getInstanceNoProxy(compName);
+ var p:Array = r.expression != null ? r.expression.split(".") : [];
+ if (p.length > 1) {
+ for (var i:int = 0; i < p.length-1; i++)
+ obj = obj[p[i] as String];
+ }
+ else if (p.length == 0)
+ _tide.setComponentRemoteSync(compName, true);
+
+ var previous:Object = null;
+ var propName:String = null;
+ if (p.length > 0) {
+ propName = p[p.length-1] as String
+
+ if (obj is IPropertyHolder)
+ previous = IPropertyHolder(obj).object[propName];
+ else if (obj != null)
+ previous = obj[propName];
+ }
+ else
+ previous = obj;
+
+ // Don't merge with temporary properties
+ if (previous is ComponentProperty || previous is Component)
+ previous = null;
+
+ var res:ContextResult = new ContextResult(r.componentName, r.expression);
+
+ if (!meta_isGlobal() && r.scope == Tide.SCOPE_SESSION)
+ val = _tide.getContext().meta_mergeExternal(val, previous, res);
+ else
+ val = meta_mergeExternal(val, previous, res);
+
+ if (propName != null) {
+ if (obj is IPropertyHolder) {
+ var evt:ResultEvent = new ResultEvent(ResultEvent.RESULT, false, true, val);
+ IPropertyHolder(obj).meta_propertyResultHandler(propName, evt);
+ }
+ else if (obj != null)
+ obj[propName] = val;
+ }
+ else
+ this[r.componentName] = val;
+ }
+ }
+ }
+
+ // Merges final result object
+ if (result) {
+ result = meta_mergeExternal(result, mergeWith);
+ if (ires != null)
+ InvocationResult(ires).result = result;
+ }
+
+ meta_tracking = true;
+
+ //this['statusMessages'].setFromServer(invocationResult);
+
+ if (ires != null) {
+ // Remove all received results from current results list
+ var newResults:Array = new Array();
+ for each (var cr:ContextResult in _results) {
+ var found:Boolean = false;
+ for each (var u:ContextUpdate in rmap) {
+ if (cr.matches(u.componentName, u.expression)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ newResults.push(cr);
+ }
+ _results = newResults;
+
+ // Dispatch received data update events
+ if (InvocationResult(ires).updates)
+ meta_handleUpdateEvents(InvocationResult(ires).updates);
+
+ // Dispatch received context events
+ var events:IList = invocationResult.events;
+ if(events!=null)
+ {
+ for (var ie:uint = 0; ie < events.length; ie++) {
+ var event:ContextEvent = events.getItemAt(ie) as ContextEvent;
+ meta_internalRaiseEvent(event.eventType, event.params);
+ }
+ }
+ }
+
+ super.meta_result(componentName, operation, ires, result, mergeWith);
+ log.debug("result merged into local context");
+ }
+ private function meta_compareResults(r1:ContextUpdate, r2:ContextUpdate, fields:Array = null):int {
+ if (r1.componentName != r2.componentName)
+ return r1.componentName < r2.componentName ? -1 : 1;
+
+ if (r1.expression == null)
+ return r2.expression == null ? 0 : -1;
+
+ if (r2.expression == null)
+ return 1;
+
+ if (r1.expression == r2.expression)
+ return 0;
+
+ if (r1.expression.indexOf(r2.expression) == 0)
+ return 1;
+ if (r2.expression.indexOf(r1.expression) == 0)
+ return -1;
+
+ return r1.expression < r2.expression ? -1 : 0;
+ }
+
+ /**
  * @private
- * Manages a remote call fault
- *
- * @param componentName name of the target component
- * @param operation name of the called operation
- * @param emsg error message
- */
- public override function meta_fault(componentName:String, operation:String, emsg:ErrorMessage):void {
- _pendingUpdates.removeAll();
-
- super.meta_fault(componentName, operation, emsg);
- }
-
-
- /**
+ * Implements managed entity functionality
+ * All entities delegate setters to this context method
+ *
+ * @param entity managed entity
+ * @param propName property name
+ * @param oldValue previous value
+ * @param newValue new value
+ */
+ public override function meta_setEntityProperty(entity:IEntity, propName:String, oldValue:*, newValue:*):void {
+ super.meta_setEntityProperty(entity, propName, oldValue, newValue);
+
+ meta_addUpdates(entity);
+ }
+
+
+ /**
  * @private
- * Implements managed entity functionality
- * All entities delegate setters to this context method
- *
- * @param entity managed entity
- * @param propName property name
- * @param oldValue previous value
- * @param newValue new value
- */
- public override function meta_setEntityProperty(entity:IEntity, propName:String, oldValue:*, newValue:*):void {
- super.meta_setEntityProperty(entity, propName, oldValue, newValue);
-
- meta_addUpdates(entity);
- }
-
- /**
+ * Implements managed entity functionality
+ * All entities delegate getters to this context method
+ *
+ * @param entity managed entity
+ * @param propName property name
+ * @param value previous value
+ *
+ * @return new value
+ */
+ public override function meta_getEntityProperty(entity:IEntity, propName:String, value:*):* {
+ if (value is IEntity || value is IList || value is IPersistentCollection) {
+ var ref:IExpression = meta_getReference(entity, false);
+ if (ref)
+ meta_addResult(ref.componentName, ref.expression ? ref.expression + "." + propName : propName);
+ }
+
+ return super.meta_getEntityProperty(entity, propName, value);
+ }
+
+
+ /**
  * @private
- * Handles updates on managed collections
- *
- * @param event collection event
- */
- public override function meta_collectionChangeHandler(event:CollectionEvent):void {
- super.meta_collectionChangeHandler(event);
-
- if (event.kind == CollectionEventKind.ADD || event.kind == CollectionEventKind.REMOVE)
- meta_addUpdates(event.target);
- }
-
- /**
+ * Handles updates on managed collections
+ *
+ * @param event collection event
+ */
+ public override function meta_collectionChangeHandler(event:CollectionEvent):void {
+ super.meta_collectionChangeHandler(event);
+
+ if (event.kind == CollectionEventKind.ADD || event.kind == CollectionEventKind.REMOVE)
+ meta_addUpdates(event.target);
+ }
+
+ /**
  * @private
- * Handles updates on managed collections
- *
- * @param event collection event
- */
- public override function meta_entityCollectionChangeHandler(event:CollectionEvent):void {
- super.meta_entityCollectionChangeHandler(event);
-
- if (event.items && event.items.length > 0 && event.items[0] is IEntity)
- meta_addUpdates(event.target);
- else if (event.kind == CollectionEventKind.UPDATE && event.items && event.items.length > 0) {
- var pce:PropertyChangeEvent = event.items[0];
- if (pce.source is IEntity)
- meta_addUpdates(event.target);
- }
- }
-
- /**
+ * Handles updates on managed collections
+ *
+ * @param event collection event
+ */
+ public override function meta_entityCollectionChangeHandler(event:CollectionEvent):void {
+ super.meta_entityCollectionChangeHandler(event);
+
+ if (event.items && event.items.length > 0 && event.items[0] is IEntity)
+ meta_addUpdates(event.target);
+ else if (event.kind == CollectionEventKind.UPDATE && event.items && event.items.length > 0) {
+ var pce:PropertyChangeEvent = event.items[0];
+ if (pce.source is IEntity)
+ meta_addUpdates(event.target);
+ }
+ }
+
+ /**
  * @private
- * Handles updates on managed maps
- *
- * @param event collection event
- */
- public override function meta_mapChangeHandler(event:CollectionEvent):void {
- super.meta_mapChangeHandler(event);
-
- if (event.kind == CollectionEventKind.ADD || event.kind == CollectionEventKind.REMOVE)
- meta_addUpdates(event.target);
- }
-
- /**
+ * Handles updates on managed maps
+ *
+ * @param event collection event
+ */
+ public override function meta_mapChangeHandler(event:CollectionEvent):void {
+ super.meta_mapChangeHandler(event);
+
+ if (event.kind == CollectionEventKind.ADD || event.kind == CollectionEventKind.REMOVE)
+ meta_addUpdates(event.target);
+ }
+
+ /**
  * @private
- * Handles updates on managed collections
- *
- * @param event collection event
- */
- public override function meta_entityMapChangeHandler(event:CollectionEvent):void {
- super.meta_entityMapChangeHandler(event);
-
- if (event.items && event.items.length > 0 && event.items[0] is Array && event.items[0][1] is IEntity)
- meta_addUpdates(event.target);
- else if (event.kind == CollectionEventKind.UPDATE && event.items && event.items.length > 0) {
- var pce:PropertyChangeEvent = event.items[0];
- if (pce.source is IEntity)
- meta_addUpdates(event.target);
- }
- }
-
-
- /**
+ * Handles updates on managed collections
+ *
+ * @param event collection event
+ */
+ public override function meta_entityMapChangeHandler(event:CollectionEvent):void {
+ super.meta_entityMapChangeHandler(event);
+
+ if (event.items && event.items.length > 0 && event.items[0] is Array && event.items[0][1] is IEntity)
+ meta_addUpdates(event.target);
+ else if (event.kind == CollectionEventKind.UPDATE && event.items && event.items.length > 0) {
+ var pce:PropertyChangeEvent = event.items[0];
+ if (pce.source is IEntity)
+ meta_addUpdates(event.target);
+ }
+ }
+
+
+ /**
  * @private
- * Add updates to referencing objects of provided object
- *
- * @param entity object
- */
- private function meta_addUpdates(entity:Object):void {
- if (!meta_tracking || meta_finished)
- return;
-
- var ref:IExpression = meta_getReference(entity);
- if (ref)
- meta_addUpdate(ref.componentName, ref.expression, meta_evaluate(ref));
- }
- }
+ * Recursively add updates to referencing objects of provided object
+ *
+ * @param entity object
+ */
+ private function meta_addUpdates(entity:Object):void {
+ if (!meta_tracking || meta_finished)
+ return;
+
+ var ref:IExpression = meta_getReference(entity);
+ if (ref)
+ meta_addUpdate(ref.componentName, ref.expression, meta_evaluate(ref), ref is TypedContextExpression);
+ }
+
+ /**
+ * @private
+ * Recursively add results for referencing objects of provided object
+ *
+ * @param entity object
+ *
+ * @return a result has been added
+ */
+ private function meta_addResults(entity:Object):Boolean {
+ if (!meta_tracking || meta_finished)
+ return false;
+
+ var ref:IExpression = meta_getReference(entity);
+ if (ref)
+ return meta_addResult(ref.componentName, ref.expression);
+ return false;
+ }
+ }
 }



heyoulin added a comment - 18/Mar/10 05:31 AM
Edit server patch:

### Eclipse Workspace Patch 1.0
#P graniteds
Index: spring/org/granite/tide/spring/TideInvocation.java
===================================================================
--- spring/org/granite/tide/spring/TideInvocation.java (revision 0)
+++ spring/org/granite/tide/spring/TideInvocation.java (revision 0)
@@ -0,0 +1,104 @@
+package org.granite.tide.spring;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.granite.tide.invocation.ContextEvent;
+import org.granite.tide.invocation.ContextUpdate;
+
+
+/**
+ * @author William DRAI
+ */
+public class TideInvocation {
+
+ private static ThreadLocal<TideInvocation> invocation = new ThreadLocal<TideInvocation>() {
+ @Override
+ protected TideInvocation initialValue() {
+ return new TideInvocation();
+ }
+ };
+
+ public static TideInvocation get() {
+ return invocation.get();
+ }
+
+ public static TideInvocation init() {
+ TideInvocation ti = new TideInvocation();
+ invocation.set(ti);
+ return ti;
+ }
+
+ public static void remove() {
+ invocation.remove();
+ }
+
+ private boolean locked = false;
+ private boolean enabled = false;
+ private boolean updated = false;
+ private boolean evaluated = false;
+ private final List<ContextUpdate> updates = new ArrayList<ContextUpdate>();
+ private final List<ContextUpdate> results = new ArrayList<ContextUpdate>();
+ private final List<ContextEvent> events = new ArrayList<ContextEvent>();
+
+
+ public List<ContextUpdate> getUpdates() {
+ return updates;
+ }
+
+ public List<ContextUpdate> getResults() {
+ return results;
+ }
+
+ public List<ContextEvent> getEvents() {
+ return events;
+ }
+
+ public void update(List<ContextUpdate> updates) {
+ this.enabled = true;
+ this.updated = false;
+ this.updates.clear();
+ if (updates != null)
+ this.updates.addAll(updates);
+ }
+ public void updated() {
+ this.updated = true;
+ this.updates.clear();
+ }
+ public boolean isUpdated() {
+ return this.updated;
+ }
+
+ public void evaluate() {
+ this.evaluated = false;
+ this.results.clear();
+ }
+ public void evaluated(List<ContextUpdate> results) {
+ this.evaluated = true;
+// this.results.clear();
+ this.results.addAll(results);
+ this.updated = false;
+ this.updates.clear();
+ }
+ public boolean isEvaluated() {
+ return this.evaluated;
+ }
+
+ public void addEvent(ContextEvent event) {
+ events.add(event);
+ }
+
+ public void lock() {
+ this.locked = true;
+ }
+ public void unlock() {
+ this.locked = false;
+ }
+ public boolean isLocked() {
+ return this.locked;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+}
Index: spring/org/granite/tide/spring/SpringMVCServiceContext.java
===================================================================
--- spring/org/granite/tide/spring/SpringMVCServiceContext.java (revision 3702)
+++ spring/org/granite/tide/spring/SpringMVCServiceContext.java (working copy)
@@ -20,7 +20,9 @@
 
 package org.granite.tide.spring;
 
+
 import java.lang.reflect.Method;
+import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -29,11 +31,13 @@
 import java.util.Map;
 import java.util.Set;
 
+import javax.persistence.Entity;
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
 
+import org.granite.config.GraniteConfig;
 import org.granite.context.GraniteContext;
 import org.granite.logging.Logger;
 import org.granite.messaging.amf.io.convert.Converter;
@@ -45,10 +49,13 @@
 import org.granite.tide.IInvocationResult;
 import org.granite.tide.TidePersistenceManager;
 import org.granite.tide.data.DataContext;
+import org.granite.tide.invocation.ContextResult;
 import org.granite.tide.invocation.ContextUpdate;
 import org.granite.tide.invocation.InvocationCall;
 import org.granite.tide.invocation.InvocationResult;
+import org.granite.tide.spring.security.Identity3;
 import org.granite.util.ClassUtil;
+import org.granite.util.Reflections;
 import org.springframework.beans.TypeMismatchException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.core.MethodParameter;
@@ -73,9 +80,13 @@
     private static final String REQUEST_VALUE = "__REQUEST_VALUE__";
     
     private static final Logger log = Logger.getLogger(SpringMVCServiceContext.class);
+ private ResultsEval resultsEval=new ResultsEval();
+
+ public Map<ContextResult, Boolean> getResultsEval() {
+ return resultsEval.getResultsEval();
+ }
 
-
- @Override
+ @Override
     public Object adjustInvokee(Object instance, String componentName, Set<Class<?>> componentClasses) {
      for (Class<?> componentClass : componentClasses) {
  if (componentClass.isAnnotationPresent(org.springframework.stereotype.Controller.class)) {
@@ -166,10 +177,24 @@
     @Override
     public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) {
      super.prepareCall(context, c, componentName, componentClass);
-
      if (componentName == null)
      return;
+ InvocationCall call = (InvocationCall)c;
+ //List<String> listeners = call.getListeners();
+ List<ContextUpdate> updates = call.getUpdates();
+ Object[] results = call.getResults();
     
+ if (results != null) {
+ Map<ContextResult, Boolean> resultsEval = getResultsEval();
+ for (Object result : results) {
+ ContextResult cr = (ContextResult)result;
+ resultsEval.put(cr, Boolean.TRUE);
+ }
+ }
+ TideInvocation tideInvocation = TideInvocation.init();
+ tideInvocation.update(updates);
+ restoreContext(updates, componentName, null);
+ tideInvocation.updated();
  Object component = findComponent(componentName, componentClass);
 
  if (context.getBean() instanceof HandlerAdapter) {
@@ -201,12 +226,87 @@
  }
     }
     
-
+ public void restoreContext(List<ContextUpdate> updates, String componentName, Object target) {
+ if (updates == null)
+ return;
+
+ GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
+
+ // Restore context
+ for (ContextUpdate update : updates) {
+ if(update.getComponentName()==null)
+ continue;
+ if(update.getComponentName().equals("identity"))
+ continue;
+ log.debug("Before invocation: evaluating expression #0.#1", update.getComponentName(), update.getExpression());
+
+ Object bean = findComponent(update.getComponentName(),null);
+ //String sourceComponentName = sourceComponent != null ? sourceComponent.getName() : update.getComponentName();
+
+ Object previous = null;
+ Object value=bean;
+ if (update.getExpression() != null) {
+ if (bean != null && config.isComponentTideDisabled(update.getComponentName(), findComponentClasses(update.getComponentName(),bean.getClass()), bean))
+ continue;
+ String[] path = update.getExpression().split("\\.");
+ if (update.getValue() != null) {
+ boolean getPrevious = true;
+ if (update.getValue().getClass().getAnnotation(Entity.class) != null) {
+ org.granite.util.Entity entity = new org.granite.util.Entity(update.getValue());
+ if (entity.getIdentifier() == null)
+ getPrevious = false;
+ }
+ if (getPrevious) {
+ try{
+ try {
+ for (int i = 0; i < path.length; i++) {
+ if (value == null)
+ break;
+ Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
+ value = Reflections.invoke(getter, value);
+ if (i < path.length-1)
+ bean = value;
+ }
+ }
+ catch (IllegalArgumentException e) {
+ // No getter found to retrieve current value
+ log.warn("Partial merge only: " + e.getMessage());
+ value = null;
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Could not get property: " + update.toString(), e);
+ }
+ previous = value;
+ if (bean != null) {
+ Method setter = Reflections.getSetterMethod(bean.getClass(), path[path.length-1]);
+ Type type = setter.getParameterTypes()[0];
+
+ value = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters().convert(update.getValue(), type);
+ // Merge entities into current persistent context if needed
+ value = mergeExternal(value, previous);
+ Reflections.invoke(setter, bean, value);
+ }
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Could not restore property: " + update.toString(), e);
+ }
+ }
+ }
+ }
+ }
+ }
     @Override
     @SuppressWarnings("unchecked")
     public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) {
- List<ContextUpdate> results = null;
-
+ TideInvocation tideInvocation = TideInvocation.get();
+
+ List<ContextUpdate> results = null;
+ if (!tideInvocation.isEvaluated()) {
+ // Do evaluation now if the interceptor has not been called
+ results = evaluateResults(componentName, false);
+ }
+ else
+ results = tideInvocation.getResults();
      if (componentName != null && context.getBean() instanceof HandlerAdapter) {
  Object component = findComponent(componentName, componentClass);
 
@@ -281,19 +381,96 @@
  }
  }
      }
-
+ int scope = 3;
+ boolean restrict = false;
+
      DataContext dataContext = DataContext.get();
  Set<Object[]> dataUpdates = dataContext != null ? dataContext.getDataUpdates() : null;
  Object[][] updates = null;
  if (dataUpdates != null && !dataUpdates.isEmpty())
  updates = dataUpdates.toArray(new Object[dataUpdates.size()][]);
-
+
+ InvocationResult res = new InvocationResult(result, results);
+ res.setScope(scope);
+ res.setRestrict(restrict);
+
+ //res.setUpdates(updates);
+ res.setEvents(tideInvocation.getEvents());
+
         InvocationResult ires = new InvocationResult(result, results);
         ires.setUpdates(updates);
-
+ TideInvocation.remove();
         return ires;
     }
 
+
+ /**
+ * Evaluate results from context
+ *
+ * @param component the target component
+ * @param target the target instance
+ * @param nothing used by initializer to avoid interactions with context sync
+ *
+ * @return list of updates to send back to the client
+ */
+ public List<ContextUpdate> evaluateResults(String componentName,boolean nothing) {
+
+ List<ContextUpdate> resultsMap = new ArrayList<ContextUpdate>();
+
+ if (nothing)
+ return resultsMap;
+
+ GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
+ ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
+
+ for (Map.Entry<ContextResult, Boolean> me : getResultsEval().entrySet()) {
+ if (!me.getValue())
+ continue;
+
+ ContextResult res = me.getKey();
+ if(res.getComponentName()==null || componentName==null)
+ continue;
+ if(!componentName.equals(res.getComponentName()) || componentName.equals("identity"))
+ continue;
+ Object targetComponent = findComponent(res.getComponentName(), null);
+ if(targetComponent==null)
+ continue;
+ boolean add = true;
+ Object value = targetComponent;
+ Boolean restrict = res.getRestrict();
+ if (value != null && config.isComponentTideDisabled(res.getComponentName(), findComponentClasses(res.getComponentName(),value.getClass()), value))
+ add = false;
+
+ if (add) {
+ getResultsEval().put(res, false);
+ String[] path = res.getExpression() != null ? res.getExpression().split("\\.") : new String[0];
+ for (int i = 0; i < path.length; i++) {
+ if (value == null)
+ break;
+ Method getter = org.granite.util.Reflections.getGetterMethod(value.getClass(), path[i]);
+ try {
+ value = org.granite.util.Reflections.invoke(getter, value);
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ if (value != null && classGetter != null) {
+ classGetter.initialize(null, null, value);
+
+ int scope = 3;
+
+ resultsMap.add(new ContextUpdate(res.getComponentName(), res.getExpression(), value, scope, Boolean.TRUE.equals(restrict)));
+ add = false;
+ }
+ }
+
+ me.setValue(Boolean.FALSE);
+ }
+
+ return resultsMap;
+ }
+
     @Override
     public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
      if (componentName != null && context.getBean() instanceof HandlerAdapter) {
Index: spring/org/granite/tide/spring/ResultsEval.java
===================================================================
--- spring/org/granite/tide/spring/ResultsEval.java (revision 0)
+++ spring/org/granite/tide/spring/ResultsEval.java (revision 0)
@@ -0,0 +1,24 @@
+package org.granite.tide.spring;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.granite.tide.invocation.ContextResult;
+
+
+/**
+ * @author William DRAI
+ */
+public class ResultsEval implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+
+ private Map<ContextResult, Boolean> resultsEval = new HashMap<ContextResult, Boolean>();
+
+
+ public Map<ContextResult, Boolean> getResultsEval() {
+ return resultsEval;
+ }
+}

William Draï added a comment - 22/Dec/10 07:32 PM
Requires merge of all Tide client-side providers.