1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
53
54
55
56
57
58 abstract class AbstractXStreamXmlFilePerEntityRepository<T extends Entity> extends AbstractXStreamXmlFileRepository<T> {
59
60
61
62
63
64
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
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()) {
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
168
169
170
171
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
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
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;
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
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
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 }