View Javadoc

1   /*
2    * Copyright 2006-2010 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sourceforge.domian.repository;
17  
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.util.Collection;
22  import java.util.Date;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.NoSuchElementException;
27  import java.util.Set;
28  import java.util.concurrent.Callable;
29  
30  import static org.apache.commons.io.FileUtils.deleteQuietly;
31  import static org.apache.commons.io.FileUtils.forceDelete;
32  import static org.apache.commons.io.FileUtils.forceMkdir;
33  import static org.apache.commons.lang.SystemUtils.FILE_SEPARATOR;
34  
35  import net.sourceforge.domian.entity.Entity;
36  import net.sourceforge.domian.repository.EntityPersistenceMetaData;
37  import net.sourceforge.domian.repository.PartitionRepository;
38  import net.sourceforge.domian.repository.PersistentEntity;
39  import net.sourceforge.domian.repository.RepositoryException;
40  import net.sourceforge.domian.specification.Specification;
41  import net.sourceforge.domian.specification.SpecificationUtils;
42  import static net.sourceforge.domian.specification.SpecificationUtils.typeSafeIsSatisfiedBy;
43  import static net.sourceforge.domian.util.InstrumentationUtils.buildMessageWithStackTrace;
44  
45  import com.thoughtworks.xstream.persistence.FilePersistenceStrategy;
46  import com.thoughtworks.xstream.persistence.PersistenceStrategy;
47  import com.thoughtworks.xstream.persistence.XmlMap;
48  import com.thoughtworks.xstream.persistence.XmlSet;
49  
50  
51  /**
52   * Common functionality for all Domian XStream-based repositories,
53   * where <i>each individual</i> entity is represented by its own XStream XML file.
54   *
55   * @author Eirik Torske
56   * @since 0.4
57   */
58  abstract class AbstractXStreamXmlFilePerEntityRepository<T extends Entity> extends AbstractXStreamXmlFileRepository<T> {
59  
60      /**
61       * A naming scheme for file-per-entity Domian XStream persistence.
62       * This particular naming scheme is based on the entities' IDs.
63       *
64       * @return the file name for this entity based on its entity-ID
65       */
66      protected static String getEntityIdBasedFilenameFrom(final Entity entity) {
67          return entity.getClass().getName() + '@' + entity.getEntityId() + XSTREAM_XML_FILE_SUFFIX;
68      }
69  
70  
71      protected void createRepositoryFilesIfNotExist() {
72          final String repositoryRootPath = getRepositoryRootPath();
73          try {
74              forceMkdir(new File(repositoryRootPath));
75              log.info("Repository root '" + repositoryRootPath + "' created (if not already in place...)");
76  
77          } catch (IOException e) {
78              log.error("Error when creating repository directory " + repositoryRootPath, e);
79          }
80  
81          try {
82              forceMkdir(getRepositoryDirectory());
83              log.info("Repository '" + this.repositoryId + "' created (if not already in place...)");
84  
85          } catch (IOException e) {
86              log.error("Error when creating repository directory " + this.repositoryId, e);
87          }
88      }
89  
90      ///////////////////////////////////////////////
91      // Repository operations as Callable classes //
92      ///////////////////////////////////////////////
93  
94      protected class IterateAllEntitiesSpecifiedBy<T> implements Callable<Iterator<T>> {
95          private Specification<T> specification;
96  
97          protected IterateAllEntitiesSpecifiedBy(Specification<T> specification) {
98              this.specification = specification;
99          }
100 
101         @Override
102         public Iterator<T> call() throws Exception {
103             return new XStreamXmlFileEntityIterator<T>(specification);
104         }
105     }
106 
107 
108     protected class FindAllEntitiesSpecifiedBy<T> implements Callable<Collection<T>> {
109         private Specification<T> specification;
110 
111         protected FindAllEntitiesSpecifiedBy(Specification<T> specification) {
112             this.specification = specification;
113         }
114 
115         @Override
116         public Collection<T> call() {
117             final Collection<T> selection = new HashSet<T>();
118             final PersistenceStrategy persistenceStrategy = new EntityId_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory());
119             final Set<PersistentEntity> persistentEntitySet = new EntityIdNamedXStreamXmlSet(persistenceStrategy);
120             if (persistentEntitySet != null && !persistentEntitySet.isEmpty()) { // NullpointerExceptions with no happy ending experienced...
121                 for (final PersistentEntity persistentEntity : persistentEntitySet) {
122                     if (persistentEntity != null) {
123                         final Entity entity = persistentEntity.getEntity();
124                         if (typeSafeIsSatisfiedBy(this.specification, entity)) {
125                             persistentEntity.touchReadMetaData();
126                             runAsynchronously(new PutWithoutEntityExistsCheck(persistentEntity.getEntity(), persistentEntity.getMetaData()));
127                             selection.add(this.specification.getType().cast(entity));
128                         }
129                     }
130                 }
131             }
132             return selection;
133         }
134     }
135 
136 
137     protected class PutWithoutEntityExistsCheck<T extends Entity> implements Runnable {
138         private T entity;
139         private EntityPersistenceMetaData metaData;
140 
141         protected PutWithoutEntityExistsCheck(final T entity, final EntityPersistenceMetaData existingEntityMetaData) {
142             this.entity = entity;
143             this.metaData = existingEntityMetaData;
144         }
145 
146         @Override
147         public void run() {
148             final PersistenceStrategy persistenceStrategy = new EntityId_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory());
149             final boolean updatePersistMetaDataIfAny = false;
150             final Set<PersistentEntity<T>> persistentEntitySet = new EntityIdNamedXStreamXmlSet(persistenceStrategy, updatePersistMetaDataIfAny);
151             persistentEntitySet.add(new PersistentEntity<T>(this.entity, this.metaData));
152         }
153     }
154 
155 
156     protected class Put<T extends Entity> implements Callable<Void> {
157         private T entity;
158         private EntityPersistenceMetaData existingEntityMetaData;
159 
160         protected Put(final T entity) {
161             this.entity = entity;
162         }
163 
164         @Override
165         public Void call() {
166             if (this.entity != null) {
167                 /* Individual file per entity/aggregate... */
168 
169                 /* Read file
170                  * If entity already exists, reuse/update its EntityPersistenceMetaData object */
171                 // TODO: unable to generify this Specification... need help with this one
172                 final Specification entitySpecification = SpecificationUtils.createUniqueSpecificationFor(this.entity);
173                 final boolean entityExists;
174                 if (this instanceof PartitionRepository) {
175                     entityExists = ((PartitionRepository<T>) this).getRootRepository().countAllEntitiesSpecifiedBy(entitySpecification) > 0;
176                 } else {
177                     entityExists = countAllEntitiesSpecifiedBy(entitySpecification) > 0;
178                 }
179                 if (entityExists) {
180                     final PersistenceStrategy persistenceStrategy = new EntityId_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory());
181                     final Set<PersistentEntity<T>> persistentEntitySet = new EntityIdNamedXStreamXmlSet(persistenceStrategy);
182                     for (final PersistentEntity<T> persistentEntity : persistentEntitySet) {
183                         final T entity = persistentEntity.getEntity();
184                         if (entitySpecification.isSatisfiedBy(entity)) {
185                             this.existingEntityMetaData = persistentEntity.getMetaData();
186                             break;
187                         }
188                     }
189                 }
190                 /* Write file */
191                 final PersistenceStrategy persistenceStrategy = new EntityId_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory());
192                 final Set<PersistentEntity<T>> persistentEntitySet = new EntityIdNamedXStreamXmlSet(persistenceStrategy);
193                 if (this.existingEntityMetaData == null) {
194                     persistentEntitySet.add(new PersistentEntity<T>(this.entity, new EntityPersistenceMetaData(new Date())));
195                 } else {
196                     persistentEntitySet.add(new PersistentEntity<T>(this.entity, this.existingEntityMetaData));
197                 }
198             }
199             return null;
200         }
201     }
202 
203 
204     protected class RemoveAllEntitiesSpecifiedBy<T extends Entity> implements Callable<Long> {
205         private Specification<T> specification;
206 
207         protected RemoveAllEntitiesSpecifiedBy(final Specification<T> specification) {
208             this.specification = specification;
209         }
210 
211         @Override
212         public Long call() {
213             long numberOfRemovedEntities = 0;
214             final EntityId_NamedXStreamFilePersistenceStrategy persistenceStrategy = new EntityId_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory());
215             final Set<PersistentEntity<T>> persistentEntitySet = new EntityIdNamedXStreamXmlSet(persistenceStrategy);
216             for (final PersistentEntity persistentEntity : persistentEntitySet) {
217                 final Entity entity = persistentEntity.getEntity();
218                 if (typeSafeIsSatisfiedBy(this.specification, entity)) {
219                     try {
220                         forceDelete(new File(getRepositoryRootPath() + FILE_SEPARATOR + getRepositoryId() + FILE_SEPARATOR + getEntityIdBasedFilenameFrom(entity)));
221                     } catch (IOException e) {
222                         log.error(buildMessageWithStackTrace(e, "Unable to remove entity=" + entity, 1, 0));
223                     }
224                     ++numberOfRemovedEntities;
225                 }
226             }
227             return numberOfRemovedEntities;
228         }
229     }
230 
231 
232     protected class Remove<T extends Entity> implements Callable<Boolean> {
233         private T entity;
234 
235         protected Remove(final T entity) {
236             this.entity = entity;
237         }
238 
239         @Override
240         public Boolean call() {
241             boolean deletionSucceeded;
242             try {
243                 forceDelete(new File(getRepositoryRootPath() + FILE_SEPARATOR + getRepositoryId() + FILE_SEPARATOR + getEntityIdBasedFilenameFrom(entity)));
244                 deletionSucceeded = true;
245 
246             } catch (IOException e) {
247                 deletionSucceeded = false;
248                 log.error(buildMessageWithStackTrace(e, "Unable to remove entity=" + this.entity, 1, 0));
249             }
250             return deletionSucceeded;
251         }
252     }
253 
254 
255     /** Custom {@link com.thoughtworks.xstream.persistence.FilePersistenceStrategy} class. */
256     protected static class EntityId_NamedXStreamFilePersistenceStrategy extends FilePersistenceStrategy {
257 
258         protected EntityId_NamedXStreamFilePersistenceStrategy(final File baseDirectory) {
259             super(baseDirectory);
260         }
261 
262         @Override
263         protected boolean isValid(final File dir, final String name) {
264             return true; // Accept every kind of file, as long as it is XStream XML format
265         }
266 
267         @Override
268         protected Object extractKey(final String name) {
269             return name;
270         }
271 
272         @Override
273         protected String unescape(final String name) {
274             return name;
275         }
276 
277         @Override
278         protected String escape(final String key) {
279             return key;
280         }
281 
282         @Override
283         protected String getName(final Object key) {
284             return String.valueOf(key);
285         }
286     }
287 
288 
289     /** Custom {@link com.thoughtworks.xstream.persistence.XmlSet} class. */
290     protected class EntityIdNamedXStreamXmlSet<T extends Entity> extends XmlSet {
291 
292         private final Map<String, PersistentEntity<T>> map;
293         private final boolean touchWriteMetaData;
294 
295         EntityIdNamedXStreamXmlSet(final PersistenceStrategy persistenceStrategy) {
296             this(persistenceStrategy, true);
297         }
298 
299         EntityIdNamedXStreamXmlSet(final PersistenceStrategy streamStrategy, final Boolean touchWriteMetaData) {
300             super(streamStrategy);
301             this.map = new XmlMap(streamStrategy);
302             this.touchWriteMetaData = touchWriteMetaData;
303         }
304 
305         @Override
306         public boolean add(final Object persistentEntityObject) {
307             final PersistentEntity persistentEntity = (PersistentEntity) persistentEntityObject;
308             if (this.touchWriteMetaData) {
309                 persistentEntity.touchWriteMetaData();
310             }
311             return persistentEntity.equals(this.map.put(getEntityIdBasedFilenameFrom(persistentEntity.getEntity()), persistentEntity));
312         }
313     }
314 
315 
316     /** Custom iterator class for {@link AbstractXStreamXmlFilePerEntityRepository}. */
317     protected class XStreamXmlFileEntityIterator<T> implements Iterator<T> {
318 
319         private Specification<T> iteratorSpecification;
320         private T currentEntity;
321         private T nextEntity;
322         private Iterator<PersistentEntity> persistedEntityIterator;
323         private boolean nextIsInvoked = false;
324         private boolean removeIsInvoked = false;
325 
326         protected XStreamXmlFileEntityIterator(final Specification<T> specification) {
327             this.iteratorSpecification = specification;
328             final PersistenceStrategy strategy = new EntityId_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory());
329             this.persistedEntityIterator = new EntityIdNamedXStreamXmlSet(strategy).iterator();
330             findNextEntity();
331         }
332 
333         protected void findNextEntity() {
334             try {
335                 synchronizer.callExclusively((Callable) new Callable<Void>() {
336                     @Override
337                     public Void call() {
338                         nextEntity = null;
339                         while (persistedEntityIterator.hasNext()) {
340                             final PersistentEntity persistentEntity = persistedEntityIterator.next();
341                             if (persistentEntity != null) {
342                                 final Entity entity = persistentEntity.getEntity();
343                                 if (typeSafeIsSatisfiedBy(iteratorSpecification, entity)) {
344                                     nextEntity = iteratorSpecification.getType().cast(entity);
345                                     return null;
346                                 }
347                             }
348                         }
349                         return null;
350                     }
351                 });
352             } catch (Exception e) {
353                 throw new RepositoryException(this.getClass().getName() + ".findNextEntity() failed", e);
354             }
355         }
356 
357         @Override
358         public boolean hasNext() {
359             return this.nextEntity != null;
360         }
361 
362         @Override
363         public T next() {
364             if (!hasNext()) {
365                 throw new NoSuchElementException();
366             }
367             this.currentEntity = this.nextEntity;
368             findNextEntity();
369             this.nextIsInvoked = true;
370             this.removeIsInvoked = false;
371             return this.currentEntity;
372         }
373 
374         @Override
375         public void remove() {
376             if (!this.nextIsInvoked || this.removeIsInvoked) {
377                 throw new IllegalStateException();
378             }
379             try {
380                 synchronizer.callExclusively((Callable) new Callable<Void>() {
381                     @Override
382                     public Void call() {
383                         deleteQuietly(new File(getRepositoryPathString() +
384                                                FILE_SEPARATOR +
385                                                getEntityIdBasedFilenameFrom((Entity) currentEntity)
386                         ));
387                         removeIsInvoked = true;
388                         nextIsInvoked = false;
389                         return null;
390                     }
391                 });
392             } catch (Exception e) {
393                 throw new RepositoryException(this.getClass().getName() + ".remove() failed", e);
394             }
395         }
396     }
397 }