| 1 | package com.itmill.toolkit.data.hbnutil; |
|---|
| 2 | |
|---|
| 3 | import java.io.Serializable; |
|---|
| 4 | import java.lang.reflect.Constructor; |
|---|
| 5 | import java.util.Collection; |
|---|
| 6 | import java.util.HashMap; |
|---|
| 7 | import java.util.LinkedHashMap; |
|---|
| 8 | import java.util.LinkedList; |
|---|
| 9 | import java.util.List; |
|---|
| 10 | import java.util.Map; |
|---|
| 11 | |
|---|
| 12 | import org.hibernate.Criteria; |
|---|
| 13 | import org.hibernate.EntityMode; |
|---|
| 14 | import org.hibernate.HibernateException; |
|---|
| 15 | import org.hibernate.Session; |
|---|
| 16 | import org.hibernate.criterion.Criterion; |
|---|
| 17 | import org.hibernate.criterion.Disjunction; |
|---|
| 18 | import org.hibernate.criterion.Order; |
|---|
| 19 | import org.hibernate.criterion.Projections; |
|---|
| 20 | import org.hibernate.criterion.Restrictions; |
|---|
| 21 | import org.hibernate.criterion.SimpleExpression; |
|---|
| 22 | import org.hibernate.metadata.ClassMetadata; |
|---|
| 23 | import org.hibernate.type.Type; |
|---|
| 24 | |
|---|
| 25 | import com.itmill.toolkit.data.Container; |
|---|
| 26 | import com.itmill.toolkit.data.Item; |
|---|
| 27 | import 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 | */ |
|---|
| 50 | public 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 | } |
|---|