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 }