View Javadoc

1   /**
2    *
3    * Copyright 2005 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.geronimo.system.configuration;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.net.MalformedURLException;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.SortedSet;
29  import java.util.Set;
30  import java.util.jar.JarFile;
31  import java.util.zip.ZipEntry;
32  import java.util.zip.ZipOutputStream;
33  import javax.management.ObjectName;
34  
35  import org.apache.geronimo.gbean.AbstractName;
36  import org.apache.geronimo.gbean.GBeanInfo;
37  import org.apache.geronimo.gbean.GBeanInfoBuilder;
38  import org.apache.geronimo.kernel.Kernel;
39  import org.apache.geronimo.kernel.ObjectNameUtil;
40  import org.apache.geronimo.kernel.config.ConfigurationAlreadyExistsException;
41  import org.apache.geronimo.kernel.config.ConfigurationData;
42  import org.apache.geronimo.kernel.config.ConfigurationInfo;
43  import org.apache.geronimo.kernel.config.ConfigurationStore;
44  import org.apache.geronimo.kernel.config.ConfigurationUtil;
45  import org.apache.geronimo.kernel.config.InvalidConfigException;
46  import org.apache.geronimo.kernel.config.NoSuchConfigException;
47  import org.apache.geronimo.kernel.config.IOUtil;
48  import org.apache.geronimo.kernel.repository.Artifact;
49  import org.apache.geronimo.kernel.repository.FileWriteMonitor;
50  import org.apache.geronimo.kernel.repository.WritableListableRepository;
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  
54  /**
55   * Implementation of ConfigurationStore GBean that installs/loads Configurations from a 
56   * repository.
57   *
58   * @version $Rev: 410741 $ $Date: 2006-05-31 21:35:48 -0700 (Wed, 31 May 2006) $
59   */
60  public class RepositoryConfigurationStore implements ConfigurationStore {
61      private final static Log log = LogFactory.getLog(RepositoryConfigurationStore.class);
62      private final Kernel kernel;
63      private final ObjectName objectName;
64      protected final WritableListableRepository repository;
65      private final InPlaceConfigurationUtil inPlaceConfUtil;
66  
67      public RepositoryConfigurationStore(WritableListableRepository repository) {
68          this(null, null, repository);
69      }
70  
71      public RepositoryConfigurationStore(Kernel kernel, String objectName, WritableListableRepository repository) {
72          this.kernel = kernel;
73          this.objectName = objectName == null ? null : ObjectNameUtil.getObjectName(objectName);
74          this.repository = repository;
75  
76          inPlaceConfUtil = new InPlaceConfigurationUtil();
77      }
78  
79      public String getObjectName() {
80          return objectName.getCanonicalName();
81      }
82  
83      public AbstractName getAbstractName() {
84          return kernel == null? null:kernel.getAbstractNameFor(this);
85      }
86  
87      public ConfigurationData loadConfiguration(Artifact configId) throws NoSuchConfigException, IOException, InvalidConfigException {
88          if(!configId.isResolved()) {
89              throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
90          }
91          File location = repository.getLocation(configId);
92  
93          if (existsReadable(location)) {
94              throw new NoSuchConfigException(configId);
95          }
96  
97          ConfigurationData configurationData;
98          try {
99              if (location.isDirectory()) {
100                 File serFile = new File(location, "META-INF");
101                 serFile = new File(serFile, "config.ser");
102 
103                 if (!serFile.exists()) {
104                     throw new InvalidConfigException("Configuration does not contain a META-INF/config.ser file: " + serFile);
105                 } else if (!serFile.canRead()) {
106                     throw new InvalidConfigException("Can not read configuration META-INF/config.ser file: " + serFile);
107                 }
108 
109                 ConfigurationStoreUtil.verifyChecksum(serFile);
110 
111                 InputStream in = new FileInputStream(serFile);
112                 try {
113                     configurationData = ConfigurationUtil.readConfigurationData(in);
114                 } finally {
115                     IOUtil.close(in);
116                 }
117             } else {
118                 JarFile jarFile = new JarFile(location);
119                 InputStream in = null;
120                 try {
121                     ZipEntry entry = jarFile.getEntry("META-INF/config.ser");
122                     in = jarFile.getInputStream(entry);
123                     configurationData = ConfigurationUtil.readConfigurationData(in);
124                 } finally {
125                     IOUtil.close(in);
126                     IOUtil.close(jarFile);
127                 }
128             }
129         } catch (ClassNotFoundException e) {
130             throw new InvalidConfigException("Unable to load class from config: " + configId, e);
131         }
132 
133         configurationData.setConfigurationDir(location);
134         configurationData.setConfigurationStore(this);
135         if (kernel != null) {
136             configurationData.setNaming(kernel.getNaming());
137         }
138 
139         return configurationData;
140     }
141 
142     private boolean existsReadable(File location) {
143         return !location.exists() || !location.canRead();
144     }
145 
146     public boolean containsConfiguration(Artifact configId) {
147         if(!configId.isResolved()) {
148             throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
149         }
150         File location = repository.getLocation(configId);
151         if (location.isDirectory()) {
152             location = new File(location, "META-INF");
153             location = new File(location, "config.ser");
154             return location.isFile() && location.canRead();
155         } else {
156             JarFile jarFile = null;
157             try {
158                 jarFile = new JarFile(location);
159                 ZipEntry entry = jarFile.getEntry("META-INF/config.ser");
160                 return entry != null && !entry.isDirectory();
161             } catch (IOException e) {
162                 return false;
163             } finally {
164                 IOUtil.close(jarFile);
165             }
166         }
167     }
168 
169     public File createNewConfigurationDir(Artifact configId) throws ConfigurationAlreadyExistsException {
170         if(!configId.isResolved()) {
171             throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
172         }
173         File location = repository.getLocation(configId);
174         if (location.exists()) {
175             throw new ConfigurationAlreadyExistsException("Configuration already exists: " + configId);
176         }
177         location.mkdirs();
178         if (!location.exists()) {
179             throw new ConfigurationAlreadyExistsException("Could not create configuration directory: " + location);
180         }
181         return location;
182     }
183 
184     public Set resolve(Artifact configId, String moduleName, String path) throws NoSuchConfigException, MalformedURLException {
185         if(!configId.isResolved()) {
186             throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
187         }
188         File location = repository.getLocation(configId);
189         if (location.isDirectory()) {
190             File inPlaceLocation = null;
191             try {
192                 inPlaceLocation = inPlaceConfUtil.readInPlaceLocation(location);
193             } catch (IOException e) {
194             }
195             if (null != inPlaceLocation) {
196                 location = inPlaceLocation;
197             }
198 
199             if (moduleName != null) {
200                 location = new File(location, moduleName);
201             }
202 
203             if (location.isDirectory()) {
204                 Set matches = IOUtil.search(location, path);
205                 return matches;
206             } else {
207                 Set matches = IOUtil.search(location, path);
208                 return matches;
209             }
210         } else {
211             Set matches = IOUtil.search(location, moduleName + "/" +path);
212             return matches;
213         }
214     }
215 
216     public void exportConfiguration(Artifact configId, OutputStream output) throws IOException, NoSuchConfigException {
217         if(!configId.isResolved()) {
218             throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
219         }
220         File dir = repository.getLocation(configId);
221         if (dir == null) {
222             throw new NoSuchConfigException(configId);
223         }
224         if (existsReadable(dir)) {
225             throw new IOException("Cannot read config store directory for " + configId + " (" + dir.getAbsolutePath() + ")");
226         }
227         ZipOutputStream out = new ZipOutputStream(output);
228         byte[] buf = new byte[10240];
229         writeToZip(dir, out, "", buf);
230         if (inPlaceConfUtil.isInPlaceConfiguration(dir)) {
231             dir = inPlaceConfUtil.readInPlaceLocation(dir);
232             writeToZip(dir, out, "", buf);
233         }
234         out.closeEntry();
235         out.finish();
236         out.flush();
237     }
238 
239     private void writeToZip(File dir, ZipOutputStream out, String prefix, byte[] buf) throws IOException {
240         File[] all = dir.listFiles();
241         for (int i = 0; i < all.length; i++) {
242             File file = all[i];
243             if (file.isDirectory()) {
244                 writeToZip(file, out, prefix + file.getName() + "/", buf);
245             } else {
246                 ZipEntry entry = new ZipEntry(prefix + file.getName());
247                 out.putNextEntry(entry);
248                 writeToZipStream(file, out, buf);
249             }
250         }
251     }
252 
253     private void writeToZipStream(File file, OutputStream out, byte[] buf) throws IOException {
254         FileInputStream in = new FileInputStream(file);
255         int count;
256         try {
257             while ((count = in.read(buf, 0, buf.length)) > -1) {
258                 out.write(buf, 0, count);
259             }
260         } finally {
261             in.close();
262         }
263     }
264 
265     public void install(InputStream in, int size, Artifact configId, FileWriteMonitor fileWriteMonitor) throws IOException {
266         try {
267             repository.copyToRepository(in, size, configId, fileWriteMonitor);
268         } catch (IOException e) {
269             throw e;
270         } finally {
271             IOUtil.close(in);
272         }
273     }
274 
275     public boolean isInPlaceConfiguration(Artifact configId) throws NoSuchConfigException, IOException {
276         if(!configId.isResolved()) {
277             throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
278         }
279         File location = repository.getLocation(configId);
280         if (location.isDirectory()) {
281             return inPlaceConfUtil.isInPlaceConfiguration(location);
282         } else {
283             return false;
284         }
285     }
286 
287     public void install(ConfigurationData configurationData) throws IOException, InvalidConfigException {
288         // determine the source file/dir
289         File source = configurationData.getConfigurationDir();
290         if (!source.exists()) {
291             throw new InvalidConfigException("Source does not exist " + source);
292         } else if (!source.canRead()) {
293             throw new InvalidConfigException("Source is not readable " + source);
294         }
295 
296         // determine the target location
297         Artifact configId = configurationData.getId();
298         File destination = repository.getLocation(configId);
299 
300         // if directory in the correct place -- noop
301         if (!source.equals(destination)) {
302             if (destination.exists()) {
303                 throw new ConfigurationAlreadyExistsException(configId.toString());
304             }
305 
306             if (source.isFile()) {
307                 // Assume this is a jar file
308                 // copy it into the repository; repository should unpack it
309                 repository.copyToRepository(source, configId, null);
310             } else if (source.isDirectory()) {
311                 // directory is in wrong place -- directory copy
312                 IOUtil.recursiveCopy(source, destination);
313             } else {
314                 throw new InvalidConfigException("Unable to install configuration from " + source);
315             }
316         }
317 
318         ExecutableConfigurationUtil.writeConfiguration(configurationData, destination);
319 
320         // write in-place configuration config file, if need be.
321         inPlaceConfUtil.writeInPlaceLocation(configurationData, destination);
322     }
323 
324     public void uninstall(Artifact configId) throws NoSuchConfigException, IOException {
325         if(!configId.isResolved()) {
326             throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
327         }
328         ConfigurationInfo configurationInfo = null;
329         try {
330             configurationInfo = loadConfigurationInfo(configId);
331         } catch (IOException e) {
332             // don't really care
333         }
334         File location = repository.getLocation(configId);
335         IOUtil.recursiveDelete(location);
336         //todo: for Maven 2 repo, delete the version directory if there's nothing left in it (probably the case)
337 
338         if (configurationInfo != null) {
339             IOException ioException = null;
340             for (Iterator iterator = configurationInfo.getOwnedConfigurations().iterator(); iterator.hasNext();) {
341                 Artifact ownedConfiguration = (Artifact) iterator.next();
342                 try {
343                     uninstall(ownedConfiguration);
344                 } catch (NoSuchConfigException e) {
345                     // ignored - already deleted or never installed
346                 } catch (IOException e) {
347                     if (ioException != null) {
348                         ioException = e;
349                     }
350                 }
351                 if (ioException != null) {
352                     throw ioException;
353                 }
354             }
355         }
356     }
357 
358     public List listConfigurations() {
359         SortedSet artifacts = repository.list();
360 
361         List configs;
362         synchronized (this) {
363             configs = new ArrayList();
364             for (Iterator i = artifacts.iterator(); i.hasNext();) {
365                 Artifact configId = (Artifact) i.next();
366                 File dir = repository.getLocation(configId);
367                 File meta = new File(dir, "META-INF");
368                 if(!meta.isDirectory() || !meta.canRead()) {
369                     continue;
370                 }
371                 File ser = new File(meta, "config.ser");
372                 if(!ser.isFile() || !ser.canRead() || ser.length() == 0) {
373                     continue;
374                 }
375                 try {
376                     ConfigurationInfo configurationInfo = loadConfigurationInfo(configId);
377                     configs.add(configurationInfo);
378                 } catch (NoSuchConfigException e) {
379                     log.error("Unexpected error: found META-INF/config.ser for "+configId+" but couldn't load ConfigurationInfo", e);
380                 } catch (IOException e) {
381                     log.error("Unable to load ConfigurationInfo for "+configId, e);
382                 }
383             }
384         }
385         return configs;
386     }
387 
388     private ConfigurationInfo loadConfigurationInfo(Artifact configId) throws NoSuchConfigException, IOException {
389         File location = repository.getLocation(configId);
390 
391         if (!location.exists() && !location.canRead()) {
392             throw new NoSuchConfigException(configId);
393         }
394 
395         File inPlaceLocation = inPlaceConfUtil.readInPlaceLocation(location);
396 
397         ConfigurationInfo configurationInfo;
398         if (location.isDirectory()) {
399             File infoFile = new File(location, "META-INF");
400             infoFile = new File(infoFile, "config.info");
401 
402             InputStream in = new FileInputStream(infoFile);
403             try {
404                 configurationInfo = ConfigurationUtil.readConfigurationInfo(in, getAbstractName(), inPlaceLocation);
405             } finally {
406                 IOUtil.close(in);
407             }
408         } else {
409             JarFile jarFile = new JarFile(location);
410             InputStream in = null;
411             try {
412                 ZipEntry entry = jarFile.getEntry("META-INF/config.info");
413                 in = jarFile.getInputStream(entry);
414                 configurationInfo = ConfigurationUtil.readConfigurationInfo(in, getAbstractName(), inPlaceLocation);
415             } finally {
416                 IOUtil.close(in);
417                 IOUtil.close(jarFile);
418             }
419         }
420 
421         return configurationInfo;
422     }
423 
424 //    /**
425 //     * Thread to cleanup unused Config Store entries.
426 //     * On Windows, open files can't be deleted. Until MultiParentClassLoaders
427 //     * are GC'ed, we won't be able to delete Config Store directories/files.
428 //     */
429 //    class ConfigStoreReaper implements Runnable {
430 //        private final int reaperInterval;
431 //        private volatile boolean done = false;
432 //
433 //        public ConfigStoreReaper(int reaperInterval) {
434 //            this.reaperInterval = reaperInterval;
435 //        }
436 //
437 //        public void close() {
438 //            this.done = true;
439 //        }
440 //
441 //        public void run() {
442 //            log.debug("ConfigStoreReaper started");
443 //            while (!done) {
444 //                try {
445 //                    Thread.sleep(reaperInterval);
446 //                } catch (InterruptedException e) {
447 //                    continue;
448 //                }
449 //                reap();
450 //            }
451 //        }
452 //
453 //        /**
454 //         * For every directory in the pendingDeletionIndex, attempt to delete all
455 //         * sub-directories and files.
456 //         */
457 //        public void reap() {
458 //            // return, if there's nothing to do
459 //            if (pendingDeletionIndex.size() == 0)
460 //                return;
461 //            // Otherwise, attempt to delete all of the directories
462 //            Enumeration list = pendingDeletionIndex.propertyNames();
463 //            boolean dirDeleted = false;
464 //            while (list.hasMoreElements()) {
465 //                String dirName = (String) list.nextElement();
466 //                File deleteFile = new File(dirName);
467 //                try {
468 //                    delete(deleteFile);
469 //                }
470 //                catch (IOException ioe) { // ignore errors
471 //                }
472 //                if (!deleteFile.exists()) {
473 //                    String configName = pendingDeletionIndex.getProperty(dirName);
474 //                    pendingDeletionIndex.remove(dirName);
475 //                    dirDeleted = true;
476 //                    log.debug("Reaped configuration " + configName + " in directory " + dirName);
477 //                }
478 //            }
479 //            // If we deleted any directories, persist the list of directories to disk...
480 //            if (dirDeleted) {
481 //                try {
482 //                    synchronized (pendingDeletionIndex) {
483 //                        saveDeleteIndex();
484 //                    }
485 //                }
486 //                catch (IOException ioe) {
487 //                    log.warn("Error saving " + DELETE_NAME + " file.", ioe);
488 //                }
489 //            }
490 //        }
491 //    }
492 //
493     public static final GBeanInfo GBEAN_INFO;
494 
495     public static GBeanInfo getGBeanInfo() {
496         return GBEAN_INFO;
497     }
498 
499     static {
500         GBeanInfoBuilder builder = GBeanInfoBuilder.createStatic(RepositoryConfigurationStore.class, "ConfigurationStore");
501         builder.addAttribute("kernel", Kernel.class, false);
502         builder.addAttribute("objectName", String.class, false);
503         builder.addReference("Repository", WritableListableRepository.class, "Repository");
504         builder.setConstructor(new String[]{"kernel", "objectName", "Repository"});
505         GBEAN_INFO = builder.getBeanInfo();
506     }
507 }