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] &lt;configuration file url&gt;
084             * Options:
085             * <UL>
086             * <LI>--help Prints help and exits.</LI>
087             * <LI>--codebase &lt;url&gt;[;&lt;url&gt;] 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 &lt;url&gt; URL to load token substitutions from. Loaded properties are backed by 
090             * system properties.</LI>
091             * <LI>--profile &lt;profile path&gt; Slash-separated profile path.</LI>
092             * <LI>--threads &lt;number of threads&gt; 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    }