001 package com.hammurapi.config.bootstrap;
002
003 import java.io.BufferedReader;
004 import java.io.File;
005 import java.io.IOException;
006 import java.io.InputStream;
007 import java.io.InputStreamReader;
008 import java.net.MalformedURLException;
009 import java.net.URL;
010 import java.net.URLClassLoader;
011 import java.util.ArrayList;
012 import java.util.Collections;
013 import java.util.HashMap;
014 import java.util.List;
015 import java.util.Map;
016 import java.util.Properties;
017 import java.util.concurrent.Callable;
018 import java.util.concurrent.Executor;
019 import java.util.logging.Logger;
020
021 import com.hammurapi.config.bootstrap.TokenExpander.TokenSource;
022
023 /**
024 * This class creates configurator boot classloader, loads
025 * com.hammurapi.config.runtime.BootstrapFactoryImpl and delegates
026 * create() method invocations to the BootstrapFactoryImpl.
027 * @author Pavel Vlasov
028 *
029 */
030 public class Bootstrapper<T> implements BootstrapFactory<T> {
031 private static final Logger logger = Logger.getLogger(Bootstrapper.class.getName());
032
033 private static FactoryClosure<Object> factoryClosure;
034 private static SimpleThreadPool executor;
035
036 private BootstrapFactory<T> factory;
037
038 public Bootstrapper() throws ConfigurationException {
039 this(DEFAULT_BOOT_CLASS_PATH);
040 }
041
042 @SuppressWarnings("unchecked")
043 public Bootstrapper(URL[] bootClassPath) throws ConfigurationException {
044 try {
045 logger.fine("New bootstrapper, boot class path: ");
046 for (URL url: bootClassPath) {
047 logger.fine("\t"+url);
048 }
049 URLClassLoader classLoader = new URLClassLoader(bootClassPath);
050 Class<?> factoryClass = classLoader.loadClass("com.hammurapi.config.runtime.BootstrapFactoryImpl");
051 factory = (BootstrapFactory<T>) factoryClass.newInstance();
052 } catch (ClassNotFoundException e) {
053 throw new ConfigurationException(e);
054 } catch (InstantiationException e) {
055 throw new ConfigurationException(e);
056 } catch (IllegalAccessException e) {
057 throw new ConfigurationException(e);
058 }
059 }
060
061 public FactoryClosure<T> create(
062 String uri,
063 TokenSource tokens,
064 String[] profilePath,
065 Map<Class<?>, Iterable<?>> services,
066 Map<String, ?> bindings) throws ConfigurationException {
067 logger.fine("Creating a new object from "+uri);
068 return factory.create(uri, tokens, profilePath, services, bindings);
069 }
070
071 public static final URL[] DEFAULT_BOOT_CLASS_PATH;
072
073 static {
074 try {
075 DEFAULT_BOOT_CLASS_PATH = new URL[] {new URL("http://www.hammurapi.com/products/config/lib/com.hammurapi.config.jar")};
076 } catch (MalformedURLException e) {
077 throw new ExceptionInInitializerError(e);
078 }
079 }
080
081 /**
082 * Loads configuration resource and executes it. The resource root must implement Runnable.
083 * Usage java [java options] com.hammurapi.bootstrap.Bootstrapper [options] <configuration file url>
084 * Options:
085 * <UL>
086 * <LI>--help Prints help and exits.</LI>
087 * <LI>--codebase <url>[;<url>] Semicolon separated list of boot class path URL's.</LI>
088 * <LI>--interactive If this option is provided, unresolved token substitutions are requested from the user.</LI>
089 * <LI>--properties <url> URL to load token substitutions from. Loaded properties are backed by
090 * system properties.</LI>
091 * <LI>--profile <profile path> Slash-separated profile path.</LI>
092 * <LI>--threads <number of threads> Thread pool size.</LI>
093 * </UL>
094 *
095 * @param args
096 */
097 public static void main(String[] args) throws Exception {
098 if (args.length==0) {
099 printUsage();
100 System.exit(1);
101 }
102
103 for (String arg: args) {
104 if ("--help".equals(arg)) {
105 printUsage();
106 System.exit(1);
107 }
108 }
109
110 URL[] codebase = null;
111 final boolean[] interactive = {false};
112 List<String> propUrls = new ArrayList<String>();
113 String[] profile = null;
114 String configUrl = null;
115
116 for (int i=0; i<args.length; ++i) {
117 if ("--codebase".equals(args[i])) {
118 ++i;
119 String[] cbs=args[i].split(";");
120 codebase = new URL[cbs.length];
121 for (int j=0; j<cbs.length; ++j) {
122 codebase[j] = new URL(cbs[j]);
123 }
124 } else if ("--interactive".equals(args[i])) {
125 interactive[0] = true;
126 } else if ("--properties".equals(args[i])) {
127 ++i;
128 propUrls.add(args[i]);
129 } else if ("--profile".equals(args[i])) {
130 ++i;
131 profile=args[i].split("/");
132 } else if ("--threads".equals(args[i])) {
133 ++i;
134 executor = new SimpleThreadPool(Integer.parseInt(args[i]));
135 } else {
136 if (configUrl==null) {
137 configUrl=args[i];
138 } else {
139 System.err.println("Invalid command line parameters - more than one configuration URL.");
140 System.exit(1);
141 }
142 }
143 }
144
145 if (configUrl==null) {
146 System.err.println("Invalid command line parameters - missing configuration URL.");
147 System.exit(1);
148 }
149
150 final Properties properties = new Properties(System.getProperties());
151 File file = new File(configUrl);
152 URL pBase = file.isFile() ? file.toURI().toURL() : new URL(configUrl);
153 for (String pu:propUrls) {
154 URL pUrl = new URL(pBase, pu);
155 InputStream in = pUrl.openStream();
156 try {
157 properties.load(in);
158 } finally {
159 in.close();
160 }
161 }
162
163 Bootstrapper<Object> factory = codebase==null ? new Bootstrapper<Object>() : new Bootstrapper<Object>(codebase);
164 final BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
165 TokenSource tokens = new TokenSource() {
166
167 public String getToken(String name) {
168 String ret = properties.getProperty(name);
169 if (ret==null && interactive[0]) {
170 try {
171 System.out.print("Enter value for property "+name+": ");
172 ret = br.readLine();
173 properties.setProperty(name, ret);
174 } catch (IOException e) {
175 e.printStackTrace();
176 }
177 }
178 return ret;
179 }
180
181 };
182
183 Map<Class<?>, Iterable<?>> services = null;
184 if (executor!=null) {
185 services = new HashMap<Class<?>, Iterable<?>>();
186 List<SimpleThreadPool> singletonList = Collections.singletonList(executor);
187 services.put(Executor.class, singletonList);
188 }
189 factoryClosure = factory.create(configUrl, tokens, profile, services, null);
190 Object result = factoryClosure.create();
191 try {
192 if (result instanceof Runnable) {
193 ((Runnable) result).run();
194 } else if (result instanceof Callable<?>) {
195 ((Callable<?>) result).call();
196 } else {
197 throw new IllegalArgumentException(result+" is not Runnable or Callable");
198 }
199 } finally {
200 Destroyable toDestroy=factoryClosure.getDestroyable();
201 if (toDestroy!=null) {
202 toDestroy.destroy();
203 }
204 factoryClosure=null;
205
206 if (executor!=null) {
207 executor.destroy();
208 }
209 }
210 }
211
212 /**
213 * For NT service. Retries startup in the case of exceptions.
214 * @param args
215 * @throws InterruptedException
216 * @throws Exception
217 */
218 public static void start(String[] args) throws InterruptedException {
219 do {
220 try {
221 main(args);
222 System.exit(0);
223 } catch (InterruptedException e) {
224 System.exit(1);
225 } catch (Exception e) {
226 e.printStackTrace();
227 Thread.sleep(10000);
228 }
229 } while (true);
230 }
231
232 /**
233 * For NT service wrapper.
234 * @param args
235 * @throws ConfigurationException
236 */
237 public static void stop(String[] args) throws ConfigurationException {
238 if (factoryClosure!=null) {
239 Destroyable toDestroy=factoryClosure.getDestroyable();
240 if (toDestroy!=null) {
241 toDestroy.destroy();
242 }
243 factoryClosure = null;
244
245 if (executor!=null) {
246 executor.destroy();
247 }
248 }
249 }
250
251 private static void printUsage() {
252 System.out.println("Usage java [java options] com.hammurapi.bootstrap.Bootstrapper [options] <configuration file url>");
253 System.out.println("Options:");
254 System.out.println("\t--help Prints help and exits.");
255 System.out.println("\t--codebase <url>[;<url>] Semicolon separated list of boot class path URL's.");
256 System.out.println("\t--interactive If this option is provided, unresolved\n\t\ttoken substitutions are requested from the user.");
257 System.out.println("\t--properties <file> URL to load token substitutions from. \n\t\tLoaded properties are backed by system properties.");
258 System.out.println("\t--profile <profile path> Slash-separated profile path.");
259 System.out.println("\t--threads <profile path> Thread pool size.");
260 }
261 }