001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.geronimo.system.configuration;
018
019 import java.io.File;
020 import java.io.FileInputStream;
021 import java.io.IOException;
022 import java.io.InputStream;
023 import java.io.OutputStream;
024 import java.net.MalformedURLException;
025 import java.util.ArrayList;
026 import java.util.Iterator;
027 import java.util.List;
028 import java.util.SortedSet;
029 import java.util.Set;
030 import java.util.jar.JarFile;
031 import java.util.zip.ZipEntry;
032 import java.util.zip.ZipOutputStream;
033 import javax.management.ObjectName;
034
035 import org.apache.geronimo.gbean.AbstractName;
036 import org.apache.geronimo.gbean.GBeanInfo;
037 import org.apache.geronimo.gbean.GBeanInfoBuilder;
038 import org.apache.geronimo.kernel.Kernel;
039 import org.apache.geronimo.kernel.ObjectNameUtil;
040 import org.apache.geronimo.kernel.config.ConfigurationAlreadyExistsException;
041 import org.apache.geronimo.kernel.config.ConfigurationData;
042 import org.apache.geronimo.kernel.config.ConfigurationInfo;
043 import org.apache.geronimo.kernel.config.ConfigurationStore;
044 import org.apache.geronimo.kernel.config.ConfigurationUtil;
045 import org.apache.geronimo.kernel.config.InvalidConfigException;
046 import org.apache.geronimo.kernel.config.NoSuchConfigException;
047 import org.apache.geronimo.kernel.config.IOUtil;
048 import org.apache.geronimo.kernel.config.PersistentConfigurationList;
049 import org.apache.geronimo.kernel.repository.Artifact;
050 import org.apache.geronimo.kernel.repository.FileWriteMonitor;
051 import org.apache.geronimo.kernel.repository.WritableListableRepository;
052 import org.apache.commons.logging.Log;
053 import org.apache.commons.logging.LogFactory;
054
055 /**
056 * Implementation of ConfigurationStore GBean that installs/loads Configurations from a
057 * repository.
058 *
059 * @version $Rev: 487175 $ $Date: 2006-12-14 03:10:31 -0800 (Thu, 14 Dec 2006) $
060 */
061 public class RepositoryConfigurationStore implements ConfigurationStore {
062 private final static Log log = LogFactory.getLog(RepositoryConfigurationStore.class);
063 private final Kernel kernel;
064 private final ObjectName objectName;
065 protected final WritableListableRepository repository;
066 private final InPlaceConfigurationUtil inPlaceConfUtil;
067
068 public RepositoryConfigurationStore(WritableListableRepository repository) {
069 this(null, null, repository);
070 }
071
072 public RepositoryConfigurationStore(Kernel kernel, String objectName, WritableListableRepository repository) {
073 this.kernel = kernel;
074 this.objectName = objectName == null ? null : ObjectNameUtil.getObjectName(objectName);
075 this.repository = repository;
076
077 inPlaceConfUtil = new InPlaceConfigurationUtil();
078 }
079
080 public String getObjectName() {
081 return objectName.getCanonicalName();
082 }
083
084 public AbstractName getAbstractName() {
085 return kernel == null? null:kernel.getAbstractNameFor(this);
086 }
087
088 public ConfigurationData loadConfiguration(Artifact configId) throws NoSuchConfigException, IOException, InvalidConfigException {
089 if(!configId.isResolved()) {
090 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
091 }
092 File location = repository.getLocation(configId);
093
094 if (existsReadable(location)) {
095 throw new NoSuchConfigException(configId);
096 }
097
098 ConfigurationData configurationData;
099 try {
100 if (location.isDirectory()) {
101 File serFile = new File(location, "META-INF");
102 serFile = new File(serFile, "config.ser");
103
104 if (!serFile.exists()) {
105 throw new InvalidConfigException("Configuration does not contain a META-INF/config.ser file: " + serFile);
106 } else if (!serFile.canRead()) {
107 throw new InvalidConfigException("Can not read configuration META-INF/config.ser file: " + serFile);
108 }
109
110 ConfigurationStoreUtil.verifyChecksum(serFile);
111
112 InputStream in = new FileInputStream(serFile);
113 try {
114 configurationData = ConfigurationUtil.readConfigurationData(in);
115 } finally {
116 IOUtil.close(in);
117 }
118 } else {
119 JarFile jarFile = new JarFile(location);
120 InputStream in = null;
121 try {
122 ZipEntry entry = jarFile.getEntry("META-INF/config.ser");
123 in = jarFile.getInputStream(entry);
124 configurationData = ConfigurationUtil.readConfigurationData(in);
125 } finally {
126 IOUtil.close(in);
127 IOUtil.close(jarFile);
128 }
129 }
130 } catch (ClassNotFoundException e) {
131 throw new InvalidConfigException("Unable to load class from config: " + configId, e);
132 }
133
134 configurationData.setConfigurationDir(location);
135 configurationData.setConfigurationStore(this);
136 if (kernel != null) {
137 configurationData.setNaming(kernel.getNaming());
138 }
139
140 return configurationData;
141 }
142
143 private boolean existsReadable(File location) {
144 return !location.exists() || !location.canRead();
145 }
146
147 public boolean containsConfiguration(Artifact configId) {
148 if(!configId.isResolved()) {
149 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
150 }
151 File location = repository.getLocation(configId);
152 if (location.isDirectory()) {
153 location = new File(location, "META-INF");
154 location = new File(location, "config.ser");
155 return location.isFile() && location.canRead();
156 } else {
157 JarFile jarFile = null;
158 try {
159 jarFile = new JarFile(location);
160 ZipEntry entry = jarFile.getEntry("META-INF/config.ser");
161 return entry != null && !entry.isDirectory();
162 } catch (IOException e) {
163 return false;
164 } finally {
165 IOUtil.close(jarFile);
166 }
167 }
168 }
169
170 public File createNewConfigurationDir(Artifact configId) throws ConfigurationAlreadyExistsException {
171 if(!configId.isResolved()) {
172 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
173 }
174 File location = repository.getLocation(configId);
175 if (location.exists()) {
176 throw new ConfigurationAlreadyExistsException("Configuration already exists: " + configId);
177 }
178 location.mkdirs();
179 if (!location.exists()) {
180 throw new ConfigurationAlreadyExistsException("Could not create configuration directory: " + location);
181 }
182 return location;
183 }
184
185 public Set resolve(Artifact configId, String moduleName, String path) throws NoSuchConfigException, MalformedURLException {
186 if(!configId.isResolved()) {
187 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
188 }
189 File location = repository.getLocation(configId);
190 if (location.isDirectory()) {
191 File inPlaceLocation = null;
192 try {
193 inPlaceLocation = inPlaceConfUtil.readInPlaceLocation(location);
194 } catch (IOException e) {
195 }
196 if (null != inPlaceLocation) {
197 location = inPlaceLocation;
198 }
199
200 if (moduleName != null) {
201 location = new File(location, moduleName);
202 }
203
204 if (location.isDirectory()) {
205 Set matches = IOUtil.search(location, path);
206 return matches;
207 } else {
208 Set matches = IOUtil.search(location, path);
209 return matches;
210 }
211 } else {
212 Set matches = IOUtil.search(location, moduleName + "/" +path);
213 return matches;
214 }
215 }
216
217 public void exportConfiguration(Artifact configId, OutputStream output) throws IOException, NoSuchConfigException {
218 if(!configId.isResolved()) {
219 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
220 }
221 File dir = repository.getLocation(configId);
222 if (dir == null) {
223 throw new NoSuchConfigException(configId);
224 }
225 if (existsReadable(dir)) {
226 throw new IOException("Cannot read config store directory for " + configId + " (" + dir.getAbsolutePath() + ")");
227 }
228 ZipOutputStream out = new ZipOutputStream(output);
229 byte[] buf = new byte[10240];
230 writeToZip(dir, out, "", buf);
231 if (inPlaceConfUtil.isInPlaceConfiguration(dir)) {
232 dir = inPlaceConfUtil.readInPlaceLocation(dir);
233 writeToZip(dir, out, "", buf);
234 }
235 out.closeEntry();
236 out.finish();
237 out.flush();
238 }
239
240 private void writeToZip(File dir, ZipOutputStream out, String prefix, byte[] buf) throws IOException {
241 File[] all = dir.listFiles();
242 for (int i = 0; i < all.length; i++) {
243 File file = all[i];
244 if (file.isDirectory()) {
245 writeToZip(file, out, prefix + file.getName() + "/", buf);
246 } else {
247 ZipEntry entry = new ZipEntry(prefix + file.getName());
248 out.putNextEntry(entry);
249 writeToZipStream(file, out, buf);
250 }
251 }
252 }
253
254 private void writeToZipStream(File file, OutputStream out, byte[] buf) throws IOException {
255 FileInputStream in = new FileInputStream(file);
256 int count;
257 try {
258 while ((count = in.read(buf, 0, buf.length)) > -1) {
259 out.write(buf, 0, count);
260 }
261 } finally {
262 in.close();
263 }
264 }
265
266 public void install(InputStream in, int size, Artifact configId, FileWriteMonitor fileWriteMonitor) throws IOException {
267 try {
268 repository.copyToRepository(in, size, configId, fileWriteMonitor);
269 } catch (IOException e) {
270 throw e;
271 } finally {
272 IOUtil.close(in);
273 }
274 }
275
276 public boolean isInPlaceConfiguration(Artifact configId) throws NoSuchConfigException, IOException {
277 if(!configId.isResolved()) {
278 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
279 }
280 File location = repository.getLocation(configId);
281 if (location.isDirectory()) {
282 return inPlaceConfUtil.isInPlaceConfiguration(location);
283 } else {
284 return false;
285 }
286 }
287
288 public void install(ConfigurationData configurationData) throws IOException, InvalidConfigException {
289 // determine the source file/dir
290 File source = configurationData.getConfigurationDir();
291 if (!source.exists()) {
292 throw new InvalidConfigException("Source does not exist " + source);
293 } else if (!source.canRead()) {
294 throw new InvalidConfigException("Source is not readable " + source);
295 }
296
297 // determine the target location
298 Artifact configId = configurationData.getId();
299 File destination = repository.getLocation(configId);
300
301 // if directory in the correct place -- noop
302 if (!source.equals(destination)) {
303 if (destination.exists()) {
304 throw new ConfigurationAlreadyExistsException(configId.toString());
305 }
306
307 if (source.isFile()) {
308 // Assume this is a jar file
309 // copy it into the repository; repository should unpack it
310 repository.copyToRepository(source, configId, null);
311 } else if (source.isDirectory()) {
312 // directory is in wrong place -- directory copy
313 IOUtil.recursiveCopy(source, destination);
314 } else {
315 throw new InvalidConfigException("Unable to install configuration from " + source);
316 }
317 }
318
319 ExecutableConfigurationUtil.writeConfiguration(configurationData, destination);
320
321 // write in-place configuration config file, if need be.
322 inPlaceConfUtil.writeInPlaceLocation(configurationData, destination);
323 }
324
325 public void uninstall(Artifact configId) throws NoSuchConfigException, IOException {
326 if(!configId.isResolved()) {
327 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved");
328 }
329 ConfigurationInfo configurationInfo = null;
330 try {
331 configurationInfo = loadConfigurationInfo(configId);
332 } catch (IOException e) {
333 // don't really care
334 }
335 File location = repository.getLocation(configId);
336 IOUtil.recursiveDelete(location);
337 // Number of directory levels up, to check and delete empty parent directories in the repo
338 int dirDepth = 0;
339
340 // FIXME: Determine the repository type
341 // For now assume the repo is a Maven2Repository. This should not cause any harm even if it is an
342 // Maven1Repository, for it would be deleting the 'repository' directory if it happens to be empty.
343 boolean m2repo = true;
344 if(m2repo) {
345 // Check version, artifact and group directories, i.e. 3 levels up
346 dirDepth = 3;
347 }
348
349 File temp = location;
350 for(int i = 0; i < dirDepth; ++i) {
351 if((temp = temp.getParentFile()).listFiles().length == 0) {
352 // Directory is empty. Remove it.
353 temp.delete();
354 } else {
355 // Directory is not empty. No need to check any more parent directories
356 break;
357 }
358 }
359
360 try {
361 // Is this the right way to get hold of PersistentConfigurationList?
362 PersistentConfigurationList configList = (PersistentConfigurationList) kernel.getGBean(PersistentConfigurationList.class);
363 if(!configList.hasGBeanAttributes(configId)) configList.removeConfiguration(configId);
364 } catch (Exception e) {
365 log.warn("Unable to remove configuration from persistent configurations. id = "+configId, e);
366 }
367
368 if (configurationInfo != null) {
369 IOException ioException = null;
370 for (Iterator iterator = configurationInfo.getOwnedConfigurations().iterator(); iterator.hasNext();) {
371 Artifact ownedConfiguration = (Artifact) iterator.next();
372 try {
373 uninstall(ownedConfiguration);
374 } catch (NoSuchConfigException e) {
375 // ignored - already deleted or never installed
376 } catch (IOException e) {
377 if (ioException != null) {
378 ioException = e;
379 }
380 }
381 if (ioException != null) {
382 throw ioException;
383 }
384 }
385 }
386 }
387
388 public List listConfigurations() {
389 SortedSet artifacts = repository.list();
390
391 List configs;
392 synchronized (this) {
393 configs = new ArrayList();
394 for (Iterator i = artifacts.iterator(); i.hasNext();) {
395 Artifact configId = (Artifact) i.next();
396 File dir = repository.getLocation(configId);
397 File meta = new File(dir, "META-INF");
398 if(!meta.isDirectory() || !meta.canRead()) {
399 continue;
400 }
401 File ser = new File(meta, "config.ser");
402 if(!ser.isFile() || !ser.canRead() || ser.length() == 0) {
403 continue;
404 }
405 try {
406 ConfigurationInfo configurationInfo = loadConfigurationInfo(configId);
407 configs.add(configurationInfo);
408 } catch (NoSuchConfigException e) {
409 log.error("Unexpected error: found META-INF/config.ser for "+configId+" but couldn't load ConfigurationInfo", e);
410 } catch (IOException e) {
411 log.error("Unable to load ConfigurationInfo for "+configId, e);
412 }
413 }
414 }
415 return configs;
416 }
417
418 private ConfigurationInfo loadConfigurationInfo(Artifact configId) throws NoSuchConfigException, IOException {
419 File location = repository.getLocation(configId);
420
421 if (!location.exists() && !location.canRead()) {
422 throw new NoSuchConfigException(configId);
423 }
424
425 File inPlaceLocation = inPlaceConfUtil.readInPlaceLocation(location);
426
427 ConfigurationInfo configurationInfo;
428 if (location.isDirectory()) {
429 File infoFile = new File(location, "META-INF");
430 infoFile = new File(infoFile, "config.info");
431
432 InputStream in = new FileInputStream(infoFile);
433 try {
434 configurationInfo = ConfigurationUtil.readConfigurationInfo(in, getAbstractName(), inPlaceLocation);
435 } finally {
436 IOUtil.close(in);
437 }
438 } else {
439 JarFile jarFile = new JarFile(location);
440 InputStream in = null;
441 try {
442 ZipEntry entry = jarFile.getEntry("META-INF/config.info");
443 in = jarFile.getInputStream(entry);
444 configurationInfo = ConfigurationUtil.readConfigurationInfo(in, getAbstractName(), inPlaceLocation);
445 } finally {
446 IOUtil.close(in);
447 IOUtil.close(jarFile);
448 }
449 }
450
451 return configurationInfo;
452 }
453
454 // /**
455 // * Thread to cleanup unused Config Store entries.
456 // * On Windows, open files can't be deleted. Until MultiParentClassLoaders
457 // * are GC'ed, we won't be able to delete Config Store directories/files.
458 // */
459 // class ConfigStoreReaper implements Runnable {
460 // private final int reaperInterval;
461 // private volatile boolean done = false;
462 //
463 // public ConfigStoreReaper(int reaperInterval) {
464 // this.reaperInterval = reaperInterval;
465 // }
466 //
467 // public void close() {
468 // this.done = true;
469 // }
470 //
471 // public void run() {
472 // log.debug("ConfigStoreReaper started");
473 // while (!done) {
474 // try {
475 // Thread.sleep(reaperInterval);
476 // } catch (InterruptedException e) {
477 // continue;
478 // }
479 // reap();
480 // }
481 // }
482 //
483 // /**
484 // * For every directory in the pendingDeletionIndex, attempt to delete all
485 // * sub-directories and files.
486 // */
487 // public void reap() {
488 // // return, if there's nothing to do
489 // if (pendingDeletionIndex.size() == 0)
490 // return;
491 // // Otherwise, attempt to delete all of the directories
492 // Enumeration list = pendingDeletionIndex.propertyNames();
493 // boolean dirDeleted = false;
494 // while (list.hasMoreElements()) {
495 // String dirName = (String) list.nextElement();
496 // File deleteFile = new File(dirName);
497 // try {
498 // delete(deleteFile);
499 // }
500 // catch (IOException ioe) { // ignore errors
501 // }
502 // if (!deleteFile.exists()) {
503 // String configName = pendingDeletionIndex.getProperty(dirName);
504 // pendingDeletionIndex.remove(dirName);
505 // dirDeleted = true;
506 // log.debug("Reaped configuration " + configName + " in directory " + dirName);
507 // }
508 // }
509 // // If we deleted any directories, persist the list of directories to disk...
510 // if (dirDeleted) {
511 // try {
512 // synchronized (pendingDeletionIndex) {
513 // saveDeleteIndex();
514 // }
515 // }
516 // catch (IOException ioe) {
517 // log.warn("Error saving " + DELETE_NAME + " file.", ioe);
518 // }
519 // }
520 // }
521 // }
522 //
523 public static final GBeanInfo GBEAN_INFO;
524
525 public static GBeanInfo getGBeanInfo() {
526 return GBEAN_INFO;
527 }
528
529 static {
530 GBeanInfoBuilder builder = GBeanInfoBuilder.createStatic(RepositoryConfigurationStore.class, "ConfigurationStore");
531 builder.addAttribute("kernel", Kernel.class, false);
532 builder.addAttribute("objectName", String.class, false);
533 builder.addReference("Repository", WritableListableRepository.class, "Repository");
534 builder.setConstructor(new String[]{"kernel", "objectName", "Repository"});
535 GBEAN_INFO = builder.getBeanInfo();
536 }
537 }