View Javadoc

1   /**
2    * Copyright 2005-2006 the original author or authors.
3    *
4    * Licensed under the Gnu General Pubic License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with
6    * the License. You may obtain a copy of the License at
7    *
8    *      http://www.opensource.org/licenses/gpl-license.php
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13   * See the Gnu General Public License for more details.
14   */
15  package org.figure8.join.core.setup;
16  
17  import org.figure8.join.util.LogUtil;
18  import org.figure8.join.util.ConnectionKeeperLauncher;
19  
20  import org.apache.commons.logging.Log;
21  
22  import javax.sql.DataSource;
23  import javax.naming.InitialContext;
24  
25  import java.io.File;
26  import java.net.URI;
27  import java.net.Socket;
28  import java.net.ServerSocket;
29  import java.net.InetAddress;
30  import java.sql.Connection;
31  import java.sql.DriverManager;
32  /**
33   * Manager object responsible of Join application bootstrapping.
34   * @author <a href="mailto:laurent.broudoux@free.fr">Laurent Broudoux</a>
35   * @version $Revision: 1.3 $
36   */
37  public class BootstrapManager{
38    
39     // Static -------------------------------------------------------------------
40     
41     /** Get a commons logger. */
42     protected static Log log = LogUtil.getLog(BootstrapManager.class);
43     
44     
45     // Attributes ---------------------------------------------------------------
46     
47     /** Flag telling if bootstrap has succeed. */
48     private boolean bootstrapped = false;
49     /** Join application home locator. */
50     private JoinHomeLocator homeLocator = null;
51     /** Application configuration wrapper. */
52     private ApplicationConfig appConfig = null;
53     /** ActiveMQ configuration wrapper. */
54     private ActiveMQConfigurator amqConfigurator = null;
55     /** Hibernate configurator wrapper. */
56     private HibernateConfigurator hbnConfigurator = null;
57  
58  
59     // Constructors -------------------------------------------------------------
60     
61     /** Creates a new instance of BootstrapManager */
62     public BootstrapManager(){
63     }
64     
65     
66     // Public -------------------------------------------------------------------
67     
68     /** @param locator <code>JoinHomeLocator</code> instance */
69     public void setHomeLocator(JoinHomeLocator locator){
70        this.homeLocator = locator;
71     }
72     /** @param config <code>ApplicationConfig</code> instance */
73     public void setApplicationConfig(ApplicationConfig config){
74        this.appConfig = config;
75     }
76     /** @param configurator <code>ActiveMQConfigurator</code> instance */
77     public void setMessagingConfigurator(ActiveMQConfigurator configurator){
78        this.amqConfigurator = configurator;
79     }
80     /** @param configurator <code>HibernateConfigurator</code> instance */
81     public void setHibernateConfigurator(HibernateConfigurator configurator){
82        this.hbnConfigurator = configurator;
83     }
84     
85     
86     // Spring lifecycle method --------------------------------------------------
87     
88     /**
89      * Bootstrap the Join application ! First step is to try locating Join
90      * home path. Then load application configuration.
91      * @throws BootstrapException if Join home cannot be located or if parsing
92      * the application configuration file failed.
93      */
94     public void bootstrap() throws BootstrapException{
95        // First, check home path.
96        if (homeLocator.getHomePath() != null){
97           appConfig.setApplicationHome(homeLocator.getHomePath());
98           // Then load application config.
99           if (appConfig.configurationFileExists())
100             appConfig.load();
101       }
102       else{
103          log.error("Unable to set up application config : no join home set");
104          throw new BootstrapException("Unable to boot application : no join home set");
105       }
106       // Update flag.
107       bootstrapped = true;
108    }
109    
110    
111    // Public -------------------------------------------------------------------
112 
113    /**
114     * Tell if bootstrap phase has ran ok.
115     * @return boostrapped flag.
116     */
117    public boolean isBootstrapped(){
118       return bootstrapped;
119    }
120 
121    /**
122     * Convenient method for knowing if application setup is complete.
123     * @return The corresponding flag on {@code ApplicationConfig}
124     */
125    public boolean isSetupComplete(){
126       // Delegate call to application configuration.
127       return appConfig.isSetupComplete();
128    }
129    /**
130     * Convenient method for knowing if application setup is a custom setup.
131     * @return The corresponding flag on {@code ApplicationConfig}
132     */
133    public boolean isCustomSetup(){
134       // Delegate call to application configuration.
135       return appConfig.isCustomSetup();
136    }
137    /**
138     * Convenient method for knowing if application setup is a standard setup.
139     * @return The corresponding flag on {@code ApplicationConfig}
140     */
141    public boolean isStandardSetup(){
142       // Delegate call to application configuration.
143       return appConfig.isStandardSetup();
144    }
145    /**
146     * Convenient method for knowing it activeMQ setup is complete.
147     * @return true if the setup is complete, false otherwise
148     */
149    public boolean isMessagingSetup(){
150       String setup = appConfig.getProperty(ActiveMQConfigurator.AMQ_SETUP_PROPERTY);
151       if (setup != null && setup.equalsIgnoreCase("true"))
152          return true;
153       return false;
154    }
155    /**
156     * Convenient method for knowing if hibernate setup is complete.
157     * @return true if the setup is complete, false otherwise
158     */
159    public boolean isHibernateSetup(){
160       String setup = appConfig.getProperty(HibernateConfigurator.HBN_SETUP_PROPERTY);
161       if (setup != null && setup.equalsIgnoreCase("true"))
162          return true;
163       return false;
164    }
165 
166    /** @return true if this side of application is synchronous */
167    public boolean isSynchronousSide(){
168       // Delegate call to application configuration.
169       return appConfig.isSynchronousSide();
170    }
171    /** @return true if this side of application is asynchronous */
172    public boolean isAsynchronousSide(){
173       // Delegate call to application configuration.
174       return appConfig.isAsynchronousSide();
175    }
176    /** @return true if this setup is custom with dissociation of synch/asynch services */
177    public boolean isDissociatedSetup(){
178       // Delegate call to application configuration.
179       return appConfig.isDissociatedSetup();
180    }
181    /**
182     * Convenient method for knowing the Url of application other side.
183     * This url only exists if application has a dissociated setup type.
184     * @return The url of the other side of application, or null if it has no sense...
185     */
186    public String getOtherSideUrl(){
187       // Use the adhos property from ApplicationConfig.
188       return appConfig.getProperty(ApplicationConfig.OTHER_SIDE_URL);
189    }
190 
191    /**
192     * Retrieve Join home location onto filesystem.
193     * @return String representing Join home location (or null if not defined)
194     */
195    public String getJoinHome(){
196       String joinHome = appConfig.getApplicationHome();
197       if (joinHome == null)
198          log.fatal("${join.home} has not been configured. Please check your join home configuration");
199       // Return joinHome in all cases.
200       return joinHome;
201    }
202 
203    /**
204     * Convenient method for retrieving application configuration property.
205     * @param property The name of the property of retrieve
206     * @return The value of the corresponding application property.
207     */
208    public String getProperty(String property){
209       // Delegate call to application configuration.
210       return appConfig.getProperty(property);
211    }
212    /**
213     * Convenient method for setting an application config property.
214     * @param key Unique id of the application property
215     * @param value Value associated to key
216     * @see org.figure8.join.core.setup.ApplicationConfig#setProperty(String, Object)
217     */
218    public void setProperty(String key, Object value){
219       // Delegate call to application configuration.
220       appConfig.setProperty(key, value);
221    }
222 
223    /**
224     * Convenient method for getting a path property that may contain the
225     * ${join.home} string. This latter will be replaced by its value.
226     * @param key Unique id of the application property
227     * @return The value of the corresponding application path property.
228     */
229    public String getPathProperty(String key){
230       // Retrieve property.
231       String path = getProperty(key);
232       if (path == null) return null;
233       // Replace join home by its value.
234       StringBuffer prop = new StringBuffer(path);
235       int length = "${join.home}".length();
236       for (int i=prop.indexOf("${join.home}"); i!=-1; i=prop.indexOf("${join.home}"))
237          prop.replace(i, i + length, getJoinHome());
238 
239       return prop.toString();
240     }
241 
242    /**
243     * Bootstrap the database access system. This method first test the access
244     * to database and them launch its configuration process within Hibernate.
245     * @param details Database security details wrapper
246     * @param embedded Tells if the db is the default embedded one
247     * @throws BootstrapException if database cannot be reached or configured
248     */
249    public void bootstrapDatabase(DatabaseDetails details, boolean embedded) throws BootstrapException{
250       try{
251          // Ensure database directory is existing.
252          // It is always used : if embedded or if on asycnhronous side.
253          File database = new File(getJoinHome(), "database");
254          if (!database.isDirectory()) database.mkdir();
255 
256          if (embedded){
257             // Assign the database a file Url.
258             String databasePath = getJoinHome() + "/database/joindb";
259             details.setDatabaseUrl("jdbc:hsqldb:" + databasePath);
260             log.debug("Database is embedded. Setting its Url to: " + details.getDatabaseUrl());
261          }
262          // Test connection before configuring it in Hibernate.
263          testDatabaseConnection(details);
264          hbnConfigurator.configureDatabase(details, embedded);
265       }
266       catch (Exception e){
267          log.fatal("Unable to bootstrap database: \n db: " + details + " \n embedded: " + embedded);
268          log.fatal("The exception message is: " + e.getMessage());
269          throw new BootstrapException("Unable to bootstrap database !");
270       }
271       launchConnectionKeeper();
272    }
273 
274    /**
275     * Bootstrap the database access system through JNDI datasource. This method first
276     * test the access to datasource and them launch its configuration process within Hibernate.
277     * @param datasourceName JNDI name of datasource to use for accessing database
278     * @param dialect Dialect of database to access (as Hibernate dialect)
279     * @throws BootstrapException if database cannot be reached or configured
280     */
281    public void bootstrapDatasource(String datasourceName, String dialect) throws BootstrapException{
282       try{
283          log.debug("Using a DataSource for connecting to database: " + datasourceName);
284          // Test access before configuring it in Hibernate.
285          testDatasourceAccess(datasourceName);
286          hbnConfigurator.configureDatasource(datasourceName, dialect);
287       }
288       catch (Exception e){
289          log.fatal("Unable to bootstrap on datasource: " + datasourceName + " with dialect " + dialect);
290          log.fatal("The exception message is: " + e.getMessage());
291          throw new BootstrapException("Unable to bootstrap on datasource !");
292       }
293    }
294 
295    /**
296     * Bootstrap the messaging system. This method launches the configuration
297     * process within AcitveMQ.
298     * @param brokerUrl Messaging broker url
299     * @param embedded Tells if the messaging broker is on this side of application
300     * @throws BootstrapException if connection to messaging system cannot be configured
301     */
302    public void bootstrapMessaging(String brokerUrl, boolean embedded) throws BootstrapException{
303       try{
304          if (embedded && brokerUrl == null){
305             // Find the port for the embedded broker.
306             int port = 61626;
307             boolean found = false;
308             while (!found && port < 65635){
309                brokerUrl = "tcp://localhost:" + port;
310                // Test connection on this port ...
311                try{
312                   testBrokerConnection(new URI(brokerUrl));
313                   found = true;
314                }
315                catch (Exception e) {port++;}
316             }
317             log.debug("Messaging broker is embedded. Setting its Url to: " + brokerUrl);
318          }
319          else{
320             // Test the connection with brokerUrl as is...
321             testBrokerConnection(new URI(brokerUrl));
322          }
323          // Configure messaging system in ActiveMQ.
324          amqConfigurator.configureMessaging(brokerUrl);
325       }
326       catch (Exception e){
327          log.fatal("Unable to bootstrap messaging system");
328          log.fatal("The exception message is: " + e.getMessage());
329          throw new BootstrapException("Unable to bootstrap messaging system !");
330       }
331    }
332 
333 
334    // Protected ----------------------------------------------------------------
335    
336    /**
337     * Test the connection to configured database.
338     * @param details Database details wrapper for connection params
339     */
340    protected void testDatabaseConnection(DatabaseDetails details) throws Exception{
341       Connection conn = null;
342       log.info("Testing the connection to database...");
343       
344       try{
345          // Just try opening a connection to db.
346          Class.forName(details.getDriverClassname());
347          conn = DriverManager.getConnection(details.getDatabaseUrl(), details.getUsername(), details.getPassword());
348          log.info("Test connection has succeed !");
349       }
350       catch (Exception e){
351          // Log a message and print stack trace.
352          log.error("Exception while testing database connection: " + e.getMessage());
353          e.printStackTrace();
354       }
355       finally{
356          try{
357             // Just try closing the connection.
358             conn.close();
359          }
360          catch (Exception e){
361             // Log a message and propagates exception.
362             log.error("The connection opened for testing database cannot be closed !");
363             throw e;
364          }
365       }
366    }
367 
368    /**
369     * Test the access to configured datasource
370     * @param datasourceName JNDI name of datasource to access
371     */
372    protected void testDatasourceAccess(String datasourceName) throws Exception{
373       log.info("Testing the access to datasource...");
374       // Retrieve object into JNDI context.
375       InitialContext ctx = new InitialContext();
376       Object obj = ctx.lookup(datasourceName);
377       DataSource ds = (DataSource)obj;
378       log.info("Test datasource has succeed ! (" + ds + ")");
379    }
380 
381    /**
382     * Test the connection to broker denoted by <b>brokerUri</b>. If URI denotes
383     * localhost, we have to test broker from server side point of view (ie. using
384     * a ServerSocket).
385     * @param brokerUri The URI of broker to test connection to
386     */
387    protected void testBrokerConnection(URI brokerUri) throws Exception{
388       Socket client = null;
389       ServerSocket server = null;
390       log.info("Testing the connection to broker...");
391 
392       try{
393          // Determine if we are on client or server with host.
394          String host = brokerUri.getHost();
395          InetAddress addr = InetAddress.getByName(host);
396          // We have to test the server side.
397          if (host.trim().equals("localhost") || addr.equals(InetAddress.getByName("localhost"))){
398             server = new ServerSocket(brokerUri.getPort());
399             server.setReuseAddress(true);
400          }
401          // We are on the client side.
402          else
403             client = new Socket(brokerUri.getHost(), brokerUri.getPort());
404       }
405       catch (Exception e){
406          // Log a message and print stack trace.
407          log.error("Exception while testing broker connection: " + e.getMessage());
408          e.printStackTrace();
409       }
410       finally{
411          try{
412             if (client != null) client.close();
413             if (server != null) server.close();
414          }
415          catch (Exception e){
416             // Log a message and propagates exception.
417             log.error("The connection opened for testing broker cannot be closed !");
418             throw e;}
419       }
420    }
421 
422    /** Launch the ConnectionKeeper daemon. */
423    protected void launchConnectionKeeper(){
424       ConnectionKeeperLauncher.startConnectionKeeperIfNecessary();
425    }
426 }