001package co.codewizards.cloudstore.local.persistence; 002 003import static co.codewizards.cloudstore.core.util.Util.*; 004 005import java.lang.reflect.ParameterizedType; 006import java.lang.reflect.Type; 007import java.util.ArrayList; 008import java.util.Collection; 009import java.util.HashMap; 010import java.util.HashSet; 011import java.util.Iterator; 012import java.util.Map; 013import java.util.Set; 014 015import javax.jdo.JDOHelper; 016import javax.jdo.JDOObjectNotFoundException; 017import javax.jdo.PersistenceManager; 018import javax.jdo.Query; 019import javax.jdo.identity.LongIdentity; 020 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * Base class for all data access objects (DAOs). 026 * <p> 027 * Usually an instance of a DAO is obtained using 028 * {@link co.codewizards.cloudstore.local.LocalRepoTransactionImpl#getDAO(Class) LocalRepoTransaction.getDAO(...)}. 029 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 030 */ 031public abstract class DAO<E extends Entity, D extends DAO<E, D>> 032{ 033 private final Logger logger; 034 private final Class<E> entityClass; 035 private final Class<D> daoClass; 036 037 /** 038 * Instantiate the DAO. 039 * <p> 040 * It is recommended <b>not</b> to invoke this constructor directly, but instead use 041 * {@link co.codewizards.cloudstore.local.LocalRepoTransactionImpl#getDAO(Class) LocalRepoTransaction.getDAO(...)}, 042 * if a {@code LocalRepoTransaction} is available (which should be in most situations). 043 * <p> 044 * After constructing, you must {@linkplain #persistenceManager(PersistenceManager) assign a <code>PersistenceManager</code>}, 045 * before you can use the DAO. This is already done when using the {@code LocalRepoTransaction}'s factory method. 046 */ 047 public DAO() { 048 ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass(); 049 Type[] actualTypeArguments = superclass.getActualTypeArguments(); 050 if (actualTypeArguments == null || actualTypeArguments.length < 2) 051 throw new IllegalStateException("Subclass " + getClass().getName() + " has no generic type argument!"); 052 053 @SuppressWarnings("unchecked") 054 Class<E> c = (Class<E>) actualTypeArguments[0]; 055 this.entityClass = c; 056 if (this.entityClass == null) 057 throw new IllegalStateException("Subclass " + getClass().getName() + " has no generic type argument!"); 058 059 @SuppressWarnings("unchecked") 060 Class<D> k = (Class<D>) actualTypeArguments[1]; 061 this.daoClass = k; 062 if (this.daoClass == null) 063 throw new IllegalStateException("Subclass " + getClass().getName() + " has no generic type argument!"); 064 065 logger = LoggerFactory.getLogger(String.format("%s<%s>", DAO.class.getName(), entityClass.getSimpleName())); 066 } 067 068 private PersistenceManager pm; 069 070 /** 071 * Gets the {@code PersistenceManager} assigned to this DAO. 072 * @return the {@code PersistenceManager} assigned to this DAO. May be <code>null</code>, if none 073 * was assigned, yet. 074 * @see #setPersistenceManager(PersistenceManager) 075 * @see #persistenceManager(PersistenceManager) 076 */ 077 public PersistenceManager getPersistenceManager() { 078 return pm; 079 } 080 /** 081 * Assigns the given {@code PersistenceManager} to this DAO. 082 * <p> 083 * The DAO cannot be used, before a non-<code>null</code> value was set using this method. 084 * @param persistenceManager the {@code PersistenceManager} to be used by this DAO. May be <code>null</code>, 085 * but a non-<code>null</code> value must be set to make this DAO usable. 086 * @see #persistenceManager(PersistenceManager) 087 */ 088 public void setPersistenceManager(PersistenceManager persistenceManager) { 089 if (this.pm != persistenceManager) { 090 daoClass2DaoInstance.clear(); 091 this.pm = persistenceManager; 092 } 093 } 094 095 protected PersistenceManager pm() { 096 if (pm == null) { 097 throw new IllegalStateException("persistenceManager not assigned!"); 098 } 099 return pm; 100 } 101 102 /** 103 * Assigns the given {@code PersistenceManager} to this DAO and returns {@code this}. 104 * <p> 105 * This method delegates to {@link #setPersistenceManager(PersistenceManager)}. 106 * @param persistenceManager the {@code PersistenceManager} to be used by this DAO. May be <code>null</code>, 107 * but a non-<code>null</code> value must be set to make this DAO usable. 108 * @return {@code this} for a fluent API. 109 * @see #setPersistenceManager(PersistenceManager) 110 */ 111 public D persistenceManager(PersistenceManager persistenceManager) { 112 setPersistenceManager(persistenceManager); 113 return thisDAO(); 114 } 115 116 protected D thisDAO() { 117 return daoClass.cast(this); 118 } 119 120 /** 121 * Get the type of the entity. 122 * @return the type of the entity; never <code>null</code>. 123 */ 124 public Class<E> getEntityClass() { 125 return entityClass; 126 } 127 128 /** 129 * Get the entity-instance referenced by the specified identifier. 130 * 131 * @param id the identifier referencing the desired entity. Must not be <code>null</code>. 132 * @return the entity-instance referenced by the specified identifier. Never <code>null</code>. 133 * @throws JDOObjectNotFoundException if the entity referenced by the given identifier does not exist. 134 */ 135 public E getObjectByIdOrFail(long id) 136 throws JDOObjectNotFoundException 137 { 138 return getObjectById(id, true); 139 } 140 141 /** 142 * Get the entity-instance referenced by the specified identifier. 143 * 144 * @param id the identifier referencing the desired entity. Must not be <code>null</code>. 145 * @return the entity-instance referenced by the specified identifier or <code>null</code>, if no 146 * such entity exists. 147 */ 148 public E getObjectByIdOrNull(long id) 149 { 150 return getObjectById(id, false); 151 } 152 153 /** 154 * Get the entity-instance referenced by the specified identifier. 155 * 156 * @param id the identifier referencing the desired entity. Must not be <code>null</code>. 157 * @param throwExceptionIfNotFound <code>true</code> to (re-)throw a {@link JDOObjectNotFoundException}, 158 * if the referenced entity does not exist; <code>false</code> to return <code>null</code> instead. 159 * @return the entity-instance referenced by the specified identifier or <code>null</code>, if no 160 * such entity exists and <code>throwExceptionIfNotFound == false</code>. 161 * @throws JDOObjectNotFoundException if the entity referenced by the given identifier does not exist 162 * and <code>throwExceptionIfNotFound == true</code>. 163 */ 164 private E getObjectById(long id, boolean throwExceptionIfNotFound) 165 throws JDOObjectNotFoundException 166 { 167 try { 168 Object result = pm().getObjectById(new LongIdentity(entityClass, id)); 169 return entityClass.cast(result); 170 } catch (JDOObjectNotFoundException x) { 171 if (throwExceptionIfNotFound) 172 throw x; 173 else 174 return null; 175 } 176 } 177 178 public Collection<E> getObjects() { 179 ArrayList<E> result = new ArrayList<E>(); 180 Iterator<E> iterator = pm().getExtent(entityClass).iterator(); 181 while (iterator.hasNext()) { 182 result.add(iterator.next()); 183 } 184 return result; 185 } 186 187 public long getObjectsCount() { 188 Query query = pm().newQuery(entityClass); 189 query.setResult("count(this)"); 190 Long result = (Long) query.execute(); 191 if (result == null) 192 throw new IllegalStateException("Query for count(this) returned null!"); 193 194 return result; 195 } 196 197 public <P extends E> P makePersistent(final P entity) 198 { 199 assertNotNull("entity", entity); 200 try { 201 final P result = pm().makePersistent(entity); 202 logger.debug("makePersistent: entityID={}", JDOHelper.getObjectId(result)); 203 return result; 204 } catch (final RuntimeException x) { 205 logger.warn("makePersistent: FAILED for entityID={}: {}", JDOHelper.getObjectId(entity), x); 206 throw x; 207 } 208 } 209 210 public void deletePersistent(final E entity) 211 { 212 assertNotNull("entity", entity); 213 logger.debug("deletePersistent: entityID={}", JDOHelper.getObjectId(entity)); 214 pm().deletePersistent(entity); 215 } 216 217 public void deletePersistentAll(final Collection<? extends E> entities) 218 { 219 assertNotNull("entities", entities); 220 if (logger.isDebugEnabled()) { 221 for (E entity : entities) { 222 logger.debug("deletePersistentAll: entityID={}", JDOHelper.getObjectId(entity)); 223 } 224 } 225 pm().deletePersistentAll(entities); 226 } 227 228 protected Collection<E> load(final Collection<E> entities) { 229 final Collection<E> result = new ArrayList<>(); 230 final Map<Class<? extends Entity>, Set<Long>> entityClass2EntityIDs = new HashMap<>(); 231 for (E entity : entities) { 232 Set<Long> entityIDs = entityClass2EntityIDs.get(entity.getClass()); 233 if (entityIDs == null) { 234 entityIDs = new HashSet<>(); 235 entityClass2EntityIDs.put(entity.getClass(), entityIDs); 236 } 237 entityIDs.add(entity.getId()); 238 } 239 240 for (Map.Entry<Class<? extends Entity>, Set<Long>> me : entityClass2EntityIDs.entrySet()) { 241 Class<? extends Entity> entityClass = me.getKey(); 242 Query query = pm().newQuery(pm().getExtent(entityClass, false)); 243 query.setFilter(":entityIDs.contains(this.id)"); 244 245 Set<Long> entityIDs = me.getValue(); 246 int idx = -1; 247 Set<Long> entityIDSubSet = new HashSet<>(300); 248 for (Long entityID : entityIDs) { 249 ++idx; 250 entityIDSubSet.add(entityID); 251 if (idx > 200) { 252 idx = -1; 253 populateLoadResult(result, query, entityIDSubSet); 254 } 255 } 256 populateLoadResult(result, query, entityIDSubSet); 257 } 258 return result; 259 } 260 261 private void populateLoadResult(Collection<E> result, Query query, Set<Long> entityIDSubSet) { 262 if (entityIDSubSet.isEmpty()) 263 return; 264 265 @SuppressWarnings("unchecked") 266 Collection<E> c = (Collection<E>) query.execute(entityIDSubSet); 267 result.addAll(c); 268 query.closeAll(); 269 entityIDSubSet.clear(); 270 } 271 272 private final Map<Class<? extends DAO<?,?>>, DAO<?,?>> daoClass2DaoInstance = new HashMap<>(3); 273 274 protected <T extends DAO<?, ?>> T getDAO(Class<T> daoClass) { 275 T dao = daoClass.cast(daoClass2DaoInstance.get(assertNotNull("daoClass", daoClass))); 276 if (dao == null) { 277 try { 278 dao = daoClass.newInstance(); 279 } catch (InstantiationException e) { 280 throw new RuntimeException(e); 281 } catch (IllegalAccessException e) { 282 throw new RuntimeException(e); 283 } 284 dao.persistenceManager(pm); 285 daoClass2DaoInstance.put(daoClass, dao); 286 } 287 return dao; 288 } 289}