1 /*
2 * Copyright 2007-2008 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package sun.module.repository;
27
28 import java.io.BufferedInputStream;
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.IOException;
32 import java.io.PrintWriter;
33 import java.io.StringWriter;
34 import java.lang.reflect.Constructor;
35 import java.lang.reflect.InvocationTargetException;
36 import java.module.Modules;
37 import java.module.Repository;
38 import java.net.MalformedURLException;
39 import java.net.URI;
40 import java.net.URL;
41 import java.net.URLClassLoader;
42 import java.net.URISyntaxException;
43 import java.security.AccessController;
44 import java.security.PrivilegedAction;
45 import java.util.HashMap;
46 import java.util.LinkedHashMap;
47 import java.util.Map;
48 import java.util.Properties;
49 import sun.module.JamUtils;
50 import sun.module.config.ModuleSystemConfig;
51 import sun.security.util.PropertyExpander;
52
53 /**
54 * Establishes the configuration of a set of repositories in a running JVM. A
55 * configuration specifies a list of repositories, one of which is a child of
56 * the bootstrap repository and the others are successive children.
57 * <p>
58 * Repositories can be configured automatically via configuration files, or by
59 * setting a system property to the name of a configuration file.
60 * <p>
61 * The initial call to {@code getApplicationRepository} causes repositories to be
62 * configured via a configuration file, with the system repository set to the
63 * configured repository that is most distant (in the parent-child distance of
64 * repository instances).
65 * @since 1.7
66 */
67 public final class RepositoryConfig
68 {
69
70 static {
71 // Setup "repository.system.home" system property if it doesn't exist
72 AccessController.doPrivileged(new PrivilegedAction<Void>() {
73 public Void run() {
74 if (System.getProperty("repository.system.home") == null) {
75 String platform = System.getProperty("os.platform");
76 String repositorySystemHome = null;
77 if (platform.equalsIgnoreCase("windows")) {
78 repositorySystemHome = "C:\\Windows\\Sun\\Java";
79 } else if (platform.equalsIgnoreCase("solaris")) {
80 repositorySystemHome = "/usr/java/packages";
81 } else if (platform.equalsIgnoreCase("linux")) {
82 repositorySystemHome = "/usr/jdk/packages";
83 }
84 if (repositorySystemHome != null) {
85 System.setProperty("repository.system.home", repositorySystemHome);
86 }
87 }
88 return null;
89 }
90 });
91 }
92
93 /** Application's repository. */
94 private static Repository applicationRepository = Repository.getBootstrapRepository();
95
96 /**
97 * True if setApplicationRepository ever completes normally: this allows for
98 * it being changed at most once from its default.
99 */
100 private static boolean applicationRepositoryWasSet = false;
101
102 /** True once configRepositories has completed. */
103 private static boolean configDone = false;
104
105 /** True once the repositories are all initialized. */
106 private static boolean initialized = false;
107
108 /** System repository in the configuration. */
109 private static Repository systemRepository;
110
111 /** Attribute which designates the parent of a repository. */
112 private static final String parentAttr = "parent";
113
114 /**Attribute which designates the source location of a repository. */
115 private static final String sourceAttr = "source";
116
117 /**
118 * Optional attribute which designates the class name of a repository.
119 * Note: only one of "classname" or "factoryname" can be used for
120 * configuring a particular repository.
121 */
122 private static final String classAttr = "classname";
123
124 /**
125 * Optional attribute which designates the class name of a factory that
126 * can create a repository. Note: only one of "classname" or "factoryname"
127 * can be used for configuring a particular repository.
128 */
129 private static final String factoryAttr = "factoryname";
130
131 /**
132 * Value of the parentAttr which specifies the repository that is to be
133 * created as the imediate child of the bootstrap repository.
134 */
135 private static final String bootstrapValue = "bootstrap";
136
137 /** Cache of RepositoryFactory instances. */
138 private static final Map<String, RepositoryFactory> factories =
139 new HashMap<String, RepositoryFactory>();
140
141 /**
142 * Sets the application repository.
143 * @param r {@code Repository} that will be the application repository
144 * @throws IllegalArgumentException if the application repository has already
145 * been set via this method.
146 */
147 public static void setApplicationRepository(Repository r) throws IllegalArgumentException {
148 if (applicationRepositoryWasSet) {
149 throw new IllegalArgumentException("Application repository is already set.");
150 } else {
151 applicationRepository = r;
152 applicationRepositoryWasSet = true;
153 }
154 }
155
156 /**
157 * Returns the current application repository, or if one has not been set (for
158 * example, by the ModuleLauncher), creates it first.
159 *
160 * @param initializeAll initialize all repositories
161 * @return the application repository
162 */
163 public static synchronized Repository getApplicationRepository(boolean initializeAll) {
164 if (!configDone) {
165 applicationRepository = configRepositories();
166 }
167 // XXX: Will revisit the bootstrapping issue
168 //
169 // During the VM startup, we can only initialize the repositories
170 // provided by rt.jar (loaded by bootstrap class loader).
171 // This method may be called when initializing the application class
172 // loader. Loading and initialize the OSGi and user-defined repositories
173 // (classes not loaded by bootstrap class loader) may result
174 // in a deadlock if loaded by the application class loader.
175 //
176 if (initializeAll && !initialized) {
177 initialized = true;
178 for (Repository r = applicationRepository; r != null; r = r.getParent()) {
179 try {
180 if (!r.isActive()) {
181 r.initialize();
182 }
183 } catch (IOException e) {
184 throw new AssertionError(e);
185 }
186 }
187 }
188 return applicationRepository;
189 }
190
191 public static synchronized Repository getApplicationRepository() {
192 return getApplicationRepository(true);
193 }
194
195 /**
196 * Returns the repository specified by the repository configuration which
197 * has the largest number of repositories between itself and the bootstrap
198 * repository.
199 * @return the repository configured to be farthest from the bootstrap
200 * repository.
201 */
202 public static synchronized Repository getSystemRepository() {
203 getApplicationRepository(); // Force initialization
204 return systemRepository;
205 }
206
207 /**
208 * Creates repositories as described in the repository.properties file.
209 * The format of the property file contents is described in {@link
210 * #configRepositories(Properties configProps)}.
211 * @return The repository in the configuration that is furthest in the
212 * list from the bootstrap repository
213 * @throws IllegalStateException if configuration has already been done
214 * @throws RuntimeException if repositories cannot be configured
215 */
216 private static Repository configRepositories() throws RuntimeException {
217 if (configDone) {
218 throw new IllegalStateException("Repositories have already been configured.");
219 }
220
221 String location = ModuleSystemConfig.getRepositoryPropertiesFileName();
222 Properties rp = new Properties();
223 try {
224 File f = new File(PropertyExpander.expand(location));
225 if (f.exists() && f.canRead()) {
226 BufferedInputStream is = null;
227 try {
228 is = new BufferedInputStream(new FileInputStream(f));
229 rp.load(is);
230 } finally {
231 JamUtils.close(is);
232 }
233 } else {
234 throw new RuntimeException(
235 "Cannot load repository properties from file " + f.getAbsolutePath());
236 }
237 } catch (PropertyExpander.ExpandException ex) {
238 throw new RuntimeException(ex);
239 } catch (IOException ex) {
240 throw new RuntimeException(ex);
241 }
242
243 return configRepositories(rp);
244 }
245
246 /**
247 * Creates repositories as described in the given properties. Each
248 * property has the form:
249 * <pre>
250 * repositoryName . attribute = value
251 * </pre>
252 * For each named repository, there can be several attribute/value pairs.
253 * One and only one named repository must have the attribute
254 * "parent" with the value "bootstrap": this indicates which repository
255 * is to be created as the immediate child of the bootstrap repository.
256 * Each other named repository must have a parent, and these must describe
257 * a list of repositories.
258 * <p>
259 * Each repository must provide a attribute "source" which
260 * indicates the source location of the repository.
261 * <p>
262 * Each repository can optionally provide a "classname" attribute, with a
263 * corresponding value that indicates the name of the Class of the
264 * repository, or a "factoryname" attribute, which indicates the name of
265 * a Class that can create a Repository.
266 * <p>
267 * If classname is given, then an instance of that named class will be
268 * created. The repository class must have a constructor which has a
269 * signature like this:
270 * <pre>
271 * FooRepository(Repository parent, String name, URL source, Map<String, String> config) throws IOException;
272 * </pre>
273 * <p>
274 * If factoryname is given, an instance of that will be created (but only
275 * once per JVM) and its {@code create} method used to create a repository
276 * instance. See {@link RepositoryFactory}.
277 * <p>
278 * If neither classname nor factoryname is given, an appropriate
279 * repository will be created based on the source. If source
280 * is a file-based URL or a filename, a LocalRepository will be created.
281 * If it is a non-file-based URL, a URLRepository will be created.
282 * <p>
283 * Other attributes are allowed, and are particular to the class of
284 * repository that is created. They are passed to the repository's
285 * constructor in it's {@code Map<String, String> config} parameter.
286 * @param configProps Properties that configure a list of repositories
287 * @return The repository in the configuration that is furthest in the
288 * list from the bootstrap repository.
289 * @throws IllegalStateException if repositories have already been
290 * configured.
291 */
292 private static Repository configRepositories(Properties configProps) {
293 if (configDone) {
294 throw new IllegalStateException("Repositories have already been configured.");
295 }
296
297 if (!configProps.isEmpty()) {
298 LinkedHashMap<String, Map<String, String>> orderedConfig =
299 getConfigFromProps(configProps);
300 systemRepository = createRepositories(orderedConfig);
301 configDone = true;
302 }
303 return systemRepository;
304 }
305
306 /**
307 * Examine the given properties to create a Map from repository name to
308 * (repository property, repository value). The returned Map is a {@code
309 * LinkedHashMap} ordered such that the bootstrap repository is first.
310 */
311 private static LinkedHashMap<String, Map<String, String>> getConfigFromProps(
312 Properties configProps) {
313 Map<String, Map<String, String>> config
314 = new HashMap<String, Map<String, String>>();
315 boolean foundBootstrap = false;
316
317 for (String name : configProps.stringPropertyNames()) {
318 int dotPos = name.indexOf('.');
319 String repoName = name.substring(0, dotPos);
320 String repoKey = name.substring(dotPos +1);
321 String repoValue = configProps.getProperty(name);
322 try {
323 repoValue = PropertyExpander.expand(repoValue);
324 } catch (PropertyExpander.ExpandException ex) {
325 throw new IllegalArgumentException(
326 "Invalid property value in repository configuration: '"
327 + repoValue + "'");
328 }
329 Map<String, String> repoConfig = config.get(repoName);
330 if (repoConfig == null) {
331 repoConfig = new HashMap<String, String>();
332 config.put(repoName, repoConfig);
333 }
334 repoConfig.put(repoKey, repoValue);
335 if (repoKey.equals(parentAttr) && repoValue.equals(bootstrapValue)) {
336 foundBootstrap = true;
337 }
338 }
339
340 if (!foundBootstrap) {
341 throw new IllegalArgumentException(
342 "RepositoryConfig: No repository specified as child of bootstrap repository");
343 }
344
345 /*
346 * Sort config into orderedConfig, with bootstrap repository first,
347 * then its child, then its grandchild, and so on.
348 */
349 LinkedHashMap<String, Map<String, String>> orderedConfig =
350 new LinkedHashMap<String, Map<String, String>>();
351 int size = config.size();
352 String parentName = bootstrapValue;
353 while (orderedConfig.size() < size) {
354 boolean repoFound = false;
355 for (String repoName : config.keySet()) {
356 Map<String, String> repoConfig = config.get(repoName);
357 String repoParent = repoConfig.get(parentAttr);
358
359 if (repoParent == null) {
360 throw new RuntimeException(
361 "Invalid repository configuration: no parent specified by repository '"
362 + repoName + "'");
363 }
364
365 if (!repoParent.equals(bootstrapValue) && config.get(repoParent) == null) {
366 throw new RuntimeException(
367 "Invalid repository configuration: missing parent specified for repository '"
368 + repoName + "'");
369 }
370
371 if (parentName.equals(repoParent)) {
372 orderedConfig.put(repoName, repoConfig);
373 parentName = repoName;
374 repoFound = true;
375 break;
376 }
377 }
378 if (!repoFound) {
379 throw new RuntimeException(
380 "Invalid repository configuration: no child repository found for parent repository '"
381 + parentName + "'");
382 }
383 }
384 return orderedConfig;
385 }
386
387 /**
388 * RepositoryCreator provides for creating instances of Repository
389 * subclasses. Each of its subclasses provides a different way of
390 * specifying how to create a Repository.
391 */
392 private abstract static class RepositoryCreator {
393 protected abstract Repository create(Repository parent, String repoName,
394 String sourceName, Map<String, String> config)
395 throws IOException, InstantiationException, NoSuchMethodException,
396 ClassNotFoundException, IllegalAccessException,
397 InvocationTargetException, URISyntaxException;
398 }
399
400 /**
401 * Creates a Local or URL repository, depending on whether the
402 * source is a file or other URL .
403 */
404 private static class DefaultCreator extends RepositoryCreator {
405 static final RepositoryCreator instance = new DefaultCreator();
406 private DefaultCreator() { }
407
408 protected Repository create(Repository parent, String repoName,
409 String sourceName, Map<String, String> config)
410 throws IOException, InstantiationException, NoSuchMethodException,
411 ClassNotFoundException, IllegalAccessException,
412 InvocationTargetException {
413 Repository rc = null;
414 try {
415 URL u = new URL(sourceName);
416 if ("file".equals(u.getProtocol())) {
417 File f = new File(u.getFile()).getCanonicalFile();
418 rc = Modules.newLocalRepository(repoName, f, config, parent);
419 } else {
420 rc = Modules.newURLRepository(repoName, u, config, parent);
421 }
422 } catch (MalformedURLException ex) {
423 File f = new File(sourceName).getCanonicalFile();
424 rc = Modules.newLocalRepository(repoName, f, config, parent);
425 }
426 return rc;
427 }
428 }
429
430 /**
431 * Creates a repository given the name of its class.
432 */
433 private static class ClassBasedCreator extends RepositoryCreator {
434 static final ClassBasedCreator instance = new ClassBasedCreator();
435 private ClassBasedCreator() { }
436
437 /** Name of Repository subclass to create. */
438 private String className;
439
440 void setClassName(String name) {
441 className = name;
442 }
443
444 protected Repository create(Repository parent, String repoName,
445 String sourceName, Map<String, String> config)
446 throws IOException, InstantiationException, NoSuchMethodException,
447 ClassNotFoundException, IllegalAccessException,
448 InvocationTargetException, URISyntaxException {
449 URI u = sourceName == null ? null : new URI(sourceName);
450 Class<?> clazz = Class.forName(className);
451 Constructor ctor = clazz.getDeclaredConstructor(
452 String.class, URI.class, Map.class,
453 Repository.class);
454 return (Repository) ctor.newInstance(
455 repoName, u, config, parent);
456 }
457 }
458
459 /**
460 * Creates a repository given the name of a factory that can create it.
461 */
462 private static class FactoryBasedCreator extends RepositoryCreator {
463 static final FactoryBasedCreator instance = new FactoryBasedCreator();
464 private FactoryBasedCreator() { }
465
466 /** Name of RepositoryFactory class to create Repository subclass. */
467 private String factoryName;
468
469 void setFactoryName(String name) {
470 factoryName = name;
471 }
472
473 protected synchronized Repository create(Repository parent, String repoName,
474 String sourceName, Map<String, String> config)
475 throws IOException, InstantiationException, NoSuchMethodException,
476 ClassNotFoundException, IllegalAccessException,
477 InvocationTargetException, URISyntaxException {
478 URL u = sourceName == null ? null : new URL(sourceName);
479 RepositoryFactory rf = factories.get(factoryName);
480 if (rf == null) {
481 Class<?> clazz = Class.forName(factoryName);
482 Constructor ctor = clazz.getDeclaredConstructor();
483 rf = (RepositoryFactory) ctor.newInstance();
484 factories.put(factoryName, rf);
485 }
486 return rf.create(parent, repoName, u, config);
487 }
488 }
489
490 /**
491 * Creates an OSGi repository
492 */
493 private static class OSGiRepositoryCreator extends RepositoryCreator {
494 static final OSGiRepositoryCreator instance = new OSGiRepositoryCreator();
495 private OSGiRepositoryCreator() { }
496
497 /** Name of Repository subclass to create. */
498 private String className = "sun.module.osgi.OSGiRepository";
499 private final String repoJar = "file:///${java.home}/lib/osgi-repo.jar";
500 private final String containerAttr = "container";
501
502 void setClassName(String name) {
503 className = name;
504 }
505 URLClassLoader getURLClassLoader(File container) throws IOException {
506 URL[] urls = new URL[2];
507 try {
508 urls[0] = new URL(PropertyExpander.expand(repoJar));
509 urls[1] = container.toURI().toURL();
510 } catch (PropertyExpander.ExpandException ex) {
511 // XXX: should not reach here
512 ex.printStackTrace();
513 }
514 return URLClassLoader.newInstance(urls);
515 }
516
517 protected Repository create(Repository parent, String repoName,
518 String sourceName, Map<String, String> config)
519 throws IOException, InstantiationException, NoSuchMethodException,
520 ClassNotFoundException, IllegalAccessException,
521 InvocationTargetException, URISyntaxException {
522 URI u = sourceName == null ? null : new URI(sourceName);
523 String containerPath = config.get(containerAttr);
524 File container;
525 try {
526 container = new File(new URI(containerPath));
527 } catch (URISyntaxException e) {
528 container = new File(containerPath);
529 }
530 if (!container.exists()) {
531 throw new IOException("OSGi container \"" + container +
532 "\" does not exist");
533 }
534
535 Class<?> clazz = Class.forName(className, true, getURLClassLoader(container));
536 Constructor ctor = clazz.getDeclaredConstructor(
537 String.class, URI.class, Repository.class, Map.class);
538 return (Repository) ctor.newInstance(repoName, u, parent, config);
539 }
540 }
541
542 /**
543 * Creates repositories based on the given {@code LinkedHashMap}, whose
544 * keys are presumed to be ordered such that the first entry describes the
545 * repository to create as a child of the bootstrap repository, the next
546 * to create as a child of that, and so on.
547 * @return The repository in the configuration that is furthest in the
548 * list from the bootstrap repository.
549 */
550 private synchronized static Repository createRepositories(
551 LinkedHashMap<String, Map<String, String>> orderedConfig) {
552
553 Repository parentRepo = Repository.getBootstrapRepository();
554 for (String repoName : orderedConfig.keySet()) {
555 Map<String, String> repoConfig = orderedConfig.get(repoName);
556
557 String sourceName = repoConfig.get(sourceAttr);
558
559 sourceName = sourceName.replace('\\', '/');
560
561 Repository repo = null;
562
563 String clazzName = repoConfig.get(classAttr);
564 String factoryName = repoConfig.get(factoryAttr);
565 if (clazzName != null && factoryName != null) {
566 throw new IllegalArgumentException(
567 "Cannot specify both classname and factoryname for '" + repoName);
568 }
569
570 RepositoryCreator creator = DefaultCreator.instance;
571 if (repoName.equals("osgi")) {
572 creator = OSGiRepositoryCreator.instance;
573 } else {
574 if (clazzName != null || factoryName != null) {
575 if (clazzName != null) {
576 ClassBasedCreator.instance.setClassName(clazzName);
577 creator = ClassBasedCreator.instance;
578 } else {
579 FactoryBasedCreator.instance.setFactoryName(factoryName);
580 creator = FactoryBasedCreator.instance;
581 }
582 }
583 }
584
585 try {
586 repo = creator.create(parentRepo, repoName, sourceName, repoConfig);
587 } catch (Error ex) {
588 throw ex;
589 } catch (RuntimeException ex) {
590 throw ex;
591 } catch (InvocationTargetException ex) {
592 String msg = ex.getMessage();
593 Throwable t = ex.getCause();
594 if (t != null) {
595 String s = t.getMessage();
596 if (s != null) {
597 msg += "\nCause: " + t.getMessage();
598 }
599 StringWriter sw = new StringWriter();
600 PrintWriter pw = new PrintWriter(sw);
601 t.printStackTrace(pw);
602 pw.close();
603 msg += "\n" + sw.toString();
604 }
605 throw new IllegalArgumentException(
606 "Cannot create repository named '" + repoName + "' at location '" + sourceName
607 + "': " + msg);
608 } catch (Exception ex) {
609 throw new IllegalArgumentException(
610 "Cannot create repository named '" + repoName + "' at location '" + sourceName
611 + "': " + ex, ex);
612 }
613
614 /*
615 * If a repository was created, make it the parent for the next
616 * one created (if any). If a repository was *not* created, then
617 * don't change the current parent.
618 */
619 if (repo != null) {
620 parentRepo = repo;
621 }
622 }
623 return parentRepo;
624 }
625 }