001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2012 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.server.database;
021    
022    import org.apache.commons.io.IOUtils;
023    import org.apache.commons.lang.StringUtils;
024    import org.apache.derby.drda.NetworkServerControl;
025    import org.sonar.api.CoreProperties;
026    import org.sonar.api.config.Settings;
027    import org.sonar.api.database.DatabaseProperties;
028    import org.sonar.api.utils.Logs;
029    import org.sonar.api.utils.SonarException;
030    import org.sonar.server.platform.ServerStartException;
031    
032    import java.io.File;
033    import java.io.FileOutputStream;
034    import java.io.IOException;
035    import java.io.PrintWriter;
036    import java.net.InetAddress;
037    import java.util.Properties;
038    
039    public class EmbeddedDatabase {
040    
041      private static final String DEFAULT_USER = "sonar";
042      private static final String DEFAULT_PWD = "sonar";
043    
044      private NetworkServerControl serverControl = null;
045    
046      private File dbHome;
047      private Properties dbProps;
048      private PrintWriter dbLog;
049    
050      public EmbeddedDatabase(Settings settings) {
051        this.dbHome = getDataDirectory(settings);
052        this.dbProps = getDefaultProperties(settings);
053      }
054    
055      public EmbeddedDatabase(File dbHome, Properties dbProps) {
056        this.dbHome = dbHome;
057        this.dbProps = dbProps;
058      }
059    
060      public File getDataDir() {
061        return dbHome;
062      }
063    
064      protected File getDataDirectory(Settings settings) {
065        String dirName = settings.getString(DatabaseProperties.PROP_EMBEDDED_DATA_DIR);
066        if (StringUtils.isBlank(dirName)) {
067          File sonarHome = new File(settings.getString(CoreProperties.SONAR_HOME));
068          if (!sonarHome.isDirectory() || !sonarHome.exists()) {
069            throw new ServerStartException("Sonar home directory does not exist");
070          }
071          return new File(sonarHome, "data");
072        }
073        return new File(dirName);
074      }
075    
076      public void setDbLog(PrintWriter dbLog) {
077        this.dbLog = dbLog;
078      }
079    
080      public void start() {
081        if (dbHome.exists() && !dbHome.isDirectory()) {
082          throw new SonarException("Database home " + dbHome.getPath() + " is not a directory");
083        }
084        if (!dbHome.exists()) {
085          dbHome.mkdirs();
086        }
087        System.setProperty("derby.system.home", dbHome.getPath());
088    
089        saveDerbyPropertiesFile();
090        startListening();
091        Logs.INFO.info("Embedded database started. Data stored in: " + dbHome.getAbsolutePath());
092      }
093    
094      private void startListening() {
095        try {
096          int port = Integer.parseInt(dbProps.getProperty("derby.drda.portNumber"));
097          String host = dbProps.getProperty("derby.drda.host");
098          serverControl = new NetworkServerControl(InetAddress.getByName(host), port, DEFAULT_USER, DEFAULT_PWD);
099          Logs.INFO.info("Starting embedded database on port " + port);
100          serverControl.start(dbLog);
101          ensureServerIsUp();
102        } catch (Exception e) {
103          throw new SonarException(e);
104        }
105      }
106    
107      private void saveDerbyPropertiesFile() {
108        FileOutputStream output = null;
109        try {
110          File derbyProps = new File(dbHome.getPath() + "/derby.properties");
111          output = new FileOutputStream(derbyProps);
112          dbProps.store(output, "GENERATED FILE, DO NOT EDIT ME UNLESS YOU WANT TO LOOSE YOUR TIME ;O)");
113    
114        } catch (IOException e) {
115          throw new SonarException(e);
116    
117        } finally {
118          IOUtils.closeQuietly(output);
119        }
120      }
121    
122      public void stop() {
123        if (serverControl != null) {
124          try {
125            serverControl.shutdown();
126            ensureServerIsDown();
127            serverControl = null;
128            Logs.INFO.info("Embedded database stopped");
129    
130          } catch (Exception e) {
131            throw new SonarException(e);
132          }
133        }
134      }
135    
136      private void ensureServerIsUp() {
137        for (int retry = 0; retry < 100; retry++) {
138          try {
139            serverControl.ping();
140            return;
141    
142          } catch (Exception ex) {
143            sleep(300);
144          }
145        }
146        throw new SonarException("Embedded database does not respond to ping requests");
147      }
148    
149      private void ensureServerIsDown() {
150        for (int retry = 0; retry < 100; retry++) {
151          try {
152            serverControl.ping();
153            sleep(300);
154    
155          } catch (SonarException se) {
156            throw se;
157    
158          } catch (Exception e) {
159            // normal case: the database does not respond to ping
160            return;
161          }
162        }
163        throw new SonarException("Fail to stop embedded database");
164      }
165    
166    
167      private void sleep(long time) {
168        try {
169          Thread.sleep(time);
170        } catch (InterruptedException e) {
171          throw new SonarException("Fail to ping embedded database", e);
172        }
173      }
174    
175      public static Properties getDefaultProperties(Settings settings) {
176        Properties props = new Properties();
177        props.setProperty("derby.drda.startNetworkServer", "true");
178        props.setProperty("derby.drda.host", StringUtils.defaultIfBlank(settings.getString("sonar.derby.drda.host"), "localhost"));
179        props.setProperty("derby.drda.portNumber", StringUtils.defaultIfBlank(settings.getString("sonar.derby.drda.portNumber"), "1527"));
180        props.setProperty("derby.drda.maxThreads", StringUtils.defaultIfBlank(settings.getString("sonar.derby.drda.maxThreads"), "20"));
181        props.setProperty("derby.drda.minThreads", StringUtils.defaultIfBlank(settings.getString("sonar.derby.drda.minThreads"), "2"));
182        props.setProperty("derby.drda.logConnections", StringUtils.defaultIfBlank(settings.getString("sonar.derby.drda.logConnections"), "false"));
183        props.setProperty("derby.stream.error.logSeverityLevel", StringUtils.defaultIfBlank(settings.getString("sonar.derby.stream.error.logSeverityLevel"), "20000"));
184        props.setProperty("derby.connection.requireAuthentication", "true");
185        props.setProperty("derby.user." + DEFAULT_USER, DEFAULT_PWD);
186        return props;
187      }
188    
189    }