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