001    package org.apache.fulcrum.hsqldb;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.avalon.framework.activity.Disposable;
023    import org.apache.avalon.framework.activity.Initializable;
024    import org.apache.avalon.framework.activity.Startable;
025    import org.apache.avalon.framework.configuration.Configurable;
026    import org.apache.avalon.framework.configuration.Configuration;
027    import org.apache.avalon.framework.configuration.ConfigurationException;
028    import org.apache.avalon.framework.logger.AbstractLogEnabled;
029    import org.hsqldb.Server;
030    import org.hsqldb.ServerConstants;
031    import org.hsqldb.persist.HsqlProperties;
032    
033    /**
034     * The original implementation was taken from
035     * http://scarab.tigris.org/source/browse/scarab/src/java/org/tigris/scarab/services/hsql/
036     * and tweaked a little bit.
037     *
038     * <p>
039     * The component is configured from the componentConfig.xml file by specifying
040     * attributes on the service element
041     * </p>
042     * <p>
043     *
044     * <dl>
045     * <dt>database</dt>
046     * <dd>The directory path where the database files will be stored</dd>
047     * <dt>dbname</dt>
048     * <dd>The alias path used to refer to the database from the JDBC url.</dd>
049     * <dt>trace</dt>
050     * <dd>(true/false) a flag enabling tracing in the hsql server.</dd>
051     * <dt>silent</dt>
052     * <dd>(true/false) a flag to control the logging output of the hsql server.</dd>
053     * <dt>start</dt>
054     * <dd>(true/false) when true the database is started at configuration time, and does
055     * not need to be started under application control.</dd>
056     * <dt>port</dt>
057     * <dd>The listening port of the hsql server.</dd>
058     * </dl>
059     *
060     * Example:
061     *  ...
062     *  <HSQLService database="./target" dbname="test" trace="true" silent="false" start="true" port="9001"/>
063     *  ...
064     *
065     * @author <a href="mailto:pti@elex.be">Peter Tillemans</a>
066     * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
067     */
068    public class HSQLServiceImpl
069            extends AbstractLogEnabled
070            implements HSQLService, Configurable, Initializable, Startable, Disposable
071    {
072        /** the HSQLDB server instance */
073        private Server server;
074    
075        /** the configuration properties */
076        private HsqlProperties serverProperties;
077    
078        /////////////////////////////////////////////////////////////////////////
079        // Avalon Service Lifecycle Implementation
080        /////////////////////////////////////////////////////////////////////////
081    
082        /**
083         * Constructor
084         */
085        public HSQLServiceImpl()
086        {
087            // nothing to do
088        }
089    
090        /**
091         * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
092         */
093        public void configure(Configuration cfg) throws ConfigurationException
094        {
095            String[] names = cfg.getAttributeNames();
096    
097            for (int i = 0; i < names.length; i++)
098            {
099                getLogger().debug(names[i] + " --> " + cfg.getAttribute(names[i]));
100            }
101    
102            this.serverProperties = new HsqlProperties();
103            this.serverProperties.setProperty("server.database.0", cfg.getAttribute("database"));
104            this.serverProperties.setProperty("server.dbname.0", cfg.getAttribute("dbname"));
105            this.serverProperties.setProperty("server.trace", cfg.getAttributeAsBoolean("trace", false));
106            this.serverProperties.setProperty("server.silent", cfg.getAttributeAsBoolean("silent", true));
107            this.serverProperties.setProperty("server.port", cfg.getAttribute("port"));
108            this.serverProperties.setProperty("server.tls", cfg.getAttribute("tls","false"));
109        }
110    
111        /**
112         * @see org.apache.avalon.framework.activity.Initializable#initialize()
113         */
114        public void initialize() throws Exception
115        {
116            this.server = new Server();
117            this.server.setProperties( this.serverProperties );
118        }
119    
120        /**
121         * Starts the HSQLDB server. The implementation polls to ensure
122         * that the HSQLDB server is fully initialized otherwise we get
123         * spurious connection exceptions. If the HSQLDB server is not
124         * upand running within 10 seconds we throw an exception.
125         *
126         * @see org.apache.avalon.framework.activity.Startable#start()
127         */
128        public void start() throws Exception
129        {
130            // The method start() waits for current state to change from
131            // SERVER_STATE_OPENING. In order to discover the success or failure
132            // of this operation, server state must be polled or a subclass of Server
133            // must be used that overrides the setState method to provide state
134            // change notification.
135    
136            server.start();
137    
138            // poll for 10 seconds until HSQLDB is up and running
139    
140            this.pollForState( ServerConstants.SERVER_STATE_ONLINE, 100 );
141        }
142    
143        /**
144         * Stop the HSQLDB server. The implementation polls to ensure
145         * that the HSQLDB server has terminated otherwise someone
146         * could call System.exit() and break the database.
147         *
148         * @see org.apache.avalon.framework.activity.Startable#stop()
149         */
150        public void stop() throws Exception
151        {
152            this.server.stop();
153    
154            // poll for 10 seconds until HSQLDB is down
155    
156            this.pollForState( ServerConstants.SERVER_STATE_SHUTDOWN, 100 );
157        }
158    
159        /**
160         * @see org.apache.avalon.framework.activity.Disposable#dispose()
161         */
162        public void dispose()
163        {
164            this.server = null;
165            this.serverProperties = null;
166        }
167    
168        /////////////////////////////////////////////////////////////////////////
169        // Service Interface Implementation
170        /////////////////////////////////////////////////////////////////////////
171    
172        public boolean isOnline() {
173            return server.getState() == ServerConstants.SERVER_STATE_ONLINE;
174        }
175    
176    
177        /////////////////////////////////////////////////////////////////////////
178        // Service Implementation
179        /////////////////////////////////////////////////////////////////////////
180    
181        /**
182         * Poll the HSQLDB server for a state change.
183         *
184         * @param desiredState the state we are waiting for
185         * @param lim the number of 100ms iteration to wait for
186         * @throws Exception something went wrong
187         */
188        private void pollForState( int desiredState, int lim )
189            throws Exception
190        {
191            int currentState;
192            boolean isSuccessful = false;
193    
194            this.getLogger().debug( "Polling for state : " + desiredState );
195    
196            for( int i=0; i<lim; i++ )
197            {
198                currentState = this.server.getState();
199    
200                if( desiredState == currentState )
201                {
202                    isSuccessful = true;
203                    break;
204                }
205    
206                Thread.sleep(100);
207            }
208    
209            if( !isSuccessful )
210            {
211    
212                Throwable serverError = this.server.getServerError();
213                String msg = "Unable to change the HSQLDB server to state : " + desiredState;
214    
215                if( serverError != null )
216                {
217                    this.getLogger().error( msg, serverError );
218    
219                        if( serverError instanceof Exception )
220                        {
221                            throw (Exception) serverError;
222                        }
223                    else
224                        {
225                            throw new RuntimeException( serverError.getMessage() );
226                        }
227                }
228                else
229                {
230                        this.getLogger().error(msg);
231                        throw new RuntimeException( msg );
232                }
233            }
234        }
235    }