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