root/incubator/hbncontainer/src/com/itmill/toolkit/data/hbnutil/HbnContainer.java @ 6383

Revision 6381, 16.0 KB (checked in by matti.tahvonen@…, 20 months ago)

enhanced to have an example of column generators also

  • Property svn:executable set to *
Line 
1package com.itmill.toolkit.data.hbnutil;
2
3import java.io.Serializable;
4import java.lang.reflect.Constructor;
5import java.util.Collection;
6import java.util.HashMap;
7import java.util.LinkedHashMap;
8import java.util.LinkedList;
9import java.util.List;
10import java.util.Map;
11
12import org.hibernate.Criteria;
13import org.hibernate.EntityMode;
14import org.hibernate.HibernateException;
15import org.hibernate.Session;
16import org.hibernate.criterion.Criterion;
17import org.hibernate.criterion.Disjunction;
18import org.hibernate.criterion.Order;
19import org.hibernate.criterion.Projections;
20import org.hibernate.criterion.Restrictions;
21import org.hibernate.criterion.SimpleExpression;
22import org.hibernate.metadata.ClassMetadata;
23import org.hibernate.type.Type;
24
25import com.itmill.toolkit.data.Container;
26import com.itmill.toolkit.data.Item;
27import com.itmill.toolkit.data.Property;
28
29/**
30 * Example Container to use with Hibernate.
31 *
32 * Lazy, almost full-featured, general purpose Hibernate entity container. Makes
33 * lots of queries, but shouldn't consume too much memory.
34 *
35 * HbnContainer expects to be used with session-per-request pattern. In in its
36 * constructor it will only need entity class type (Pojo) and a SessionManager
37 * that it will use to get a hibernate sesion with open transaction.
38 *
39 * HbnContainer also expects that identifiers are auto generated.
40 *
41 * Note, container caches size, firstId, lastId to be much faster with large
42 * datasets. TODO make this caching optional, actually should trust on
43 * Hibernates and DB engines query caches.
44 *
45 * TODO ContainerFilterable
46 *
47 * TODO Better documentation
48 *
49 */
50public class HbnContainer implements Container.Indexed, Container.Sortable,
51                Container.ItemSetChangeNotifier {
52
53        public interface SessionManager {
54                /**
55                 * @return a Hibernate Session with open transaction
56                 */
57                Session getSession();
58        }
59
60        /**
61         * Item wrappping an entity class.
62         */
63        public class EntityItem implements Item {
64
65                protected Object pojo;
66
67                protected Map<Object, Property> properties = new HashMap<Object, Property>();
68
69                public EntityItem(Serializable id) {
70                        pojo = sessionManager.getSession().get(type, id);
71                }
72
73                public Object getPojo() {
74                        return pojo;
75                }
76
77                public boolean addItemProperty(Object id, Property property)
78                                throws UnsupportedOperationException {
79                        return false;
80                }
81
82                public Property getItemProperty(Object id) {
83                        Property p = properties.get(id);
84                        if (p == null) {
85                                p = new EntityItemProperty(id.toString());
86                                properties.put(id, p);
87                        }
88                        return p;
89                }
90
91                public Collection getItemPropertyIds() {
92                        return getContainerPropertyIds();
93                }
94
95                public boolean removeItemProperty(Object id)
96                                throws UnsupportedOperationException {
97                        return false;
98                }
99
100                public class EntityItemProperty implements Property {
101
102                        private String propertyName;
103
104                        public EntityItemProperty(String propertyName) {
105                                this.propertyName = propertyName;
106                        }
107
108                        public Class getType() {
109                                return getClassMetadata().getPropertyType(propertyName)
110                                                .getReturnedClass();
111                        }
112
113                        public Object getValue() {
114                                return getClassMetadata().getPropertyValue(pojo, propertyName,
115                                                EntityMode.POJO);
116                        }
117
118                        public boolean isReadOnly() {
119                                return false;
120                        }
121
122                        public void setReadOnly(boolean newStatus) {
123                        }
124
125                        public void setValue(Object newValue) throws ReadOnlyException,
126                                        ConversionException {
127                                try {
128
129                                        Object value;
130                                        try {
131                                                if (getType().isAssignableFrom(newValue.getClass())) {
132                                                        value = newValue;
133                                                } else {
134                                                        // Gets the string constructor
135                                                        final Constructor constr = getType()
136                                                                        .getConstructor(
137                                                                                        new Class[] { String.class });
138
139                                                        value = constr.newInstance(new Object[] { newValue
140                                                                        .toString() });
141                                                }
142
143                                                getClassMetadata().setPropertyValue(pojo, propertyName,
144                                                                value, EntityMode.POJO);
145                                                // Persist (possibly) detached pojo
146                                                sessionManager.getSession().merge(pojo);
147                                        } catch (final java.lang.Exception e) {
148                                                throw new Property.ConversionException(e);
149                                        }
150
151                                } catch (HibernateException e) {
152                                        e.printStackTrace();
153                                }
154                        }
155
156                        @Override
157                        public String toString() {
158                                return getValue().toString();
159                        }
160                }
161        }
162
163        private static final int ROW_BUF_SIZE = 100;
164        private static final int ID_TO_INDEX_MAX_SIZE = 300;
165
166        /** Entity class that will be listed in container */
167        protected Class type;
168        protected final SessionManager sessionManager;
169        private ClassMetadata classMetadata;
170
171        /** internal flag used to temporarely invert order of listing */
172        private boolean asc = true;
173
174        private List ascRowBuffer;
175        private List descRowBuffer;
176        private Object lastId;
177        private Object firstId;
178        private int indexRowBufferFirstIndex;
179        private List indexRowBuffer;
180        private Map<Object, Integer> idToIndex = new LinkedHashMap<Object, Integer>();
181        private boolean[] orderAscendings;
182        private Object[] orderPropertyIds;
183        private Integer size;
184        private LinkedList<ItemSetChangeListener> itemSetChangeListeners;
185
186        /**
187         * Creates a new instance of HbnContainer, listing all object of givent
188         * type.
189         *
190         * @param entityType
191         *            Entity class to be listed in container.
192         * @param sessionMgr
193         *            interface via Hibernate session is fetched
194         */
195        public HbnContainer(Class entityType, SessionManager sessionMgr) {
196                type = entityType;
197                sessionManager = sessionMgr;
198        }
199
200        public boolean addContainerProperty(Object propertyId, Class type,
201                        Object defaultValue) throws UnsupportedOperationException {
202                // can't modify properties as they are defined by Pojo/DB
203                throw new UnsupportedOperationException();
204        }
205
206        public Object addItem() throws UnsupportedOperationException {
207                Object o;
208                try {
209                        o = type.newInstance();
210                        // insert into DB
211                        sessionManager.getSession().save(o);
212                        clearInternalCache();
213                        fireItemSetChange();
214                        return getIdForPojo(o);
215                } catch (InstantiationException e) {
216                        e.printStackTrace();
217                        return null;
218                } catch (IllegalAccessException e) {
219                        e.printStackTrace();
220                        return null;
221                }
222        }
223
224        public Item addItem(Object itemId) throws UnsupportedOperationException {
225                // Expecting autogenerated identifiers
226                throw new UnsupportedOperationException();
227        }
228
229        public boolean containsId(Object itemId) {
230                // test if entity can be found with given id
231                try {
232                        return (sessionManager.getSession()
233                                        .get(type, (Serializable) itemId) != null);
234                } catch (Exception e) {
235                        // this should not happen if used correctly
236                        e.printStackTrace();
237                        return false;
238                }
239        }
240
241        public Property getContainerProperty(Object itemId, Object propertyId) {
242                return getItem(itemId).getItemProperty(propertyId);
243        }
244
245        @SuppressWarnings("unchecked")
246        public Collection getContainerPropertyIds() {
247                // use Hibernates metadata helper to determine property names
248                String[] propertyNames = getClassMetadata().getPropertyNames();
249                LinkedList properyIds = new LinkedList();
250                for (int i = 0; i < propertyNames.length; i++) {
251                        properyIds.add(propertyNames[i]);
252                }
253                return properyIds;
254        }
255
256        /**
257         * @return Hibernates ClassMetadata for the listed entity type
258         */
259        private ClassMetadata getClassMetadata() {
260                if (classMetadata == null) {
261                        classMetadata = sessionManager.getSession().getSessionFactory()
262                                        .getClassMetadata(type);
263                }
264                return classMetadata;
265        }
266
267        public Item getItem(Object itemId) {
268                return loadItem((Serializable) itemId);
269        }
270
271        /**
272         * This method is used to fetch Items by id. Override this if you need
273         * customized EntityItems.
274         *
275         * @param itemId
276         * @return
277         */
278        protected EntityItem loadItem(Serializable itemId) {
279                return new EntityItem(itemId);
280        }
281
282        public Collection getItemIds() {
283                List list = sessionManager.getSession().createQuery(
284                                "select " + getClassMetadata().getIdentifierPropertyName()
285                                                + " from " + getClassMetadata().getEntityName()).list();
286                return list;
287        }
288
289        public Class getType(Object propertyId) {
290                Type propertyType = getClassMetadata().getPropertyType(
291                                propertyId.toString());
292                return propertyType.getReturnedClass();
293        }
294
295        public boolean removeAllItems() throws UnsupportedOperationException {
296                // TODO
297                return false;
298        }
299
300        public boolean removeContainerProperty(Object propertyId)
301                        throws UnsupportedOperationException {
302                // can't modify properties as they are defined by Pojo/DB
303                throw new UnsupportedOperationException();
304        }
305
306        public boolean removeItem(Object itemId)
307                        throws UnsupportedOperationException {
308                Object p = sessionManager.getSession()
309                                .load(type, (Serializable) itemId);
310                // remove row from db
311                sessionManager.getSession().delete(p);
312                clearInternalCache();
313                fireItemSetChange();
314                return true;
315        }
316
317        public void addListener(ItemSetChangeListener listener) {
318                if (itemSetChangeListeners == null) {
319                        itemSetChangeListeners = new LinkedList<ItemSetChangeListener>();
320                }
321                itemSetChangeListeners.add(listener);
322        }
323
324        public void removeListener(ItemSetChangeListener listener) {
325                if (itemSetChangeListeners != null) {
326                        itemSetChangeListeners.remove(listener);
327                }
328
329        }
330
331        private void fireItemSetChange() {
332                if (itemSetChangeListeners != null) {
333                        final Object[] l = itemSetChangeListeners.toArray();
334                        final Container.ItemSetChangeEvent event = new Container.ItemSetChangeEvent() {
335                                public Container getContainer() {
336                                        return HbnContainer.this;
337                                }
338                        };
339                        for (int i = 0; i < l.length; i++) {
340                                ((ItemSetChangeListener) l[i]).containerItemSetChange(event);
341                        }
342                }
343        }
344
345        public int size() {
346                if (size == null) {
347                        size = (Integer) getBaseCriteria().setProjection(
348                                        Projections.rowCount()).uniqueResult();
349                }
350                return size.intValue();
351        }
352
353        public Object addItemAfter(Object previousItemId)
354                        throws UnsupportedOperationException {
355                // TODO Auto-generated method stub
356                return null;
357        }
358
359        public Item addItemAfter(Object previousItemId, Object newItemId)
360                        throws UnsupportedOperationException {
361                // TODO Auto-generated method stub
362                return null;
363        }
364
365        /**
366         * Gets a base listing using current orders etc.
367         *
368         * @return criteria with current Order criterias added
369         */
370        private Criteria getCriteria() {
371                return addOrder(getBaseCriteria()).addOrder(getNaturalOrder());
372        }
373
374        private Criteria getBaseCriteria() {
375                return sessionManager.getSession().createCriteria(type);
376        }
377
378        private Order getNaturalOrder() {
379                if (asc) {
380                        return Order.asc(getIdPropertyName());
381                } else {
382                        return Order.desc(getIdPropertyName());
383                }
384        }
385
386        private Criteria addOrder(Criteria criteria) {
387                if (orderPropertyIds != null) {
388                        for (int i = 0; i < orderPropertyIds.length; i++) {
389                                boolean a = asc ? orderAscendings[i] : !orderAscendings[i];
390                                if (a) {
391                                        criteria = criteria.addOrder(Order.asc(orderPropertyIds[i]
392                                                        .toString()));
393                                } else {
394                                        criteria = criteria.addOrder(Order.desc(orderPropertyIds[i]
395                                                        .toString()));
396                                }
397                        }
398                }
399                return criteria;
400        }
401
402        public Object firstItemId() {
403                if (firstId == null) {
404                        firstId = firstItemId(true);
405                }
406                return firstId;
407        }
408
409        public Object firstItemId(boolean byPassCache) {
410                if (byPassCache) {
411                        Object first = getCriteria().setMaxResults(1).setCacheable(true)
412                                        .uniqueResult();
413                        return getIdForPojo(first);
414                } else {
415                        return firstItemId();
416                }
417        }
418
419        private Object getIdForPojo(Object pojo) {
420                return getClassMetadata().getIdentifier(pojo, EntityMode.POJO);
421        }
422
423        public boolean isFirstId(Object itemId) {
424                return itemId.equals(firstItemId());
425        }
426
427        public boolean isLastId(Object itemId) {
428                return itemId.equals(lastItemId());
429        }
430
431        public Object lastItemId() {
432                if (lastId == null) {
433                        asc = !asc;
434                        lastId = firstItemId(true);
435                        asc = !asc;
436                }
437                return lastId;
438        }
439
440        /*
441         * Simple method, but lot's of code :-)
442         *
443         * Rather complicated logic is needed to avoid: - large number of db queries
444         * - scrolling through whole query result
445         *
446         * This way this container can be used with large data sets.
447         */
448        public Object nextItemId(Object itemId) {
449                if (isLastId(itemId)) {
450                        return null;
451                }
452
453                EntityItem item = new EntityItem((Serializable) itemId);
454
455                // check if next itemId is in current buffer
456                List buffer = getRowBuffer();
457                try {
458                        int curBufIndex = buffer.indexOf(item.getPojo());
459                        if (curBufIndex != -1) {
460                                Object object = buffer.get(curBufIndex + 1);
461                                return getIdForPojo(object);
462                        }
463                } catch (Exception e) {
464                        // not in buffer
465                }
466
467                // itemId was not in buffer
468                // build query with current order and limiting result set with the
469                // reference row. Then first result is next item.
470
471                Criteria crit = getCriteria();
472                Disjunction afterCurrent = Restrictions.disjunction();
473
474                Criterion curEq = null;
475                if (orderAscendings != null) {
476                        for (int i = 0; i < orderAscendings.length; i++) {
477                                String col = orderPropertyIds[i].toString();
478                                Object colVal = item.getItemProperty(col).getValue();
479
480                                boolean ruleAsc = asc ? orderAscendings[i]
481                                                : !orderAscendings[i];
482
483                                SimpleExpression gtCurOrder = ruleAsc ? Restrictions.gt(col,
484                                                colVal) : Restrictions.lt(col, colVal);
485                                if (i == 0) {
486                                        curEq = Restrictions.eq(col, colVal);
487                                        afterCurrent.add(gtCurOrder);
488                                } else {
489                                        afterCurrent.add(Restrictions.and(curEq, gtCurOrder));
490                                        curEq = Restrictions.and(curEq, Restrictions
491                                                        .eq(col, colVal));
492                                }
493                        }
494                }
495                SimpleExpression naturalGt;
496                if (asc) {
497                        naturalGt = Restrictions.gt(getIdPropertyName(), itemId);
498                } else {
499                        naturalGt = Restrictions.lt(getIdPropertyName(), itemId);
500                }
501                if (curEq != null) {
502                        afterCurrent.add(Restrictions.and(curEq, naturalGt));
503                        crit.add(afterCurrent);
504                } else {
505                        crit.add(naturalGt);
506                }
507
508                crit = crit.setMaxResults(ROW_BUF_SIZE);
509                List newBuffer = crit.list();
510                if (newBuffer.size() > 0) {
511                        // save buffer to optimize query count
512                        setRowBuffer(newBuffer);
513                        Object nextPojo = newBuffer.get(0);
514                        return getIdForPojo(nextPojo);
515                } else {
516                        return null;
517                }
518        }
519
520        /**
521         * RowBuffer stores some pojos to avoid excessive number of DB queries.
522         *
523         * @return
524         */
525        private List getRowBuffer() {
526                if (asc) {
527                        return ascRowBuffer;
528                } else {
529                        return descRowBuffer;
530                }
531        }
532
533        /**
534         * RowBuffer stores some pojos to avoid excessive number of DB queries.
535         */
536        private void setRowBuffer(List list) {
537                if (asc) {
538                        ascRowBuffer = list;
539                } else {
540                        descRowBuffer = list;
541                }
542        }
543
544        /**
545         * @return column name of identifier property
546         */
547        private String getIdPropertyName() {
548                return getClassMetadata().getIdentifierPropertyName();
549        }
550
551        public Object prevItemId(Object itemId) {
552                // temp flip order and use nextItemId
553                asc = !asc;
554                Object prev = nextItemId(itemId);
555                asc = !asc;
556                return prev;
557        }
558
559        // Container.Indexed
560
561        public Object addItemAt(int index) throws UnsupportedOperationException {
562                throw new UnsupportedOperationException();
563        }
564
565        public Item addItemAt(int index, Object newItemId)
566                        throws UnsupportedOperationException {
567                throw new UnsupportedOperationException();
568        }
569
570        public Object getIdByIndex(int index) {
571                if (indexRowBuffer == null) {
572                        resetIndexRowBuffer(index);
573                }
574                int indexInCache = index - indexRowBufferFirstIndex;
575                if (!(indexInCache >= 0 && indexInCache < indexRowBuffer.size())) {
576                        resetIndexRowBuffer(index);
577                        indexInCache = 0;
578                }
579                Object pojo = indexRowBuffer.get(indexInCache);
580                Object id = getIdForPojo(pojo);
581                idToIndex.put(id, new Integer(index));
582                if (idToIndex.size() > ID_TO_INDEX_MAX_SIZE) {
583                        // clear one from beginning
584                        idToIndex.remove(idToIndex.keySet().iterator().next());
585                }
586                return id;
587        }
588
589        private void resetIndexRowBuffer(int index) {
590                indexRowBufferFirstIndex = index;
591                indexRowBuffer = getCriteria().setFirstResult(index).setMaxResults(
592                                ROW_BUF_SIZE).list();
593        }
594
595        /*
596         * Note! Expects that getIdByIndex is called for this itemId. When used with
597         * Table, this shouldn't be a problem.
598         *
599         * TODO make workaround for this. Too bad it is going to be a very slow
600         * operation.
601         */
602        public int indexOfId(Object itemId) {
603                Integer index = idToIndex.get(itemId);
604                return index;
605        }
606
607        // Container.Sortable methods
608
609        public Collection getSortableContainerPropertyIds() {
610                return getContainerPropertyIds();
611        }
612
613        public void sort(Object[] propertyId, boolean[] ascending) {
614                clearInternalCache();
615                orderPropertyIds = propertyId;
616                orderAscendings = ascending;
617        }
618
619        private void clearInternalCache() {
620                idToIndex.clear();
621                indexRowBuffer = null;
622                ascRowBuffer = null;
623                descRowBuffer = null;
624                firstId = null;
625                lastId = null;
626                size = null;
627        }
628
629}
Note: See TracBrowser for help on using the browser.