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 }