View Javadoc

1   /*
2    * $Id: Library.java,v 1.13 2005/06/01 19:55:51 jlerner Exp $
3    *
4    * Copyright (c) 1999-2004, BBN Technologies, LLC.
5    * All rights reserved.
6    * http://www.daml.org/legal/opensource/bbn_license.html
7    */
8    
9   package com.bbn.swede.core.libraries;
10  
11  import java.io.BufferedInputStream;
12  import java.io.BufferedOutputStream;
13  import java.io.File;
14  import java.io.FileNotFoundException;
15  import java.io.FileOutputStream;
16  import java.io.FilenameFilter;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.lang.reflect.InvocationTargetException;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Properties;
27  import java.util.regex.Matcher;
28  import java.util.regex.Pattern;
29  import java.util.zip.ZipEntry;
30  import java.util.zip.ZipFile;
31  import java.util.zip.ZipOutputStream;
32  
33  import org.eclipse.core.runtime.IProgressMonitor;
34  import org.eclipse.core.runtime.IStatus;
35  import org.eclipse.core.runtime.MultiStatus;
36  import org.eclipse.core.runtime.NullProgressMonitor;
37  import org.eclipse.core.runtime.Status;
38  
39  import com.bbn.swede.core.OWLCore;
40  
41  /***
42   * This is a concrete implementation of the ILibrary interface, with methods
43   * for implementing most of the functionality required for library support in
44   * SWeDE. 
45   * <br /> <br />
46   * Libraries are basically magic ZIP files containing OWL files and metadata.  
47   * They are arranged according to a strict structure.  Each library file has an
48   * extension as defined in {@link Libraries#LIBRARY_EXTENSION} and they are stored 
49   * in a special directory called {@link Libraries#LIBRARY_DIRECTORY} found in the 
50   * workspace root (e.g. eclipse/workspace/.swlib/ontology-library.swlib where 
51   * both the LIBRARY_EXTENSION and the LIBRARY_DIRECTORY are ".swlib").  Keeping 
52   * libraries in that central location allows each users to create and manipulate 
53   * projects as they see fit without concerning themselves with how libraries are managed.  
54   * Another option would have been to store the libraries in the SWeDE plugin directory, 
55   * but then difficulties would arise when upgrading to new versions of SWeDE.  
56   * <br /> <br />
57   * Within a single library file, there is a tripartite structure.  Each of the files in 
58   * the library, referred to as <code>Entry</code>s exist within a subdirectory of the ZIP 
59   * called '/entry', and each file is automatically appended the extension '.entry'.  So 
60   * for instance, a file called 'vehicle-ont.owl' would, in the ZIP, have an file called 
61   * '/entry/vehicle-ont.owl.entry'.  Associated meta-data is stored in a separate directory,
62   * this one called '/metadata', and each file has a file there as well, similarly appended 
63   * with an extension, in this case, '.metadata'.  So 'vehicle-ont.owl' would also have an 
64   * file in the '/entry' directory as mentioned above, and also a file in the 'metadata' 
65   * directory (i.e. '/metadata/vehicle-ont.owl.metadata').
66   * <br /> <br />
67   * @author aperezlo
68   */
69  class Library implements ILibrary
70  {
71     private ZipFile _zipFile;
72     private String _filename;
73     private LibraryDescriptor _descriptor;
74     private List _entries;
75     private LibraryConfiguration _configuration;
76     private LibraryEntryDescriptor[] _refreshBuffer;
77     private boolean _isOpen;
78     
79     static final int NO_CHANGE = 0;
80     static final int ENTRY_REFRESHED = 1;
81     
82     /***
83      * This constructor is used to create a new library on
84      * the disk.  Given a name, this constructor creates a 
85      * new file for the library in the standard library location 
86      * as defined by {@link Libraries#getLibraryLocationPath()} with an 
87      * extension as defined by {@link Libraries#LIBRARY_EXTENSION}.
88      * 
89      * @param filename the name of the new library; it is incorporated into the name of the file
90      */
91     Library(String filename)
92     {
93        File f = null; 
94        _filename = filename + Libraries.LIBRARY_EXTENSION;
95        
96        f = new File(Libraries.getLibraryLocationPath() + _filename);
97        try
98        {
99           f.createNewFile();
100       }
101       catch(Exception e)
102       {
103          OWLCore.logWarning(OWLCore.getID(), "Unable to create library " + filename, e);
104       }
105       _entries = new ArrayList();
106       _configuration = new LibraryConfiguration();
107       _configuration.setName(_filename.substring(0, _filename.lastIndexOf(".")));
108    }
109    
110    /***
111     * Creates a library based on a zip file that already exists on disk, owned
112     * by a specific descriptor.
113     * @param zipFile The zip file containing the existing library
114     * @param owner The descriptor for the library
115     */
116    Library(File zipFile, LibraryDescriptor owner)
117    {
118       this(zipFile);
119       _descriptor = owner;
120    }
121    
122    
123    /***
124     * Creates a library based on a zip file that already exists on disk.
125     * 
126     * @param zipFile The zip file containing the existing library
127     */
128    Library(File zipFile)
129    {
130       _descriptor = null;
131       _entries = new ArrayList();
132       _configuration = new LibraryConfiguration();
133       try
134       {
135          _zipFile = new ZipFile(zipFile);
136       }
137       catch(Exception ze)
138       {
139          OWLCore.logWarning(OWLCore.getID(), "Unable to instantiate existing library " + zipFile.getName(), ze);
140       }
141       if(_zipFile != null)
142       {
143          init();
144       }
145    }
146    
147    /***
148     * Retrieves the ZIP file that holds this library.
149     * @return the zip file
150     */
151    ZipFile getZipFile()
152    {
153       return _zipFile;
154    }
155    
156    /***
157     * Removes an ILibraryEntryDescriptor from the library.  This change is not 
158     * permanent until the library is re-written to disk.
159     * 
160     * @param toRemove a ILibraryEntryDescriptor which should be removed from
161     * the library.   
162     */
163    synchronized void removeEntry(ILibraryEntryDescriptor toRemove)
164    {
165       toRemove.close();
166       _entries.remove(toRemove);
167    }
168 
169    /***
170     * Extracts an entry from the library.
171     * @param outDirectory The destination directory for the unzipped file
172     * @param le The entry to extract
173     * @param progress A progress monitor for the unzip operation
174     * @return <code>IStatus.OK</code> if the entry is extracted successfully,
175     *         <code>IStatus.CANCEL</code> if the operation is cancelled, or
176     *         <code>IStatus.ERROR</code> if something goes wrong.
177     */
178    synchronized IStatus unzipOne(File outDirectory, LibraryEntryDescriptor le, IProgressMonitor progress)
179    {
180       MultiStatus toReturn = new MultiStatus(OWLCore.getID(), IStatus.OK, "Unzip succeeded.", null);
181       final int iBufferSize = 2024;
182       byte[] buffer = new byte[iBufferSize];
183       int length = iBufferSize;
184       File output = new File(outDirectory.toString() + File.separator + le.getName());
185       BufferedInputStream in = null;
186       BufferedOutputStream out = null;
187       progress.beginTask("Unzipping...", IProgressMonitor.UNKNOWN);
188       try
189       {
190          output.createNewFile();
191       }
192       catch (IOException e)
193       {
194          toReturn.add(new Status(IStatus.ERROR, OWLCore.getID(), IStatus.OK, "Could not create file...", e));
195       }
196       if(toReturn.getSeverity() == IStatus.OK)
197       {
198          synchronized (_zipFile)
199          {
200             try
201             {
202                in = new BufferedInputStream(le.getFileInputStream());
203                out = new BufferedOutputStream(new FileOutputStream(output));
204                while(length > -1 && !progress.isCanceled())
205                {
206                   length = in.read(buffer, 0, iBufferSize);
207                   if(length > -1)
208                   {
209                      out.write(buffer, 0, length);
210                   }
211                   progress.worked(iBufferSize);
212                }
213             }
214             catch (FileNotFoundException e1)
215             {
216                toReturn.add(new Status(IStatus.ERROR, OWLCore.getID(), IStatus.OK, "File not found...", e1));
217             }
218             catch (IOException e1)
219             {
220                toReturn.add(new Status(IStatus.ERROR, OWLCore.getID(), IStatus.OK, "Unknown IO Exception...", e1));
221             }
222             finally
223             {
224                try
225                {
226                   out.flush();
227                   out.close();
228                   in.close();
229                }
230                catch (IOException e2)
231                {
232                   toReturn.add(new Status(IStatus.ERROR, OWLCore.getID(), IStatus.OK, "Unknown IO Exception...", e2));
233                }
234                finally
235                {
236                   progress.done();
237                }
238             }
239          }
240       }
241       if(progress.isCanceled())
242       {
243          toReturn.add(Status.CANCEL_STATUS);
244       }
245       return toReturn;
246    }
247    
248    /***
249     * Extracts all entries in the library.
250     * @param outDirectory The destination directory for the extracted files.
251     */
252    synchronized void unzipAll(File outDirectory)
253    {
254       Iterator i = null;
255       synchronized (_entries)
256       {
257          i = _entries.iterator();
258          while(i.hasNext())
259          {
260             LibraryEntryDescriptor led = (LibraryEntryDescriptor) i.next();
261             unzipOne(outDirectory, led, new NullProgressMonitor());
262          }
263       }
264    }
265    
266    /***
267     * This method looks in the particular directory specified by 
268     * <code>infileLocation</code>, and adds each of the .owl files 
269     * in the directory to itself. 
270     * 
271     * @param infileLocation the location where this library should look for its constituent files
272     * @param progress an object to receive updates about the progress of this operation
273     * @return IStatus.OK if a success, IStatus.CANCEL if it was canceled, and IStatus.ERROR otherwise
274     */
275    synchronized IStatus rebuild(File infileLocation, IProgressMonitor progress)
276    {
277       MultiStatus toReturn = new MultiStatus(OWLCore.getID(), IStatus.OK, "Library rebuilt successfully...", null);
278       LibraryEntryDescriptor led = null;
279       LibraryEntryDescriptor newLed = null;
280       File newFile = null;
281       List newEntries = new ArrayList();
282       List rebuiltEntries = new ArrayList();
283       String fileName = null;
284       File[] owlFiles = infileLocation.listFiles(new FilenameFilter()
285       {
286          private Pattern _owlPattern = Pattern.compile("//.owl", Pattern.CASE_INSENSITIVE);
287          public boolean accept(File arg0, String arg1)
288          {
289             boolean toReturn = false;
290             Matcher m = _owlPattern.matcher(arg1.substring(arg1.lastIndexOf(".")));
291             if(m.find())
292             {
293                toReturn = true;
294             }
295             return toReturn;
296          }
297       });
298       progress.beginTask("Rebuilding library...", 10 * owlFiles.length);
299       close();
300       // basically here's what we do:
301       // we go through all the .owl files in the edit directory
302       // if we already have an entry for a particular file, then lets just 
303       // keep that entry, and setFile it.  But if we have a file that doesn't 
304       // have an entry, then we should create an entry and set its file.  We 
305       // don't worry about properties right now, because each file will also 
306       // have its metadata set by its own LibraryStructuralEdit thingy
307       for(int j = 0; (j < owlFiles.length) && !progress.isCanceled(); j++)
308       {
309          fileName = owlFiles[j].getName();
310          led = (LibraryEntryDescriptor) createDescriptor().getDescriptorFor(fileName);
311          if(led != null)
312          {
313             ((LibraryEntry) led.getEntry()).setFile(owlFiles[j]);
314             rebuiltEntries.add(led);
315          }
316          else
317          {
318             newEntries.add(new LibraryEntry(this, owlFiles[j], new Properties(), null).createDescriptor());
319          }
320          progress.worked(5);
321       }
322 
323       // write the library to the refresh directory
324       if(!progress.isCanceled())
325       {
326          _entries.clear();
327          _entries.addAll(newEntries);
328          _entries.addAll(rebuiltEntries);
329          progress.worked(2 * owlFiles.length);
330          writeLibrary(infileLocation.toString());
331          progress.worked(3 * owlFiles.length);
332       }
333       
334       progress.done();
335       if(progress.isCanceled())
336       {
337          toReturn.add(Status.CANCEL_STATUS);
338       }
339       return toReturn;
340    }
341    
342    /***
343     * Indicates whether the library is open.
344     * @return <code>true</code> if the library is open, <code>false</code> if not.
345     */
346    boolean isOpen()
347    {
348       return _isOpen;
349    }
350    
351    /***
352     * Adds an entry to the library.  This change is not permanent until the 
353     * library is written to disk.
354     * 
355     * @param newEntry the new entry to add
356     */
357    synchronized void addEntry(ILibraryEntryDescriptor newEntry)
358    {
359       _entries.add(newEntry);
360    }
361    
362    /***
363     * Attempts to close the zip file and free all other system resources.
364     * 
365     * @return true if the operation was a success, false otherwise
366     */
367    synchronized boolean close()
368    {
369       boolean toReturn = true;
370 
371       try
372       {
373          if(_zipFile != null)
374          {
375             _zipFile.close();
376          }
377       }
378       catch (IOException e)
379       {
380          toReturn = false;
381       }
382 
383       _isOpen = !toReturn;
384       
385       return toReturn;
386    }
387    
388    private synchronized void init()
389    {
390       Iterator zipEntryIterator = Collections.list(_zipFile.entries()).iterator();
391 
392       _filename = _zipFile.getName();
393       _filename = _filename.substring(_filename.lastIndexOf(File.separator) + File.separator.length());
394       _configuration.setName(_filename.substring(0, _filename.lastIndexOf(".")));
395       
396       ZipEntry zip = null;
397       ZipEntry metaEntry = null;
398       ZipEntry oastEntry = null;
399 
400       String entryName = null;
401       String metaName = null;
402       String oastName = null;
403       
404       ILibraryEntry libraryEntry = null;
405       ILibraryEntryDescriptor libraryEntryDescriptor = null;
406 
407       InputStream propertyStream = null;
408       
409       while(zipEntryIterator.hasNext())
410       {
411          zip = (ZipEntry) zipEntryIterator.next();
412          entryName = zip.getName();
413          // Check for library-level metadata...
414          if(entryName.equals(_filename + LibraryDescriptor.METADATA_EXTENSION))
415          {
416             try
417             {
418                propertyStream = _zipFile.getInputStream(zip);
419                _configuration.load(propertyStream);
420             }
421             catch(Exception e)
422             {
423                OWLCore.logWarning(OWLCore.getID(), "Unable to load configuration for " + _zipFile.getName(), e);
424             }
425             finally
426             {
427                try
428                {
429                   propertyStream.close();
430                }
431                catch (IOException e1)
432                {
433                   // TODO Auto-generated catch block
434                   e1.printStackTrace();
435                }
436             }
437          }
438          if((entryName.endsWith(LibraryDescriptor.FILE_EXTENSION)) && (!zip.isDirectory()))
439          {
440             metaName = entryName.replaceAll(LibraryDescriptor.FILE_PATH + "/", LibraryDescriptor.METADATA_PATH + "/");
441             metaName = metaName.replaceAll(
442                LibraryDescriptor.FILE_PATH + "////",
443                LibraryDescriptor.METADATA_PATH + "////");
444             metaName = metaName.substring(0, metaName.lastIndexOf(".")) + LibraryDescriptor.METADATA_EXTENSION;
445             
446             oastName = entryName.replaceAll(LibraryDescriptor.FILE_PATH + "/", LibraryDescriptor.OAST_PATH + "/");
447             oastName = oastName.replaceAll(LibraryDescriptor.FILE_PATH + "////", LibraryDescriptor.OAST_PATH + "////");
448             oastName = oastName.substring(0, oastName.lastIndexOf(".")) + LibraryDescriptor.OAST_EXTENSION;
449             
450             metaEntry = _zipFile.getEntry(metaName);
451             oastEntry = _zipFile.getEntry(oastName);
452             
453             libraryEntry = new LibraryEntry(this, zip, metaEntry, oastEntry);
454             _entries.add(libraryEntry.createDescriptor());
455          }
456       }
457    }
458    
459    /* (non-Javadoc)
460     * @see com.bbn.swede.core.libraries.ILibrary#createDescriptor()
461     */
462    public ILibraryDescriptor createDescriptor()
463    {
464       if(_descriptor == null)
465       {
466          _descriptor = new LibraryDescriptor(this);
467       }
468       
469       return _descriptor;
470    }
471    
472    /***
473     * Retrieves the properties associated with this library, as stored in its
474     * configuration.
475     * @return the library's properties
476     */
477    Properties getProperties()
478    {
479       Properties toReturn = null;
480       synchronized (_configuration)
481       {
482          toReturn = _configuration.getProperties();
483       }
484       return toReturn;
485    }
486    
487    /***
488     * Sets the properties stored in this library's configuration.
489     * @param props the properties to associated with this library
490     */
491    void setProperties(Properties props)
492    {
493       synchronized (_configuration)
494       {
495          _configuration.setProperties(props);
496       }
497    }
498    
499    /***
500     * Retrieves the name of the file that holds this library (e.g. "library.swlib").
501     * @return The name of the library file
502     */
503    String getFilename()
504    {
505       return _filename;
506    }
507    
508    /***
509     * Retrieves the descriptors for all entries in this library.
510     * @return a list of the <code>ILibraryEntryDescriptor</code> objects this 
511     *         library holds
512     */
513    synchronized List getEntries()
514    {
515       List toReturn = new ArrayList();
516       toReturn.addAll(_entries);
517       return toReturn;
518    }
519    
520    /***
521     * This method attempts to refresh a library entry. This invovles
522     * re-downloading the appopriate file, and rebuilding the LibraryEntry using
523     * the updated copy of the files. All of the original metadata is preseved in
524     * the new LibraryEntry.
525     * 
526     * Because it is possible for files to be added and deleted from the library,
527     * and because the library maintains entries in a Collection, during a
528     * refresh, the entries are copied into a 'refreshBuffer', which is an array
529     * representation of the entries list which allows for easier manipulation of
530     * those entries.
531     * 
532     * 
533     * @param led a LibraryEntryDescriptor to update
534     * @param progressMonitor a progress monitor to report progress
535     * @param refreshBufferIndex which index is this entry in the _refeshBuffer?
536     * @return an IStatus object with IStatus.OK if everything went well, or
537     *         expressing the errors if any were encountered
538     */
539    private synchronized IStatus refresh(LibraryEntryDescriptor led,
540       IProgressMonitor progressMonitor, int refreshBufferIndex)
541    {
542       MultiStatus toReturn = new MultiStatus(OWLCore.getID(), IStatus.OK, "OK - " + led.getName() + "'", null);
543       URL downloadURL = null;
544       DownloadFileOperation dfo = null;
545       String filename = null;
546       LibraryEntryConfiguration tempConfig = (LibraryEntryConfiguration) led.getConfiguration();
547       LibraryEntry newEntry = null;
548       
549       progressMonitor.subTask("Retrieving URL...");
550       
551       try
552       {
553          downloadURL = new URL(tempConfig.getURL());
554          filename = led.getName();
555       }
556       catch (MalformedURLException e)
557       {
558          e.printStackTrace();
559          toReturn.add(new Status(IStatus.ERROR, OWLCore.getID(), IStatus.OK, e.getMessage(), e));
560       }
561       
562       progressMonitor.worked(1);
563       progressMonitor.subTask("Downloading...");
564       if(downloadURL != null)
565       {
566          dfo = new DownloadFileOperation(downloadURL, filename, true);
567          try
568          {
569             dfo.run(new NullProgressMonitor());
570             progressMonitor.worked(4);
571             if(dfo.getExceptions().size() == 0)
572             {
573                dfo.getFile().deleteOnExit();
574                _refreshBuffer[refreshBufferIndex] = (LibraryEntryDescriptor)new LibraryEntry(
575                   this, dfo.getFile(), ((LibraryEntryConfiguration)led
576                      .getConfiguration()).getProperties(), null)
577                   .createDescriptor();
578             }
579             else
580             {
581                Exception excep = null;
582                Iterator exceptions = dfo.getExceptions().iterator();
583                IStatus tempStatus = null;
584                while(exceptions.hasNext())
585                {
586                   excep = (Exception) exceptions.next();
587                   tempStatus = new Status(IStatus.ERROR, OWLCore.getID(),
588                      IStatus.OK, excep.getMessage() == null ? "Unknown error"
589                         : excep.getMessage(), excep);
590                   toReturn.add(tempStatus);
591                }
592             }
593          }
594          catch (InvocationTargetException e1)
595          {
596             toReturn.add(new Status(IStatus.ERROR, OWLCore.getID(), IStatus.OK, e1.getMessage(), e1));
597          }
598          catch (InterruptedException e1)
599          {
600             toReturn.add(new Status(IStatus.ERROR, OWLCore.getID(), IStatus.OK, e1.getMessage(), e1));
601          }
602       }
603 
604       progressMonitor.worked(5);
605       if(progressMonitor.isCanceled())
606       {
607          toReturn.add(Status.CANCEL_STATUS);
608       }
609       return toReturn; 
610    }
611    
612 
613    /***
614     * This method writes the contents into a new file in the directory specified 
615     * by <code>path</code>.  E.g. if path = "/home/user/files/libraries/", and 
616     * the library is named 'ont-library', then after a call to this method,
617     * there should exists a new file called 'ont-library.swlib' in 
618     * "/home/user/files/libraries/".
619     * 
620     * @param path the location of the new library
621     */
622    synchronized void writeLibrary(String path)
623    {
624       _filename = _configuration.getName() + Libraries.LIBRARY_EXTENSION;
625       File outFile = new File(path + (path.endsWith(File.separator) ? "" : File.separator) +  _filename);
626       ZipOutputStream outStream = null;
627       Iterator entryIterator = null;
628       LibraryEntryDescriptor entry = null;
629       ZipEntry libraryProps = null;
630       synchronized (_configuration)
631       {
632          try
633          {
634             outStream = new ZipOutputStream(new FileOutputStream(outFile));
635             outStream.setLevel(9);
636             libraryProps = new ZipEntry(_filename + LibraryDescriptor.METADATA_EXTENSION);
637             outStream.putNextEntry(libraryProps);
638             _configuration.store(outStream, "'" + _filename + "' Library Properties");
639             outStream.closeEntry();
640             entryIterator = _entries.iterator();
641             while(entryIterator.hasNext())
642             {
643                entry = (LibraryEntryDescriptor) entryIterator.next();
644                entry.write(outStream);
645             }
646          }
647          catch(Exception e)
648          {
649             OWLCore.logError(OWLCore.getID(), "Error writing library '" + _filename + "'.", e);
650          }
651          finally
652          {
653             try
654             {
655                outStream.flush();
656                outStream.finish();
657                outStream.close();
658             }
659             catch(Exception e)
660             {
661                OWLCore.logError(OWLCore.getID(), "Error cleaning up from writing library '" + _filename + "'.", e);
662             }
663          }
664       }
665    }
666    
667 }