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 static java.lang.Boolean.FALSE;
22  import static java.lang.Boolean.TRUE;
23  import java.util.Collection;
24  import java.util.Date;
25  import java.util.Set;
26  import java.util.concurrent.Callable;
27  
28  import static org.apache.commons.io.FileUtils.forceDelete;
29  import static org.apache.commons.io.FileUtils.forceMkdir;
30  
31  import net.sourceforge.domian.entity.Entity;
32  import net.sourceforge.domian.repository.EntityPersistenceMetaData;
33  import net.sourceforge.domian.repository.Repository;
34  import static net.sourceforge.domian.specification.SpecificationFactory.allEntities;
35  import net.sourceforge.domian.util.StopWatch;
36  
37  import com.thoughtworks.xstream.persistence.FilePersistenceStrategy;
38  import com.thoughtworks.xstream.persistence.PersistenceStrategy;
39  import com.thoughtworks.xstream.persistence.XmlSet;
40  
41  
42  /**
43   * Common functionality for all Domian XStream-based repositories,
44   * where <i>all</i> entities are gathered in one single XStream XML file.
45   *
46   * @author Eirik Torske
47   * @since 0.4
48   */
49  abstract class AbstractXStreamSingleXmlFileRepository<T extends Entity> extends AbstractXStreamXmlFileRepository<T> {
50  
51      /** @return the repository type name; the name of the repository implementation (e.g. Unix-name-variant of the class simple name) */
52      protected abstract String getRepositoryTypeName();
53  
54      ///////////////////////////////////////////////
55      // Public interface methods                  //
56      ///////////////////////////////////////////////
57  
58      @Override
59      public EntityPersistenceMetaData getMetaDataFor(final T notApplicableEntity) {
60          final PersistenceStrategy metadataPersistenceStrategy =
61                  new Type_Id_Date_CustomDenomination_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory(),
62                                                                                          AbstractXStreamSingleXmlFileRepository.this,
63                                                                                          "metadata");
64          final Set<Entity[]> metadataXmlSet = new XmlSet(metadataPersistenceStrategy);
65          if (!metadataXmlSet.isEmpty()) {
66              return (EntityPersistenceMetaData) metadataXmlSet.iterator().next()[0];
67          }
68          return null;
69      }
70  
71      ///////////////////////////////////////////////
72      // Other methods                             //
73      ///////////////////////////////////////////////
74  
75      protected void createRepositoryRootPathIfNotExist() {
76          try {
77              forceMkdir(new File(getRepositoryRootPath()));
78              log.info("Repository root '" + getRepositoryRootPath() + "' created (if not already in place...)");
79  
80          } catch (IOException e) {
81              log.error("Error when creating repository directory " + getRepositoryRootPath(), e);
82          }
83  
84          try {
85              forceMkdir(getRepositoryDirectory());
86              log.info("Repository '" + this.repositoryId + "' created (if not already in place...)");
87  
88          } catch (IOException e) {
89              log.error("Error when creating repository directory " + this.repositoryId, e);
90          }
91      }
92  
93  
94      protected void purgeRepositoryFilesIfExist() {
95          purgeRepositoryFilesIfExist(2, TRUE, TRUE);
96      }
97  
98  
99      protected void purgeRepositoryEntitiesFileOnly_IfExist() {
100         purgeRepositoryFilesIfExist(1, FALSE, FALSE);
101     }
102 
103 
104     protected void purgeRepositoryFilesIfExist(final int filesToBePurgedLimit, final Boolean purgeRepositoryFolder, final Boolean purgeMetadata) {
105         int purgedFiles = 0;
106 
107         final File repoDirectory = getRepositoryDirectory();
108         if (repoDirectory.exists() && repoDirectory.isDirectory() && repoDirectory.canWrite()) {
109             final File[] files = repoDirectory.listFiles();
110             for (final File file : files) {
111                 if (purgedFiles > filesToBePurgedLimit) {
112                     throw new IllegalStateException(filesToBePurgedLimit + " files have been deleted in '" + repoDirectory.getAbsolutePath() + "' - revisit test logic and approve behaviour!");
113                 }
114                 if (!purgeMetadata && file.getName().contains("entities")) {
115                     try {
116                         log.info("Deleting " + file.getAbsolutePath() + " ...");
117                         forceDelete(file);
118                         ++purgedFiles;
119 
120                     } catch (IOException e) {
121                         log.error("Error when deleting " + file.getAbsolutePath(), e);
122                     }
123                 }
124             }
125             if (purgeRepositoryFolder) {
126                 try {
127                     log.info("Deleting " + repoDirectory.getAbsolutePath() + " ...");
128                     forceDelete(repoDirectory);
129 
130                 } catch (IOException e) {
131                     log.error("Error when deleting " + repoDirectory.getAbsolutePath(), e);
132                 }
133             }
134         }
135     }
136 
137     ///////////////////////////////////////////////
138     // Repository operations as Callable classes //
139     ///////////////////////////////////////////////
140 
141     protected class Load implements Callable<Void> {
142         private Repository repository;
143 
144         protected Load(final Repository repository) {
145             this.repository = repository;
146         }
147 
148         @Override
149         public Void call() throws Exception {
150             StopWatch stopWatch = null;
151             if (log.isInfoEnabled()) {
152                 stopWatch = new StopWatch().start();
153             }
154 
155             /* Firstly, purge the existing in-memory entities! */
156             final long numberOfEntitiesPurged = this.repository.remove(allEntities());
157             log.info("Repository '" + repositoryId + "': " + numberOfEntitiesPurged + " entities/aggregates purged...");
158 
159             /* Deserialize files */
160             final PersistenceStrategy persistenceStrategy =
161                     new Type_Id_Date_CustomDenomination_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory(),
162                                                                                             AbstractXStreamSingleXmlFileRepository.this,
163                                                                                             "entities");
164             final Set<Entity[]> entityXmlSet = new XmlSet(persistenceStrategy);
165             Entity[] persistedEntities = null;
166             if (entityXmlSet != null && !entityXmlSet.isEmpty()) {
167                 /* Add to repository */
168                 persistedEntities = entityXmlSet.iterator().next();
169                 for (final Entity entity : persistedEntities) {
170                     this.repository.put(entity);
171                 }
172                 /* Update meta data file */
173                 final PersistenceStrategy metadataPersistenceStrategy =
174                         new Type_Id_Date_CustomDenomination_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory(),
175                                                                                                 AbstractXStreamSingleXmlFileRepository.this,
176                                                                                                 "metadata");
177                 final Set<Entity[]> metadataXmlSet = new XmlSet(metadataPersistenceStrategy);
178                 final EntityPersistenceMetaData metadata;
179                 if (!metadataXmlSet.isEmpty()) {
180                     metadata = (EntityPersistenceMetaData) metadataXmlSet.iterator().next()[0];
181                 } else {
182                     metadata = new EntityPersistenceMetaData(new Date());
183                 }
184                 metadataXmlSet.add(new Entity[]{metadata.touchReadMetaData()});
185             }
186             if (log.isInfoEnabled()) {
187                 if (persistedEntities != null && persistedEntities.length > 0) {
188                     log.info("Repository '" + repositoryId + "' loaded in " + stopWatch.elapsedTimeToString() + " [" + persistedEntities.length + " entities/aggregates in total]");
189                 }
190             }
191             return null;
192         }
193     }
194 
195 
196     protected class Persist implements Callable<Void> {
197         private Repository repository;
198 
199         protected Persist(final Repository repository) {
200             this.repository = repository;
201         }
202 
203         @Override
204         public Void call() throws Exception {
205             StopWatch stopWatch = null;
206             if (log.isInfoEnabled()) {
207                 stopWatch = new StopWatch().start();
208             }
209             /* Firstly, purge the existing persisted entities! file (not the repository folder, not the meta data) */
210             purgeRepositoryEntitiesFileOnly_IfExist();
211 
212             /* Serialize files
213                One file per repository - <entity-array> as root element */
214             final Collection<Entity> allEntities = this.repository.find(allEntities());
215             if (!allEntities.isEmpty()) {
216 
217                 /* Write entity file */
218                 final PersistenceStrategy entitiesPersistenceStrategy =
219                         new Type_Id_Date_CustomDenomination_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory(),
220                                                                                                 AbstractXStreamSingleXmlFileRepository.this,
221                                                                                                 "entities");
222                 final Set<Entity[]> entityXmlSet = new XmlSet(entitiesPersistenceStrategy);
223                 entityXmlSet.add(allEntities.toArray(new Entity[allEntities.size()]));
224 
225                 /* Update meta data file */
226                 final PersistenceStrategy metadataPersistenceStrategy =
227                         new Type_Id_Date_CustomDenomination_NamedXStreamFilePersistenceStrategy(getRepositoryDirectory(),
228                                                                                                 AbstractXStreamSingleXmlFileRepository.this,
229                                                                                                 "metadata");
230                 final Set<Entity[]> metadataXmlSet = new XmlSet(metadataPersistenceStrategy);
231                 final EntityPersistenceMetaData metadata;
232                 if (!metadataXmlSet.isEmpty()) {
233                     metadata = (EntityPersistenceMetaData) metadataXmlSet.iterator().next()[0];
234                 } else {
235                     metadata = new EntityPersistenceMetaData(new Date());
236                 }
237                 metadataXmlSet.add(new Entity[]{metadata.touchWriteMetaData()});
238             }
239             if (log.isInfoEnabled()) {
240                 log.info("Repository '" + repositoryId + "'  persisted in " + stopWatch.elapsedTimeToString() + " [" + allEntities.size() + " entities/aggregates in total]");
241             }
242             return null;
243         }
244     }
245 
246 
247     /** Custom {@link com.thoughtworks.xstream.persistence.FilePersistenceStrategy} class. */
248     protected class Type_Id_Date_CustomDenomination_NamedXStreamFilePersistenceStrategy extends FilePersistenceStrategy {
249 
250         private final AbstractXStreamSingleXmlFileRepository persistentRepository;
251         private final String denomination; // 'entities' or 'metadata'
252 
253         protected Type_Id_Date_CustomDenomination_NamedXStreamFilePersistenceStrategy(final File repositoryDir,
254                                                                                       final AbstractXStreamSingleXmlFileRepository persistentRepository,
255                                                                                       final String denomination) {
256             super(repositoryDir);
257             this.persistentRepository = persistentRepository;
258             this.denomination = denomination;
259         }
260 
261         @Override
262         protected boolean isValid(final File dir, final String name) {
263             return name.contains(this.denomination);
264         }
265 
266         @Override
267         protected Object extractKey(final String name) {
268             return name;
269         }
270 
271         @Override
272         protected String getName(final Object key) {
273             return getRepositoryTypeName() +
274                    "_" + this.persistentRepository.getRepositoryId() +
275                    "_" + this.denomination +
276                    XSTREAM_XML_FILE_SUFFIX;
277         }
278     }
279 }