001 /*
002 GRANITE DATA SERVICES
003 Copyright (C) 2012 GRANITE DATA SERVICES S.A.S.
004
005 This file is part of Granite Data Services.
006
007 Granite Data Services is free software; you can redistribute it and/or modify
008 it under the terms of the GNU Library General Public License as published by
009 the Free Software Foundation; either version 2 of the License, or (at your
010 option) any later version.
011
012 Granite Data Services is distributed in the hope that it will be useful, but
013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015 for more details.
016
017 You should have received a copy of the GNU Library General Public License
018 along with this library; if not, see <http://www.gnu.org/licenses/>.
019 */
020
021 package org.granite.client.tide.server;
022
023 import java.util.ArrayList;
024 import java.util.HashMap;
025 import java.util.List;
026 import java.util.Map;
027
028 import org.granite.client.tide.ScopeType;
029 import org.granite.client.tide.SyncMode;
030 import org.granite.logging.Logger;
031 import org.granite.tide.invocation.ContextResult;
032 import org.granite.tide.invocation.ContextUpdate;
033
034 /**
035 * @author William DRAI
036 */
037 public class TrackingContext {
038
039 private static final Logger log = Logger.getLogger(TrackingContext.class);
040
041 private boolean enabled = true;
042 private List<ContextUpdate> updates = new ArrayList<ContextUpdate>();
043 private List<ContextUpdate> pendingUpdates = new ArrayList<ContextUpdate>();
044 private List<ContextResult> results = new ArrayList<ContextResult>();
045 private List<String> lastResults = new ArrayList<String>();
046
047 /**
048 * @private
049 * Enable/disable tracking on the context
050 *
051 * @param enabled enable or disable tracking on the current context
052 */
053 public void setEnabled(boolean enabled) {
054 this.enabled = enabled;
055 }
056
057 /**
058 * @private
059 * Return tracking mode
060 *
061 * @return true if tracking enabled
062 */
063 public boolean isEnabled() {
064 return enabled;
065 }
066
067 /**
068 * @private
069 * @return current list of updates that will be sent to the server
070 */
071 public List<ContextUpdate> getUpdates() {
072 return updates;
073 }
074
075 /**
076 * @private
077 * @return current list of results that will be requested from the server
078 */
079 public List<ContextResult> getResults() {
080 return results;
081 }
082
083 /**
084 * @private
085 * Resets the context
086 */
087 public void clear() {
088 updates.clear();
089 pendingUpdates.clear();
090 results.clear();
091 lastResults.clear();
092 }
093
094 /**
095 * Resets the current updates and optionally saves them as pending
096 *
097 * @param savePending moves existing updates to pending before clearing
098 */
099 public void clearUpdates(boolean savePending) {
100 if (savePending)
101 pendingUpdates = new ArrayList<ContextUpdate>(updates);
102 updates.clear();
103 }
104
105 /**
106 * Resets the current pending updates
107 */
108 public void clearPendingUpdates() {
109 pendingUpdates.clear();
110 lastResults.clear();
111 }
112
113 /**
114 * Filter the current updates with a function
115 */
116 public void filterUpdates(UpdateFilter filter) {
117 // Keep only updates for identity component
118 for (int i = 0; i < updates.size(); i++) {
119 ContextUpdate u = updates.get(i);
120 if (!filter.accept(u)) {
121 updates.remove(i);
122 i--;
123 }
124 }
125 }
126
127 public static interface UpdateFilter {
128 public boolean accept(ContextUpdate u);
129 }
130
131
132 /**
133 * @private
134 * Add update to current context
135 * Note: always move the update in last position in case the value can depend on previous updates
136 *
137 * @param componentName name of the component/context variable or null if typed component reference
138 * @param componentClassName class name of the component/context variable or null if untyped component name
139 * @param expr EL expression to evaluate
140 * @param value value to send to server
141 * @param typed component name represents a typed component instance
142 */
143 public void addUpdate(String componentName, String componentClassName, String expr, Object value) {
144 internalAddUpdate(componentName, componentClassName, expr, value, ScopeType.EVENT, SyncMode.NONE, componentName == null);
145 }
146
147 /**
148 * @private
149 * Add update to current context
150 * Note: always move the update in last position in case the value can depend on previous updates
151 *
152 * @param componentName name of the component/context variable or null if typed component reference
153 * @param componentClassName class name of the component/context variable or null if untyped component name
154 * @param expr EL expression to evaluate
155 * @param value value to send to server
156 * @param scope scope of the result
157 * @param sync remote sync mode of the result
158 * @param typed component name represents a typed component instance
159 */
160 protected void internalAddUpdate(String componentName, String componentClassName, String expr, Object value, ScopeType scope, SyncMode sync, boolean typed) {
161 if (!enabled)
162 return;
163
164 boolean found = false;
165 for (int i = 0; i < updates.size(); i++) {
166 ContextUpdate u = updates.get(i);
167 if (u.getComponentName() == componentName && u.getComponentClassName() == componentClassName && u.getExpression() == expr) {
168 u.setValue(value);
169 if (i < updates.size()-1) {
170 found = false;
171 updates.remove(i); // Remove here to add it in last position
172 i--;
173 }
174 else
175 found = true;
176 }
177 else if (u.getComponentName() == componentName && u.getComponentClassName() == componentClassName && u.getExpression() != null && (expr == null || u.getExpression().indexOf(expr + ".") == 0)) {
178 updates.remove(i);
179 i--;
180 }
181 else if (u.getComponentName() == componentName && u.getComponentClassName() == componentClassName && expr != null && (u.getExpression() == null || expr.indexOf(u.getExpression() + ".") == 0))
182 found = true;
183 }
184
185 if (!found) {
186 log.debug("add new update {0}", (componentName != null ? componentName : "") + (componentClassName != null ? "(" + componentClassName + ")" : "") + (expr != null ? "." + expr : ""));
187 ContextUpdate cu = new ContextUpdate(componentName, expr, value, scope.ordinal(), false);
188 cu.setComponentClassName(componentClassName);
189 updates.add(cu);
190 }
191 }
192
193 /**
194 * @private
195 * Add result evaluator in current context
196 *
197 * @param componentName name of the component/context variable
198 * @param componentClassName class name of the component/context variable
199 * @param expr EL expression to evaluate
200 * @param instance current instance of the component
201 *
202 * @return true if the result was not already present in the current context
203 */
204 public boolean addResult(String componentName, String componentClassName, String expr, Object instance) {
205 return internalAddResult(componentName, componentClassName, expr, instance, ScopeType.EVENT, SyncMode.NONE);
206 }
207
208 /**
209 * @private
210 * Add result evaluator in current context
211 *
212 * @param componentName name of the component/context variable
213 * @param componentClassName class name of the component/context variable
214 * @param expr EL expression to evaluate
215 * @param instance current instance of the component
216 * @param scope scope of the update
217 * @param sync remote sync mode of the update
218 *
219 * @return true if the result was not already present in the current context
220 */
221 protected boolean internalAddResult(String componentName, String componentClassName, String expr, Object instance, ScopeType scope, SyncMode sync) {
222 if (!enabled || sync == SyncMode.NONE || (instance == null && expr == null))
223 return false;
224
225 // Check in existing results
226 for (ContextResult r : results) {
227 if (r.getComponentName() == componentName && r.getComponentClassName() == componentClassName && r.getExpression() == expr)
228 return false;
229 }
230
231 // Check in last received results
232 String e = componentName + (componentClassName != null ? "(" + componentClassName + ")" : "") + (expr != null ? "." + expr : "");
233 if (lastResults.indexOf(e) >= 0)
234 return false;
235
236 log.debug("add new result {0}", e);
237 // TODO: should store somewhere if the client componentName is the same as the server bean name
238 ContextResult cr = new ContextResult(componentName, expr);
239 cr.setComponentClassName(componentClassName);
240 results.add(cr);
241 return true;
242 }
243
244
245 public void addLastResult(String res) {
246 lastResults.add(res);
247 }
248
249
250 public void removeResults(List<ContextUpdate> rmap) {
251 // Remove all received results from current results list
252 List<ContextResult> newResults = new ArrayList<ContextResult>();
253 for (ContextResult cr : this.results) {
254 boolean found = false;
255 for (ContextUpdate u : rmap) {
256 if (cr.matches(u.getComponentName(), u.getComponentClassName(), u.getExpression())) {
257 found = true;
258 break;
259 }
260 }
261 if (!found)
262 newResults.add(cr);
263 }
264 this.results = newResults;
265 }
266
267
268 /**
269 * Trace the current tracking context
270 */
271 public void traceContext() {
272 log.debug("updates: %s", updates.toString());
273 log.debug("results: %s", results.toString());
274 }
275
276
277 /**
278 * @private
279 *
280 * Reset current tracking context and returns saved context
281 *
282 * @return saved tracking context
283 */
284 public Map<String, Object> saveAndResetContext() {
285 Map<String, Object> savedTrackingContext = new HashMap<String, Object>();
286 savedTrackingContext.put("updates", new ArrayList<ContextUpdate>(updates));
287 savedTrackingContext.put("results", new ArrayList<ContextResult>(results));
288 savedTrackingContext.put("pendingUpdates", new ArrayList<ContextUpdate>(pendingUpdates));
289 savedTrackingContext.put("lastResults", new ArrayList<String>(lastResults));
290
291 updates.clear();
292 pendingUpdates.clear();
293 results.clear();
294 lastResults.clear();
295
296 return savedTrackingContext;
297 }
298
299 /**
300 * @private
301 *
302 * Restore tracking context
303 *
304 * @param trackingContext object containing the current call context
305 */
306 @SuppressWarnings("unchecked")
307 public void restoreContext(Map<String, Object> trackingContext) {
308 updates = (List<ContextUpdate>)trackingContext.get("updates");
309 results = (List<ContextResult>)trackingContext.get("results");
310 pendingUpdates = (List<ContextUpdate>)trackingContext.get("pendingUpdates");
311 lastResults = (List<String>)trackingContext.get("lastResults");
312 }
313 }