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.Collections; 031 import java.util.jar.JarFile; 032 import java.util.zip.ZipEntry; 033 import java.util.zip.ZipOutputStream; 034 import javax.management.ObjectName; 035 036 import org.apache.geronimo.gbean.AbstractName; 037 import org.apache.geronimo.gbean.GBeanInfo; 038 import org.apache.geronimo.gbean.GBeanInfoBuilder; 039 import org.apache.geronimo.kernel.Kernel; 040 import org.apache.geronimo.kernel.ObjectNameUtil; 041 import org.apache.geronimo.kernel.config.ConfigurationAlreadyExistsException; 042 import org.apache.geronimo.kernel.config.ConfigurationData; 043 import org.apache.geronimo.kernel.config.ConfigurationInfo; 044 import org.apache.geronimo.kernel.config.ConfigurationStore; 045 import org.apache.geronimo.kernel.config.ConfigurationUtil; 046 import org.apache.geronimo.kernel.config.InvalidConfigException; 047 import org.apache.geronimo.kernel.config.NoSuchConfigException; 048 import org.apache.geronimo.kernel.config.IOUtil; 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: 544586 $ $Date: 2007-06-05 14:26:52 -0400 (Tue, 05 Jun 2007) $ 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(path == null) { 205 return Collections.singleton(location.toURL()); 206 } else { 207 if (location.isDirectory()) { 208 Set matches = IOUtil.search(location, path); 209 return matches; 210 } else { 211 Set matches = IOUtil.search(location, path); 212 return matches; 213 } 214 } 215 } else { 216 Set matches = IOUtil.search(location, moduleName + "/" +path); 217 return matches; 218 } 219 } 220 221 public void exportConfiguration(Artifact configId, OutputStream output) throws IOException, NoSuchConfigException { 222 if(!configId.isResolved()) { 223 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved"); 224 } 225 File dir = repository.getLocation(configId); 226 if (dir == null) { 227 throw new NoSuchConfigException(configId); 228 } 229 if (existsReadable(dir)) { 230 throw new IOException("Cannot read config store directory for " + configId + " (" + dir.getAbsolutePath() + ")"); 231 } 232 ZipOutputStream out = new ZipOutputStream(output); 233 byte[] buf = new byte[10240]; 234 writeToZip(dir, out, "", buf); 235 if (inPlaceConfUtil.isInPlaceConfiguration(dir)) { 236 dir = inPlaceConfUtil.readInPlaceLocation(dir); 237 writeToZip(dir, out, "", buf); 238 } 239 out.closeEntry(); 240 out.finish(); 241 out.flush(); 242 } 243 244 private void writeToZip(File dir, ZipOutputStream out, String prefix, byte[] buf) throws IOException { 245 File[] all = dir.listFiles(); 246 for (int i = 0; i < all.length; i++) { 247 File file = all[i]; 248 if (file.isDirectory()) { 249 writeToZip(file, out, prefix + file.getName() + "/", buf); 250 } else { 251 ZipEntry entry = new ZipEntry(prefix + file.getName()); 252 out.putNextEntry(entry); 253 writeToZipStream(file, out, buf); 254 } 255 } 256 } 257 258 private void writeToZipStream(File file, OutputStream out, byte[] buf) throws IOException { 259 FileInputStream in = new FileInputStream(file); 260 int count; 261 try { 262 while ((count = in.read(buf, 0, buf.length)) > -1) { 263 out.write(buf, 0, count); 264 } 265 } finally { 266 in.close(); 267 } 268 } 269 270 public void install(InputStream in, int size, Artifact configId, FileWriteMonitor fileWriteMonitor) throws IOException { 271 try { 272 repository.copyToRepository(in, size, configId, fileWriteMonitor); 273 } catch (IOException e) { 274 throw e; 275 } finally { 276 IOUtil.close(in); 277 } 278 } 279 280 public boolean isInPlaceConfiguration(Artifact configId) throws NoSuchConfigException, IOException { 281 if(!configId.isResolved()) { 282 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved"); 283 } 284 File location = repository.getLocation(configId); 285 if (location.isDirectory()) { 286 return inPlaceConfUtil.isInPlaceConfiguration(location); 287 } else { 288 return false; 289 } 290 } 291 292 public void install(ConfigurationData configurationData) throws IOException, InvalidConfigException { 293 // determine the source file/dir 294 File source = configurationData.getConfigurationDir(); 295 if (!source.exists()) { 296 throw new InvalidConfigException("Source does not exist " + source); 297 } else if (!source.canRead()) { 298 throw new InvalidConfigException("Source is not readable " + source); 299 } 300 301 // determine the target location 302 Artifact configId = configurationData.getId(); 303 File destination = repository.getLocation(configId); 304 305 // if directory in the correct place -- noop 306 if (!source.equals(destination)) { 307 if (destination.exists()) { 308 throw new ConfigurationAlreadyExistsException(configId.toString()); 309 } 310 311 if (source.isFile()) { 312 // Assume this is a jar file 313 // copy it into the repository; repository should unpack it 314 repository.copyToRepository(source, configId, null); 315 } else if (source.isDirectory()) { 316 // directory is in wrong place -- directory copy 317 IOUtil.recursiveCopy(source, destination); 318 } else { 319 throw new InvalidConfigException("Unable to install configuration from " + source); 320 } 321 } 322 323 ExecutableConfigurationUtil.writeConfiguration(configurationData, destination); 324 325 // write in-place configuration config file, if need be. 326 inPlaceConfUtil.writeInPlaceLocation(configurationData, destination); 327 } 328 329 public void uninstall(Artifact configId) throws NoSuchConfigException, IOException { 330 if(!configId.isResolved()) { 331 throw new IllegalArgumentException("Artifact "+configId+" is not fully resolved"); 332 } 333 ConfigurationInfo configurationInfo = null; 334 try { 335 configurationInfo = loadConfigurationInfo(configId); 336 } catch (IOException e) { 337 // don't really care 338 } 339 File location = repository.getLocation(configId); 340 IOUtil.recursiveDelete(location); 341 // Number of directory levels up, to check and delete empty parent directories in the repo 342 int dirDepth = 0; 343 344 // FIXME: Determine the repository type 345 // For now assume the repo is a Maven2Repository. This should not cause any harm even if it is an 346 // Maven1Repository, for it would be deleting the 'repository' directory if it happens to be empty. 347 boolean m2repo = true; 348 if(m2repo) { 349 // Check version, artifact and group directories, i.e. 3 levels up 350 dirDepth = 3; 351 } 352 353 File temp = location; 354 for(int i = 0; i < dirDepth; ++i) { 355 if((temp = temp.getParentFile()).listFiles().length == 0) { 356 // Directory is empty. Remove it. 357 temp.delete(); 358 } else { 359 // Directory is not empty. No need to check any more parent directories 360 break; 361 } 362 } 363 364 if (configurationInfo != null) { 365 IOException ioException = null; 366 for (Iterator iterator = configurationInfo.getOwnedConfigurations().iterator(); iterator.hasNext();) { 367 Artifact ownedConfiguration = (Artifact) iterator.next(); 368 try { 369 uninstall(ownedConfiguration); 370 } catch (NoSuchConfigException e) { 371 // ignored - already deleted or never installed 372 } catch (IOException e) { 373 if (ioException != null) { 374 ioException = e; 375 } 376 } 377 if (ioException != null) { 378 throw ioException; 379 } 380 } 381 } 382 } 383 384 public List listConfigurations() { 385 SortedSet artifacts = repository.list(); 386 387 List configs; 388 synchronized (this) { 389 configs = new ArrayList(); 390 for (Iterator i = artifacts.iterator(); i.hasNext();) { 391 Artifact configId = (Artifact) i.next(); 392 File dir = repository.getLocation(configId); 393 File meta = new File(dir, "META-INF"); 394 if(!meta.isDirectory() || !meta.canRead()) { 395 continue; 396 } 397 File ser = new File(meta, "config.ser"); 398 if(!ser.isFile() || !ser.canRead() || ser.length() == 0) { 399 continue; 400 } 401 try { 402 ConfigurationInfo configurationInfo = loadConfigurationInfo(configId); 403 configs.add(configurationInfo); 404 } catch (NoSuchConfigException e) { 405 log.error("Unexpected error: found META-INF/config.ser for "+configId+" but couldn't load ConfigurationInfo", e); 406 } catch (IOException e) { 407 log.error("Unable to load ConfigurationInfo for "+configId, e); 408 } 409 } 410 } 411 return configs; 412 } 413 414 private ConfigurationInfo loadConfigurationInfo(Artifact configId) throws NoSuchConfigException, IOException { 415 File location = repository.getLocation(configId); 416 417 if (!location.exists() && !location.canRead()) { 418 throw new NoSuchConfigException(configId); 419 } 420 421 File inPlaceLocation = inPlaceConfUtil.readInPlaceLocation(location); 422 423 ConfigurationInfo configurationInfo; 424 if (location.isDirectory()) { 425 File infoFile = new File(location, "META-INF"); 426 infoFile = new File(infoFile, "config.info"); 427 428 InputStream in = new FileInputStream(infoFile); 429 try { 430 configurationInfo = ConfigurationUtil.readConfigurationInfo(in, getAbstractName(), inPlaceLocation); 431 } finally { 432 IOUtil.close(in); 433 } 434 } else { 435 JarFile jarFile = new JarFile(location); 436 InputStream in = null; 437 try { 438 ZipEntry entry = jarFile.getEntry("META-INF/config.info"); 439 in = jarFile.getInputStream(entry); 440 configurationInfo = ConfigurationUtil.readConfigurationInfo(in, getAbstractName(), inPlaceLocation); 441 } finally { 442 IOUtil.close(in); 443 IOUtil.close(jarFile); 444 } 445 } 446 447 return configurationInfo; 448 } 449 450 // /** 451 // * Thread to cleanup unused Config Store entries. 452 // * On Windows, open files can't be deleted. Until MultiParentClassLoaders 453 // * are GC'ed, we won't be able to delete Config Store directories/files. 454 // */ 455 // class ConfigStoreReaper implements Runnable { 456 // private final int reaperInterval; 457 // private volatile boolean done = false; 458 // 459 // public ConfigStoreReaper(int reaperInterval) { 460 // this.reaperInterval = reaperInterval; 461 // } 462 // 463 // public void close() { 464 // this.done = true; 465 // } 466 // 467 // public void run() { 468 // log.debug("ConfigStoreReaper started"); 469 // while (!done) { 470 // try { 471 // Thread.sleep(reaperInterval); 472 // } catch (InterruptedException e) { 473 // continue; 474 // } 475 // reap(); 476 // } 477 // } 478 // 479 // /** 480 // * For every directory in the pendingDeletionIndex, attempt to delete all 481 // * sub-directories and files. 482 // */ 483 // public void reap() { 484 // // return, if there's nothing to do 485 // if (pendingDeletionIndex.size() == 0) 486 // return; 487 // // Otherwise, attempt to delete all of the directories 488 // Enumeration list = pendingDeletionIndex.propertyNames(); 489 // boolean dirDeleted = false; 490 // while (list.hasMoreElements()) { 491 // String dirName = (String) list.nextElement(); 492 // File deleteFile = new File(dirName); 493 // try { 494 // delete(deleteFile); 495 // } 496 // catch (IOException ioe) { // ignore errors 497 // } 498 // if (!deleteFile.exists()) { 499 // String configName = pendingDeletionIndex.getProperty(dirName); 500 // pendingDeletionIndex.remove(dirName); 501 // dirDeleted = true; 502 // log.debug("Reaped configuration " + configName + " in directory " + dirName); 503 // } 504 // } 505 // // If we deleted any directories, persist the list of directories to disk... 506 // if (dirDeleted) { 507 // try { 508 // synchronized (pendingDeletionIndex) { 509 // saveDeleteIndex(); 510 // } 511 // } 512 // catch (IOException ioe) { 513 // log.warn("Error saving " + DELETE_NAME + " file.", ioe); 514 // } 515 // } 516 // } 517 // } 518 // 519 public static final GBeanInfo GBEAN_INFO; 520 521 public static GBeanInfo getGBeanInfo() { 522 return GBEAN_INFO; 523 } 524 525 static { 526 GBeanInfoBuilder builder = GBeanInfoBuilder.createStatic(RepositoryConfigurationStore.class, "ConfigurationStore"); 527 builder.addAttribute("kernel", Kernel.class, false); 528 builder.addAttribute("objectName", String.class, false); 529 builder.addReference("Repository", WritableListableRepository.class, "Repository"); 530 builder.setConstructor(new String[]{"kernel", "objectName", "Repository"}); 531 GBEAN_INFO = builder.getBeanInfo(); 532 } 533 }