View Javadoc

1   /**
2    * Copyright (c) 2011, University of Konstanz, Distributed Systems Group
3    * All rights reserved.
4    * 
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions are met:
7    * * Redistributions of source code must retain the above copyright
8    * notice, this list of conditions and the following disclaimer.
9    * * Redistributions in binary form must reproduce the above copyright
10   * notice, this list of conditions and the following disclaimer in the
11   * documentation and/or other materials provided with the distribution.
12   * * Neither the name of the University of Konstanz nor the
13   * names of its contributors may be used to endorse or promote products
14   * derived from this software without specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  
28  package org.treetank.access;
29  
30  import static com.google.common.base.Objects.toStringHelper;
31  import static com.google.common.base.Preconditions.checkState;
32  
33  import java.io.File;
34  import java.util.Map;
35  import java.util.concurrent.ConcurrentHashMap;
36  import java.util.concurrent.ConcurrentMap;
37  
38  import org.treetank.access.conf.ConstructorProps;
39  import org.treetank.access.conf.ResourceConfiguration;
40  import org.treetank.access.conf.SessionConfiguration;
41  import org.treetank.access.conf.StorageConfiguration;
42  import org.treetank.api.ISession;
43  import org.treetank.api.IStorage;
44  import org.treetank.bucket.IConstants;
45  import org.treetank.bucket.IndirectBucket;
46  import org.treetank.bucket.MetaBucket;
47  import org.treetank.bucket.DataBucket;
48  import org.treetank.bucket.RevisionRootBucket;
49  import org.treetank.bucket.UberBucket;
50  import org.treetank.bucket.interfaces.IReferenceBucket;
51  import org.treetank.exception.TTException;
52  import org.treetank.exception.TTIOException;
53  import org.treetank.io.IBackend;
54  import org.treetank.io.IBackendReader;
55  import org.treetank.io.IBackendWriter;
56  import org.treetank.io.IOUtils;
57  
58  /**
59   * This class represents one concrete database for enabling several {@link ISession} objects.
60   * 
61   * @see IStorage
62   * @author Sebastian Graf, University of Konstanz
63   */
64  public final class Storage implements IStorage {
65  
66      /** Central repository of all running databases. */
67      private static final ConcurrentMap<File, Storage> STORAGEMAP = new ConcurrentHashMap<File, Storage>();
68  
69      /** Central repository of all running sessions. */
70      protected final Map<String, Session> mSessions;
71  
72      /** StorageConfiguration with fixed settings. */
73      private final StorageConfiguration mStorageConfig;
74  
75      /**
76       * Private constructor.
77       * 
78       * @param pStorageConf
79       *            {@link StorageConfiguration} reference to configure the {@link IStorage}
80       * @throws TTException
81       *             Exception if something weird happens
82       */
83      private Storage(final StorageConfiguration pStorageConf) throws TTException {
84          mStorageConfig = pStorageConf;
85          mSessions = new ConcurrentHashMap<String, Session>();
86  
87      }
88  
89      // //////////////////////////////////////////////////////////
90      // START Creation/Deletion of Storages /////////////////////
91      // //////////////////////////////////////////////////////////
92      /**
93       * Creating a storage. This includes loading the storageconfiguration,
94       * building up the structure and preparing everything for login.
95       * 
96       * 
97       * @param pStorageConfig
98       *            which are used for the storage, including storage location
99       * @return true if creation is valid, false otherwise
100      * @throws TTIOException
101      *             if something odd happens within the creation process.
102      */
103     public static synchronized boolean createStorage(final StorageConfiguration pStorageConfig)
104         throws TTIOException {
105         boolean returnVal = true;
106         // if file is existing, skipping
107         if (!pStorageConfig.mFile.exists() && pStorageConfig.mFile.mkdirs()) {
108             returnVal =
109                 IOUtils.createFolderStructure(pStorageConfig.mFile, StorageConfiguration.Paths.values());
110             // serialization of the config
111             StorageConfiguration.serialize(pStorageConfig);
112             // if something was not correct, delete the partly created
113             // substructure
114             if (!returnVal) {
115                 pStorageConfig.mFile.delete();
116             }
117             return returnVal;
118         } else {
119             return false;
120         }
121     }
122 
123     /**
124      * Truncate a storage. This deletes all relevant data. All running sessions
125      * must be closed beforehand.
126      * 
127      * @param pConf
128      *            the storage at this path should be deleted.
129      * @throws TTException
130      *             any kind of false Treetank behaviour
131      */
132     public static synchronized void truncateStorage(final StorageConfiguration pConf) throws TTException {
133         // check that database must be closed beforehand
134         if (!STORAGEMAP.containsKey(pConf.mFile)) {
135             if (existsStorage(pConf.mFile)) {
136                 final IStorage storage = new Storage(pConf);
137                 final File[] resources =
138                     new File(pConf.mFile, StorageConfiguration.Paths.Data.getFile().getName()).listFiles();
139                 for (final File resource : resources) {
140                     storage.truncateResource(new SessionConfiguration(resource.getName(), null));
141                 }
142                 storage.close();
143                 // instantiate the database for deletion
144                 IOUtils.recursiveDelete(pConf.mFile);
145             }
146         }
147     }
148 
149     /**
150      * Check if Storage exists or not at a given path.
151      * 
152      * @param pStoragePath
153      *            to be checked.
154      * @return true if existing, false otherwise.
155      */
156     public static synchronized boolean existsStorage(final File pStoragePath) {
157         // if file is existing and folder is a tt-dataplace, delete it
158         if (pStoragePath.exists()
159             && IOUtils.compareStructure(pStoragePath, StorageConfiguration.Paths.values()) == 0) {
160             return true;
161         } else {
162             return false;
163         }
164 
165     }
166 
167     // //////////////////////////////////////////////////////////
168     // END Creation/Deletion of Databases ///////////////////////
169     // //////////////////////////////////////////////////////////
170 
171     // //////////////////////////////////////////////////////////
172     // START Creation/Deletion of Resources /////////////////////
173     // //////////////////////////////////////////////////////////
174 
175     /**
176      * {@inheritDoc}
177      */
178     @Override
179     public synchronized boolean createResource(final ResourceConfiguration pResConf) throws TTException {
180         boolean returnVal = true;
181         // Setting the missing params in the settings, this overrides already
182         // set data.
183         final File path = new File(pResConf.mProperties.getProperty(ConstructorProps.RESOURCEPATH));
184         if (!path.exists() && path.mkdir()) {
185             returnVal = IOUtils.createFolderStructure(path, ResourceConfiguration.Paths.values());
186 
187             // serialization of the config
188             ResourceConfiguration.serialize(pResConf);
189 
190             // Boostrapping the Storage, this is quite dirty because of the initialization of the key, i
191             // guess..however...
192             bootstrap(this, pResConf);
193             return returnVal;
194         } else {
195             return false;
196         }
197     }
198 
199     /**
200      * {@inheritDoc}
201      * 
202      */
203     @Override
204     public synchronized boolean truncateResource(final SessionConfiguration pSesConf) throws TTException {
205         // check that database must be closed beforehand
206         checkState(!mSessions.containsKey(pSesConf.getResource()),
207             "Please close all session before truncating!");
208         if (existsResource(pSesConf.getResource())) {
209             ISession session = getSession(pSesConf);
210             if (session.truncate()) {
211                 return true;
212             } else {
213                 return false;
214             }
215         } else {
216             return false;
217         }
218 
219     }
220 
221     // //////////////////////////////////////////////////////////
222     // END Creation/Deletion of Resources ///////////////////////
223     // //////////////////////////////////////////////////////////
224 
225     // //////////////////////////////////////////////////////////
226     // START Opening of Databases ///////////////////////
227     // //////////////////////////////////////////////////////////
228     /**
229      * Open database. A database can be opened only once. Afterwards the
230      * singleton instance bound to the File is given back.
231      * 
232      * @param pFile
233      *            where the database is located sessionConf a {@link SessionConfiguration} object to set up
234      *            the session
235      * @return {@link IStorage} instance.
236      * @throws TTException
237      *             if something odd happens
238      */
239     public static synchronized IStorage openStorage(final File pFile) throws TTException {
240         checkState(existsStorage(pFile), "DB could not be opened (since it was not created?) at location %s",
241             pFile);
242         StorageConfiguration config = StorageConfiguration.deserialize(pFile);
243         final Storage storage = new Storage(config);
244         final IStorage returnVal = STORAGEMAP.putIfAbsent(pFile, storage);
245         if (returnVal == null) {
246             return storage;
247         } else {
248             return returnVal;
249         }
250     }
251 
252     // //////////////////////////////////////////////////////////
253     // END Opening of Databases ///////////////////////
254     // //////////////////////////////////////////////////////////
255 
256     // //////////////////////////////////////////////////////////
257     // START DB-Operations//////////////////////////////////
258     // /////////////////////////////////////////////////////////
259 
260     /**
261      * {@inheritDoc}
262      */
263     @Override
264     public synchronized ISession getSession(final SessionConfiguration pSessionConf) throws TTException {
265 
266         final File resourceFile =
267             new File(new File(mStorageConfig.mFile, StorageConfiguration.Paths.Data.getFile().getName()),
268                 pSessionConf.getResource());
269         Session returnVal = mSessions.get(pSessionConf.getResource());
270         if (returnVal == null) {
271             checkState(resourceFile.exists(),
272                 "Resource could not be opened (since it was not created?) at location %s", resourceFile);
273             ResourceConfiguration config =
274                 ResourceConfiguration.deserialize(mStorageConfig.mFile, pSessionConf.getResource());
275             config.mBackend.initialize();
276             // reading first reference and instantiate this.
277             final IBackendReader backendReader = config.mBackend.getReader();
278             UberBucket bucket = backendReader.readUber();
279             backendReader.close();
280 
281             returnVal = new Session(this, config, pSessionConf, bucket);
282             mSessions.put(pSessionConf.getResource(), returnVal);
283         }
284         return returnVal;
285     }
286 
287     /**
288      * {@inheritDoc}
289      */
290     @Override
291     public synchronized boolean close() throws TTException {
292         for (final ISession session : mSessions.values()) {
293             session.close();
294         }
295         return STORAGEMAP.remove(mStorageConfig.mFile) != null;
296     }
297 
298     // //////////////////////////////////////////////////////////
299     // End DB-Operations//////////////////////////////////
300     // /////////////////////////////////////////////////////////
301 
302     /**
303      * {@inheritDoc}
304      */
305     @Override
306     public boolean existsResource(String pResourceName) {
307         final File resourceFile =
308             new File(new File(mStorageConfig.mFile, StorageConfiguration.Paths.Data.getFile().getName()),
309                 pResourceName);
310         return resourceFile.exists();
311     }
312 
313     /**
314      * {@inheritDoc}
315      */
316     @Override
317     public String[] listResources() {
318         return new File(mStorageConfig.mFile, StorageConfiguration.Paths.Data.getFile().getName()).list();
319     }
320 
321     /**
322      * {@inheritDoc}
323      */
324     @Override
325     public File getLocation() {
326         return mStorageConfig.mFile;
327     }
328 
329     /**
330      * Boostraping a resource within this storage.
331      * 
332      * @param pStorage
333      *            storage where the new resource should be created in.
334      * @param pResourceConf
335      *            related {@link ResourceConfiguration} for the new resource
336      * @throws TTException
337      */
338     private static void bootstrap(final Storage pStorage, final ResourceConfiguration pResourceConf)
339         throws TTException {
340 
341         final IBackend storage = pResourceConf.mBackend;
342         storage.initialize();
343         final IBackendWriter writer = storage.getWriter();
344 
345         final UberBucket uberBucket = new UberBucket(1, 0, 1);
346         long newBucketKey = uberBucket.incrementBucketCounter();
347         uberBucket.setReferenceKey(IReferenceBucket.GUARANTEED_INDIRECT_OFFSET, newBucketKey);
348         uberBucket.setReferenceHash(IReferenceBucket.GUARANTEED_INDIRECT_OFFSET, IConstants.BOOTSTRAP_HASHED);
349         writer.write(uberBucket);
350 
351         // --- Create revision tree
352         // ------------------------------------------------
353         // Initialize revision tree to guarantee that there is a revision root
354         // bucket.
355 
356         IReferenceBucket bucket;
357         for (int i = 0; i < IConstants.INDIRECT_BUCKET_COUNT.length; i++) {
358             bucket = new IndirectBucket(newBucketKey);
359             newBucketKey = uberBucket.incrementBucketCounter();
360             bucket.setReferenceKey(0, newBucketKey);
361             bucket.setReferenceHash(0, IConstants.BOOTSTRAP_HASHED);
362             writer.write(bucket);
363         }
364 
365         RevisionRootBucket revBucket = new RevisionRootBucket(newBucketKey, 0, 0);
366 
367         newBucketKey = uberBucket.incrementBucketCounter();
368         // establishing fresh MetaBucket
369         MetaBucket metaBucker = new MetaBucket(newBucketKey);
370         revBucket.setReferenceKey(RevisionRootBucket.META_REFERENCE_OFFSET, newBucketKey);
371         revBucket.setReferenceHash(RevisionRootBucket.META_REFERENCE_OFFSET, IConstants.BOOTSTRAP_HASHED);
372 
373         newBucketKey = uberBucket.incrementBucketCounter();
374         bucket = new IndirectBucket(newBucketKey);
375         revBucket.setReferenceKey(IReferenceBucket.GUARANTEED_INDIRECT_OFFSET, newBucketKey);
376         revBucket.setReferenceHash(IReferenceBucket.GUARANTEED_INDIRECT_OFFSET, IConstants.BOOTSTRAP_HASHED);
377         writer.write(revBucket);
378         writer.write(metaBucker);
379 
380         // --- Create data tree
381         // ----------------------------------------------------
382 
383         // Initialize revision tree to guarantee that there is a revision root
384         // bucket.
385 
386         for (int i = 0; i < IConstants.INDIRECT_BUCKET_COUNT.length; i++) {
387             newBucketKey = uberBucket.incrementBucketCounter();
388             bucket.setReferenceKey(0, newBucketKey);
389             bucket.setReferenceHash(0, IConstants.BOOTSTRAP_HASHED);
390             writer.write(bucket);
391             bucket = new IndirectBucket(newBucketKey);
392         }
393 
394         final DataBucket ndp = new DataBucket(newBucketKey, IConstants.NULLDATA);
395         writer.write(ndp);
396         writer.writeUberBucket(uberBucket);
397         writer.close();
398         storage.close();
399 
400     }
401 
402     /**
403      * {@inheritDoc}
404      */
405     @Override
406     public String toString() {
407         return toStringHelper(this).add("mSessions", mSessions).add("mStorageConfig", mStorageConfig)
408             .toString();
409     }
410 
411 }