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.collections.javafx;
022
023 import java.lang.reflect.Method;
024 import java.util.Collections;
025 import java.util.HashMap;
026 import java.util.List;
027 import java.util.Map;
028 import java.util.concurrent.Future;
029
030 import javax.annotation.PreDestroy;
031 import javax.inject.Named;
032
033 import javafx.beans.property.ObjectProperty;
034 import javafx.beans.property.SimpleObjectProperty;
035 import javafx.collections.FXCollections;
036 import javafx.collections.MapChangeListener;
037 import javafx.collections.ObservableMap;
038
039 import org.granite.client.tide.Context;
040 import org.granite.client.tide.ContextAware;
041 import org.granite.client.tide.Initializable;
042 import org.granite.client.tide.NameAware;
043 import org.granite.client.tide.PropertyHolder;
044 import org.granite.client.tide.impl.ComponentImpl;
045 import org.granite.client.tide.server.Component;
046 import org.granite.client.tide.server.ServerSession;
047 import org.granite.client.tide.server.TideResultEvent;
048 import org.granite.logging.Logger;
049 import org.granite.tide.data.model.Page;
050 import org.granite.tide.data.model.PageInfo;
051 import org.granite.tide.data.model.SortInfo;
052 import org.granite.util.TypeUtil;
053
054 /**
055 * Implementation of the Tide paged collection with an generic service backend.<br/>
056 * <br/>
057 * By default the corresponding service should have the same name and expose a 'find' method<br/>
058 * that returns a Map with the following properties :<br/>
059 * <pre>
060 * resultCount
061 * resultList
062 * firstResult
063 * maxResults
064 * </pre>
065 *
066 * The name of the remote service can be overriden by setting the remoteComponentName property.
067 * The name of the remote method can by set by the remoteMethodName property.
068 *
069 * @author William DRAI
070 */
071 @Named
072 public class PagedQuery<E, F> extends PagedCollection<E> implements Component, PropertyHolder, NameAware, ContextAware, Initializable {
073
074 private static Logger log = Logger.getLogger(PagedQuery.class);
075
076 protected Component component = null;
077
078 private final ServerSession serverSession;
079 private String remoteComponentName = null;
080 private Context context = null;
081
082 protected String methodName = "find";
083 protected boolean methodNameSet = false;
084
085 protected boolean usePage = false;
086
087 private SortInfo sortInfo = new SortInfo();
088 protected SortAdapter sortAdapter = null;
089
090 private Map<String, Object> internalFilterMap = new HashMap<String, Object>();
091 private ObservableMap<String, Object> filterMap = FXCollections.observableMap(Collections.synchronizedMap(internalFilterMap));
092 private Class<F> filterClass = null;
093 private ObjectProperty<F> filter = null;
094
095
096 protected PagedQuery() {
097 this.serverSession = null;
098 // CDI proxying...
099 }
100
101 public PagedQuery(ServerSession serverSession) {
102 this.serverSession = serverSession;
103
104 this.filterMap.addListener(new MapChangeListener<String, Object>() {
105 @Override
106 public void onChanged(MapChangeListener.Change<? extends String, ?> change) {
107 fullRefresh = true;
108 filterRefresh = true;
109 }
110 });
111 }
112
113 public void setName(String componentName) {
114 remoteComponentName = componentName;
115 }
116
117 public void setContext(Context context) {
118 this.context = context;
119 if (component instanceof ContextAware)
120 ((ContextAware)component).setContext(context);
121 }
122
123 public void init() {
124 component = new ComponentImpl(serverSession);
125 ((ComponentImpl)component).setName(remoteComponentName);
126 ((ComponentImpl)component).setContext(context);
127 }
128
129
130 public void setSortAdapter(SortAdapter sortAdapter) {
131 this.sortAdapter = sortAdapter;
132 if (sortAdapter != null)
133 sortAdapter.apply(sortInfo);
134 }
135
136 public SortAdapter getSortAdapter() {
137 return sortAdapter;
138 }
139
140 public void resetSort() {
141 this.sortAdapter = null;
142 sortInfo.setOrder(null);
143 sortInfo.setDesc(null);
144 }
145
146
147 public ObjectProperty<F> filterProperty() {
148 return filter;
149 }
150 @SuppressWarnings("unchecked")
151 public F getFilter() {
152 if (filter != null)
153 return filter.get();
154 try {
155 return (F)filterMap;
156 }
157 catch (ClassCastException e) {
158 return null;
159 }
160 }
161 public void setFilter(F filter) {
162 this.filter.set(filter);
163 }
164 @SuppressWarnings("unchecked")
165 public void setFilterClass(Class<F> filterClass) throws IllegalAccessException, InstantiationException {
166 if (Map.class.isAssignableFrom(filterClass))
167 return;
168 this.filterClass = filterClass;
169 this.filter = new SimpleObjectProperty<F>(this, "filter");
170 setFilter((F)TypeUtil.newInstance(filterClass, Object.class));
171 }
172
173 @SuppressWarnings("unchecked")
174 public void resetFilter() {
175 internalFilterMap.clear();
176 if (filterClass != null) {
177 try {
178 setFilter((F)TypeUtil.newInstance(filterClass, Object.class));
179 }
180 catch (Exception e) {
181 log.error(e, "Could not reset typed filter for PagedQuery %s", getName());
182 }
183 }
184 }
185
186 @Override
187 @PreDestroy
188 public void clear() {
189 super.clear();
190 }
191
192 public void reset() {
193 resetFilter();
194 resetSort();
195 clear();
196 }
197
198 @Override
199 public boolean refresh() {
200 if (filter != null && this.context.getEntityManager().isDeepDirtyEntity(filter.get())) {
201 filterRefresh = true;
202 fullRefresh = true;
203 }
204 return super.refresh();
205 }
206
207
208 public String getName() {
209 return remoteComponentName;
210 }
211
212 public void setRemoteComponentName(String remoteComponentName) {
213 if (remoteComponentName != this.remoteComponentName) {
214 Object component = context.byName(remoteComponentName);
215 if (component == null || !(component instanceof ComponentImpl)) {
216 this.component = new ComponentImpl(serverSession);
217 ((ComponentImpl)this.component).setName(remoteComponentName);
218 ((ComponentImpl)this.component).setContext(context);
219 context.set(remoteComponentName, component);
220 }
221 }
222 else {
223 this.component = new ComponentImpl(serverSession);
224 ((ComponentImpl)this.component).setName(remoteComponentName);
225 ((ComponentImpl)this.component).setContext(context);
226 }
227 }
228
229 public void setRemoteComponentClass(Class<? extends ComponentImpl> remoteComponentClass) throws IllegalAccessException, InstantiationException {
230 component = TypeUtil.newInstance(remoteComponentClass, new Class<?>[] { ServerSession.class }, new Object[] { serverSession });
231 }
232
233 public void setMethodName(String methodName) {
234 this.methodName = methodName;
235 this.methodNameSet = true;
236 }
237
238 public void setUsePage(boolean usePage) {
239 this.usePage = usePage;
240 }
241
242
243 /**
244 * Trigger a results query for the current filter
245 * @param first : index of first required result
246 * @param last : index of last required result
247 */
248 protected void find(int first, int last) {
249 super.find(first, last);
250
251 int max = 0;
252 if (this.initializing && this.max > 0)
253 max = this.max;
254 else if (!this.initializing)
255 max = last-first;
256
257 PagedCollectionResponder findResponder = new PagedCollectionResponder(first, max);
258 Object filter = null;
259 if (this.filter != null)
260 filter = this.filter.get();
261 else {
262 // Copy filter map to avoid concurrent modifications
263 synchronized (internalFilterMap) {
264 filter = new HashMap<String, Object>(internalFilterMap);
265 }
266 }
267
268 doFind(filter, first, max, findResponder);
269 }
270
271 protected synchronized void doFind(Object filter, int first, int max, PagedCollectionResponder findResponder) {
272 // Force evaluation of max, results and count
273 if (sortAdapter != null)
274 sortAdapter.retrieve(sortInfo);
275
276 String[] order = sortInfo.getOrder();
277 if (order != null && order.length == 0)
278 order = null;
279 boolean[] desc = sortInfo.getDesc();
280 if (desc != null && desc.length == 0)
281 desc = null;
282
283 boolean usePage = this.usePage;
284 try {
285 for (Method m : component.getClass().getMethods()) {
286 if (m.getName().equals(methodName) && m.getParameterTypes().length >= 2
287 && PageInfo.class.isAssignableFrom(m.getParameterTypes()[1])) {
288 usePage = true;
289 break;
290 }
291 }
292 }
293 catch (Exception e) {
294 // Untyped component proxy
295 }
296
297 if (usePage) {
298 PageInfo pageInfo = new PageInfo(first, max, order, desc);
299 component.call(methodName, new Object[] { filter, pageInfo, findResponder });
300 }
301 else {
302 component.call(methodName, new Object[] { filter, first, max, order, desc, findResponder });
303 }
304 }
305
306 @Override
307 @SuppressWarnings("unchecked")
308 protected Page<E> getResult(TideResultEvent<?> event, int first, int max) {
309 if (event.getResult() instanceof Page<?>)
310 return (Page<E>)event.getResult();
311
312 Map<String, Object> result = (Map<String, Object>)event.getResult();
313 Page<E> page = new Page<E>(result.containsKey("firstResult") ? (Integer)result.get("firstResult") : first,
314 result.containsKey("maxResults") ? (Integer)result.get("maxResults") : max,
315 ((Number)result.get("resultCount")).intValue(), (List<E>)result.get("resultList"));
316 return page;
317 }
318
319
320 /**
321 * PropertyHolder interface
322 */
323 public Object getObject() {
324 if (component instanceof PropertyHolder)
325 return ((PropertyHolder)component).getObject();
326 return null;
327 }
328
329 public void setProperty(String propName, Object value) {
330 if (component instanceof PropertyHolder)
331 ((PropertyHolder)component).setProperty(propName, value);
332 }
333
334
335 @Override
336 public <T> Future<T> call(String operation, Object... args) {
337 throw new UnsupportedOperationException();
338 }
339
340 }