View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 org.kuali.rice.kew.plugin;
17  
18  import org.apache.commons.io.FileUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.config.property.Config;
21  import org.kuali.rice.kew.api.WorkflowRuntimeException;
22  
23  import java.io.BufferedInputStream;
24  import java.io.BufferedOutputStream;
25  import java.io.File;
26  import java.io.FileOutputStream;
27  import java.io.InputStream;
28  import java.io.OutputStream;
29  import java.net.MalformedURLException;
30  import java.net.URL;
31  import java.util.Enumeration;
32  import java.util.zip.ZipEntry;
33  import java.util.zip.ZipFile;
34  
35  
36  /**
37   * Loads a plugin from a zip file on the file system.
38   *
39   * @author Kuali Rice Team (rice.collab@kuali.org)
40   */
41  public class ZipFilePluginLoader extends BasePluginLoader {
42  	private static final Logger LOG = Logger.getLogger(ZipFilePluginLoader.class);
43  
44  	private final File pluginZipFile;
45      private final File extractionDirectory;
46      private long zipFileLastModified = -1;
47      private boolean loadFailed = false;
48  
49      private static String validatePluginZipFile(File pluginZipFile) {
50          PluginUtils.validatePluginZipFile(pluginZipFile);
51          String fileName = pluginZipFile.getName();
52          int indexOf = fileName.lastIndexOf(".zip");
53          return fileName.substring(0, indexOf);
54      }
55  
56      public ZipFilePluginLoader(File pluginZipFile, File sharedPluginDirectory, ClassLoader parentClassLoader, Config parentConfig) {
57      	super(validatePluginZipFile(pluginZipFile), sharedPluginDirectory, parentClassLoader, parentConfig);
58      	this.pluginZipFile = pluginZipFile;
59      	this.extractionDirectory = determineExtractionDirectory(getSimplePluginName(), pluginZipFile);
60      }
61      
62  	public boolean isModified() {
63  		long currentZipFileLastModified = pluginZipFile.lastModified();
64  		if (zipFileLastModified == -1) {
65  			zipFileLastModified = currentZipFileLastModified;
66  			return false;
67  		} else if (currentZipFileLastModified > zipFileLastModified) {
68  			return !isZipFileStillBeingModified();
69  		}
70  		return false;
71  	}
72  
73  	protected boolean isZipFileStillBeingModified() {
74  		long lastModified = pluginZipFile.lastModified();
75  		long size = pluginZipFile.length();
76  		// sleep for a fraction of a second and then check again if the values have changed
77  		try {
78  			Thread.sleep(100);
79  		} catch (InterruptedException e) {}
80  		if (lastModified != pluginZipFile.lastModified()) {
81  			return true;
82  		}
83  		if (size != pluginZipFile.length()) {
84  			return true;
85  		}
86  		return false;
87  	}
88  
89  	public boolean isRemoved() {
90  		return pluginZipFile != null && !pluginZipFile.exists();
91  	}
92  
93      @Override
94  	public Plugin load() throws Exception {
95          try {
96              updateLastModified();
97              extractPluginFiles();
98              Plugin plugin = super.load();
99              loadFailed = false;
100             return plugin;
101         } catch (Exception e) {
102             loadFailed = true;
103             throw e;
104         }
105 	}
106 
107     protected File determineExtractionDirectory(String pluginName, File pluginZipFile) {
108 		return new File(pluginZipFile.getParentFile(), pluginName);
109 	}
110 
111     /**
112      * Extracts the plugin files if necessary.
113      */
114     protected void extractPluginFiles() throws Exception {
115     	if (isExtractNeeded()) {
116     		// first, delete the current copy of the extracted plugin
117     		if (extractionDirectory.exists()) {
118     			// TODO how to handle locked files under windows?!?  This will throw an IOException in this case.
119     			FileUtils.deleteDirectory(extractionDirectory);
120     		}
121     		if (!extractionDirectory.mkdir()) {
122     			throw new WorkflowRuntimeException("Could not create the extraction directory for the plugin: " + extractionDirectory.getAbsolutePath());
123     		}
124     		ZipFile zipFile = new ZipFile(pluginZipFile, ZipFile.OPEN_READ);
125     		for (Enumeration entries = zipFile.entries(); entries.hasMoreElements();) {
126     			ZipEntry entry = (ZipEntry)entries.nextElement();
127 				File entryFile = new File(extractionDirectory + java.io.File.separator + entry.getName());
128     			if (entry.isDirectory()) { // if its a directory, create it
129     				if (!entryFile.mkdir()) {
130     					throw new WorkflowRuntimeException("Failed to create directory: " + entryFile.getAbsolutePath());
131     				}
132     				continue;
133     			}
134     			InputStream is = null;
135     			OutputStream os = null;
136     			try {
137     				is = new BufferedInputStream(zipFile.getInputStream(entry)); // get the input stream
138     				os = new BufferedOutputStream(new FileOutputStream(entryFile));
139     				while (is.available() > 0) {  // write contents of 'is' to 'fos'
140     					os.write(is.read());
141     				}
142     			} finally {
143     				if (os != null) {
144     					os.close();
145     				}
146     				if (is != null) {
147     					is.close();
148     				}
149     			}
150     		}
151     	}
152     }
153     
154     /**
155      * @return the loadFailed
156      */
157     public boolean isLoadFailed() {
158         return this.loadFailed;
159     }
160 
161     /**
162      * An extract is required if the plugin has been modified or the last modified date of the zip file
163      * is later than the last modified date of the extraction directory.
164      */
165     protected boolean isExtractNeeded() {
166     	return isModified() || pluginZipFile.lastModified() > extractionDirectory.lastModified();
167     }
168 
169     protected void updateLastModified() {
170     	zipFileLastModified = pluginZipFile.lastModified();
171     }
172 
173 	protected PluginClassLoader createPluginClassLoader() throws MalformedURLException {
174         LOG.info(getLogPrefix() + " Initiating loading of plugin from file system: " + extractionDirectory.getPath());
175         LOG.info(getLogPrefix() + " Absolute path on file system is: " + extractionDirectory.getAbsolutePath());
176         /* MalformedURLException should technically never be thrown as the URLs are coming from (presumably)
177          * valid File objects
178          */
179         return new PluginClassLoader(parentClassLoader, sharedPluginDirectory, extractionDirectory);
180     }
181 
182     protected URL getPluginConfigURL() throws PluginException, MalformedURLException {
183         File pluginConfigFile = new File(extractionDirectory, pluginConfigPath);
184         if (!pluginConfigFile.exists() || !pluginConfigFile.isFile()) {
185             throw new PluginException(getLogPrefix() + " Could not locate the plugin config file at path " + pluginConfigFile.getAbsolutePath());
186         }
187         return pluginConfigFile.toURI().toURL();
188     }
189 }