/* GRANITE DATA SERVICES Copyright (C) 2012 GRANITE DATA SERVICES S.A.S. This file is part of Granite Data Services. Granite Data Services is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Granite Data Services is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, see <http://www.gnu.org/licenses/>. */ package org.granite.client.tide.server; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.granite.client.tide.ScopeType; import org.granite.client.tide.SyncMode; import org.granite.logging.Logger; import org.granite.tide.invocation.ContextResult; import org.granite.tide.invocation.ContextUpdate; /** * @author William DRAI */ public class TrackingContext { private static final Logger log = Logger.getLogger(TrackingContext.class); private boolean enabled = true; private List<ContextUpdate> updates = new ArrayList<ContextUpdate>(); private List<ContextUpdate> pendingUpdates = new ArrayList<ContextUpdate>(); private List<ContextResult> results = new ArrayList<ContextResult>(); private List<String> lastResults = new ArrayList<String>(); /** * Enable/disable tracking on the context * * @param enabled enable or disable tracking on the current context */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * Return tracking mode * * @return true if tracking enabled */ public boolean isEnabled() { return enabled; } /** * @return current list of updates that will be sent to the server */ public List<ContextUpdate> getUpdates() { return updates; } /** * @return current list of results that will be requested from the server */ public List<ContextResult> getResults() { return results; } /** * Reset the context */ public void clear() { updates.clear(); pendingUpdates.clear(); results.clear(); lastResults.clear(); } /** * Reset the current updates and optionally saves them as pending * * @param savePending moves existing updates to pending before clearing */ public void clearUpdates(boolean savePending) { if (savePending) pendingUpdates = new ArrayList<ContextUpdate>(updates); updates.clear(); } /** * Reset the current pending updates */ public void clearPendingUpdates() { pendingUpdates.clear(); lastResults.clear(); } /** * Filter the current updates with a function */ public void filterUpdates(UpdateFilter filter) { // Keep only updates for identity component for (int i = 0; i < updates.size(); i++) { ContextUpdate u = updates.get(i); if (!filter.accept(u)) { updates.remove(i); i--; } } } public static interface UpdateFilter { public boolean accept(ContextUpdate u); } /** * 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 or null if typed component reference * @param componentClassName class name of the component/context variable or null if untyped component name * @param expr EL expression to evaluate * @param value value to send to server */ public void addUpdate(String componentName, String componentClassName, String expr, Object value) { internalAddUpdate(componentName, componentClassName, expr, value, ScopeType.EVENT, SyncMode.NONE, componentName == null); } /** * 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 or null if typed component reference * @param componentClassName class name of the component/context variable or null if untyped component name * @param expr EL expression to evaluate * @param value value to send to server * @param scope scope of the result * @param sync remote sync mode of the result * @param typed component name represents a typed component instance */ protected void internalAddUpdate(String componentName, String componentClassName, String expr, Object value, ScopeType scope, SyncMode sync, boolean typed) { if (!enabled) return; boolean found = false; for (int i = 0; i < updates.size(); i++) { ContextUpdate u = updates.get(i); if (u.getComponentName() == componentName && u.getComponentClassName() == componentClassName && u.getExpression() == expr) { u.setValue(value); if (i < updates.size()-1) { found = false; updates.remove(i); // Remove here to add it in last position i--; } else found = true; } else if (u.getComponentName() == componentName && u.getComponentClassName() == componentClassName && u.getExpression() != null && (expr == null || u.getExpression().indexOf(expr + ".") == 0)) { updates.remove(i); i--; } else if (u.getComponentName() == componentName && u.getComponentClassName() == componentClassName && expr != null && (u.getExpression() == null || expr.indexOf(u.getExpression() + ".") == 0)) found = true; } if (!found) { log.debug("add new update {0}", (componentName != null ? componentName : "") + (componentClassName != null ? "(" + componentClassName + ")" : "") + (expr != null ? "." + expr : "")); ContextUpdate cu = new ContextUpdate(componentName, expr, value, scope.ordinal(), false); cu.setComponentClassName(componentClassName); updates.add(cu); } } /** * Add result evaluator in current context * * @param componentName name of the component/context variable * @param componentClassName class name of the component/context variable * @param expr EL expression to evaluate * @param instance current instance of the component * * @return true if the result was not already present in the current context */ public boolean addResult(String componentName, String componentClassName, String expr, Object instance) { return internalAddResult(componentName, componentClassName, expr, instance, ScopeType.EVENT, SyncMode.NONE); } /** * Add result evaluator in current context * * @param componentName name of the component/context variable * @param componentClassName class name of the component/context variable * @param expr EL expression to evaluate * @param instance current instance of the component * @param scope scope of the update * @param sync remote sync mode of the update * * @return true if the result was not already present in the current context */ protected boolean internalAddResult(String componentName, String componentClassName, String expr, Object instance, ScopeType scope, SyncMode sync) { if (!enabled || sync == SyncMode.NONE || (instance == null && expr == null)) return false; // Check in existing results for (ContextResult r : results) { if (r.getComponentName() == componentName && r.getComponentClassName() == componentClassName && r.getExpression() == expr) return false; } // Check in last received results String e = componentName + (componentClassName != null ? "(" + componentClassName + ")" : "") + (expr != null ? "." + expr : ""); if (lastResults.indexOf(e) >= 0) return false; log.debug("add new result {0}", e); // TODO: should store somewhere if the client componentName is the same as the server bean name ContextResult cr = new ContextResult(componentName, expr); cr.setComponentClassName(componentClassName); results.add(cr); return true; } public void addLastResult(String res) { lastResults.add(res); } public void removeResults(List<ContextUpdate> rmap) { // Remove all received results from current results list List<ContextResult> newResults = new ArrayList<ContextResult>(); for (ContextResult cr : this.results) { boolean found = false; for (ContextUpdate u : rmap) { if (cr.matches(u.getComponentName(), u.getComponentClassName(), u.getExpression())) { found = true; break; } } if (!found) newResults.add(cr); } this.results = newResults; } /** * Trace the current tracking context */ public void traceContext() { log.debug("updates: %s", updates.toString()); log.debug("results: %s", results.toString()); } /** * Reset current tracking context and returns saved context * * @return saved tracking context */ public Map<String, Object> saveAndResetContext() { Map<String, Object> savedTrackingContext = new HashMap<String, Object>(); savedTrackingContext.put("updates", new ArrayList<ContextUpdate>(updates)); savedTrackingContext.put("results", new ArrayList<ContextResult>(results)); savedTrackingContext.put("pendingUpdates", new ArrayList<ContextUpdate>(pendingUpdates)); savedTrackingContext.put("lastResults", new ArrayList<String>(lastResults)); updates.clear(); pendingUpdates.clear(); results.clear(); lastResults.clear(); return savedTrackingContext; } /** * Restore tracking context * * @param trackingContext object containing the current call context */ @SuppressWarnings("unchecked") public void restoreContext(Map<String, Object> trackingContext) { updates = (List<ContextUpdate>)trackingContext.get("updates"); results = (List<ContextResult>)trackingContext.get("results"); pendingUpdates = (List<ContextUpdate>)trackingContext.get("pendingUpdates"); lastResults = (List<String>)trackingContext.get("lastResults"); } }