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    }