/*
* Installer.java
*
* This work is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This work is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* Copyright (c) 2004-2008 Per Cederberg. All rights reserved.
*/
package org.liquidsite.app.install;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import org.liquidsite.core.data.AttributeData;
import org.liquidsite.core.data.AttributePeer;
import org.liquidsite.core.data.ContentPeer;
import org.liquidsite.core.data.DataObjectException;
import org.liquidsite.core.data.DataSource;
import org.liquidsite.core.data.PermissionPeer;
import org.liquidsite.core.text.TaggedFormatter;
import org.liquidsite.util.db.DatabaseConnection;
import org.liquidsite.util.db.DatabaseConnectionException;
import org.liquidsite.util.db.DatabaseConnector;
import org.liquidsite.util.db.DatabaseDataException;
import org.liquidsite.util.db.DatabaseException;
import org.liquidsite.util.db.DatabaseQuery;
import org.liquidsite.util.db.DatabaseResults;
import org.liquidsite.util.log.Log;
/**
* The install helper class. This class handles all the installation
* tasks, such as database updates and configuration writing.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
public class Installer {
/**
* The class logger.
*/
static final Log LOG = new Log(Installer.class);
/**
* The version to install.
*/
private String version;
/**
* The database connector to use.
*/
private DatabaseConnector connector;
/**
* The SQL file directory.
*/
private File sqlDir;
/**
* The database updaters available.
*/
private ArrayList updaters = new ArrayList();
/**
* Create a new installer.
*
* @param version the application version to install
* @param connector the database connector to use
* @param sqlDir the base directory for the SQL files
*/
public Installer(String version,
DatabaseConnector connector,
File sqlDir) {
this.version = version;
this.connector = connector;
this.sqlDir = sqlDir;
updaters.add(new DatabaseUpdater("0.3",
"0.4",
"UPDATE_LIQUIDSITE_TABLES_0.4.sql"));
updaters.add(new DatabaseUpdater("0.4",
"0.5",
"UPDATE_LIQUIDSITE_TABLES_0.5.sql"));
updaters.add(new Version06DatabaseUpdater());
updaters.add(new DatabaseUpdater("0.6", "0.7"));
updaters.add(new DatabaseUpdater("0.7", "0.8"));
updaters.add(new DatabaseUpdater("0.8", "0.8.1"));
updaters.add(new DatabaseUpdater("0.8.1", "0.8.2"));
updaters.add(new Version09DatabaseUpdater());
updaters.add(new DatabaseUpdater("0.9",
"0.10",
"UPDATE_LIQUIDSITE_TABLES_0.10.sql"));
updaters.add(new DatabaseUpdater("0.10", "0.10.1"));
updaters.add(new DatabaseUpdater("0.10.1",
"1.0.0",
"UPDATE_LIQUIDSITE_TABLES_1.0.sql"));
updaters.add(new DatabaseUpdater("1.0.0", "1.0.1"));
updaters.add(new DatabaseUpdater("1.0.1", "1.0.2"));
}
/**
* Checks if the specified Liquid Site version can be updated by
* this installer.
*
* @param fromVersion the version to update from
*
* @return true if the installer supports the update, or
* false otherwise
*/
public boolean canUpdate(String fromVersion) {
DatabaseUpdater updater;
if (version.equals(fromVersion)) {
return true;
} else {
for (int i = 0; i < updaters.size(); i++) {
updater = (DatabaseUpdater) updaters.get(i);
if (updater.canUpdate(this, fromVersion)) {
return true;
}
}
}
return false;
}
/**
* Returns the SQL file directory.
*
* @return the SQL file directory
*/
public File getSqlDir() {
return sqlDir;
}
/**
* Creates the Liquid Site database tables. The database must
* already exist for this method to work.
*
* @param database the database name
*
* @throws InstallException if the database tables couldn't be
* created correctly
*/
public void createTables(String database) throws InstallException {
DatabaseConnection con = null;
try {
con = connector.getConnection();
con.setCatalog(database);
con.execute(new File(sqlDir, "CREATE_LIQUIDSITE_TABLES.sql"));
} catch (DatabaseConnectionException e) {
LOG.error("couldn't create tables", e);
throw new InstallException("couldn't create tables", e);
} catch (DatabaseException e) {
LOG.error("couldn't create tables", e);
throw new InstallException("couldn't create tables", e);
} catch (FileNotFoundException e) {
LOG.error("couldn't find create table SQL file", e);
throw new InstallException("couldn't find create table SQL file",
e);
} catch (IOException e) {
LOG.error("couldn't read create table SQL file", e);
throw new InstallException("couldn't read create table SQL file",
e);
} finally {
if (con != null) {
connector.returnConnection(con);
}
}
}
/**
* Updates the Liquid Site database tables. The database must
* already contain the specified Liquid Site version for this
* method to work.
*
* @param database the database name
* @param fromVersion the database Liquid Site version
*
* @throws InstallException if the database tables couldn't be
* updated correctly
*/
public void updateTables(String database, String fromVersion)
throws InstallException {
DatabaseConnection con = null;
if (!canUpdate(fromVersion)) {
throw new InstallException("cannot update from version " +
fromVersion);
}
try {
con = connector.getConnection();
con.setCatalog(database);
updateTables(con, fromVersion);
} catch (DatabaseConnectionException e) {
LOG.error("couldn't update tables", e);
throw new InstallException("couldn't update tables", e);
} catch (DatabaseException e) {
LOG.error("couldn't update tables", e);
throw new InstallException("couldn't update tables", e);
} catch (FileNotFoundException e) {
LOG.error("couldn't find update table SQL file", e);
throw new InstallException("couldn't find update table SQL file",
e);
} catch (IOException e) {
LOG.error("couldn't read update table SQL file", e);
throw new InstallException("couldn't read update table SQL file",
e);
} finally {
if (con != null) {
connector.returnConnection(con);
}
}
}
/**
* Updates the Liquid Site database tables. The database must
* already contain the specified Liquid Site version for this
* method to work.
*
* @param con the database connection to use
* @param fromVersion the database Liquid Site version
*
* @throws DatabaseException if a database statement execution
* failed
* @throws FileNotFoundException if an update tables SQL file
* couldn't be found
* @throws IOException if an update tables SQL file couldn't be
* read
*/
void updateTables(DatabaseConnection con, String fromVersion)
throws DatabaseException, FileNotFoundException, IOException {
DatabaseUpdater updater;
if (version.equals(fromVersion)) {
return;
} else {
for (int i = 0; i < updaters.size(); i++) {
updater = (DatabaseUpdater) updaters.get(i);
if (updater.canUpdate(this, fromVersion)) {
updater.updateTables(this, con);
return;
}
}
}
}
/**
* A database updater. This class handles a database update from
* one version to another. Multiple database updaters can be
* chained together to provide an update path across several
* versions.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
private class DatabaseUpdater {
/**
* The version to update from.
*/
private String from;
/**
* The version to update to.
*/
private String to;
/**
* The SQL file containing the update statements.
*/
private String sqlFile;
/**
* Creates a new database update that performs no changes.
*
* @param from the version to update from
* @param to the version to update to
*/
public DatabaseUpdater(String from, String to) {
this(from, to, null);
}
/**
* Creates a new database update from a SQL statement file.
*
* @param from the version to update from
* @param to the version to update to
* @param sqlFile the SQL file with update statements
*/
public DatabaseUpdater(String from, String to, String sqlFile) {
this.from = from;
this.to = to;
this.sqlFile = sqlFile;
}
/**
* Checks if this updater can handle a specified version. This
* method will call the installer to check if the version this
* updater creates is valid or if it in turn can be updated.
* The update search is thus exhaustive, checking for every
* possible path from the specified version to the installer
* version.
*
* @param installer the installer to use
* @param from the version to update from
*
* @return true if the specified version can be handled, or
* false otherwise
*/
public boolean canUpdate(Installer installer, String from) {
return this.from.equals(from)
&& installer.canUpdate(this.to);
}
/**
* Updates the Liquid Site database tables. The database must
* already contain the specified Liquid Site version for this
* method to work. This method will also call the installer
* to further update the new version created. The update can
* thus handle an update path consisting of chaining several
* updaters together.
*
* @param installer the installer to use
* @param con the database connection to use
*
* @throws DatabaseException if a database statement execution
* failed
* @throws FileNotFoundException if an update tables SQL file
* couldn't be found
* @throws IOException if an update tables SQL file couldn't be
* read
*/
public void updateTables(Installer installer, DatabaseConnection con)
throws DatabaseException, FileNotFoundException, IOException {
if (sqlFile != null) {
con.execute(new File(getSqlDir(), sqlFile));
}
updateTableContent(con);
installer.updateTables(con, to);
}
/**
* Updates the Liquid Site database table content. This method
* is called by the updateTables() method, directly after
* executing the SQL script and before calling the next
* updater in the chain. The default implementation of this
* method does nothing.
*
* @param con the database connection to use
*
* @throws DatabaseException if a database statement execution
* failed
* @throws FileNotFoundException if an update tables SQL file
* couldn't be found
* @throws IOException if an update tables SQL file couldn't be
* read
*/
protected void updateTableContent(DatabaseConnection con)
throws DatabaseException, FileNotFoundException, IOException {
// Do nothing in the default implementation
}
}
/**
* A database updater for version 0.6.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
private class Version06DatabaseUpdater extends DatabaseUpdater {
/**
* Creates a new database updater for version 0.6.
*/
public Version06DatabaseUpdater() {
super("0.5", "0.6", "UPDATE_LIQUIDSITE_TABLES_0.6.sql");
}
/**
* Updates the Liquid Site database table content. This method
* is called by the updateTables() method, directly after
* executing the SQL script and before calling the next
* updater in the chain.
*
* @param con the database connection to use
*
* @throws DatabaseException if a database statement execution
* failed
*/
protected void updateTableContent(DatabaseConnection con)
throws DatabaseException {
DataSource src = new DataSource(con);
DatabaseQuery query = new DatabaseQuery();
DatabaseResults res;
// Set the content status
query.setSql("SELECT DISTINCT(ID) FROM LS_CONTENT");
res = con.execute(query);
for (int i = 0; i < res.getRowCount(); i++) {
try {
ContentPeer.doStatusUpdate(src, res.getRow(i).getInt(0));
} catch (DatabaseDataException e) {
throw new DatabaseException(e.getMessage());
} catch (DataObjectException e) {
throw new DatabaseException(e.getMessage());
}
}
// Remove document permissions
query.setSql("SELECT DISTINCT(ID), DOMAIN FROM LS_CONTENT " +
"WHERE CATEGORY = 12");
res = con.execute(query);
for (int i = 0; i < res.getRowCount(); i++) {
try {
PermissionPeer.doDelete(src,
res.getRow(i).getString(1),
res.getRow(i).getInt(0));
} catch (DatabaseDataException e) {
throw new DatabaseException(e.getMessage());
} catch (DataObjectException e) {
throw new DatabaseException(e.getMessage());
}
}
}
}
/**
* A database updater for version 0.9.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
private class Version09DatabaseUpdater extends DatabaseUpdater {
/**
* Creates a new database updater for version 0.9.
*/
public Version09DatabaseUpdater() {
super("0.8.2", "0.9", "UPDATE_LIQUIDSITE_TABLES_0.9.sql");
}
/**
* Updates the Liquid Site database table content. This method
* is called by the updateTables() method, directly after
* executing the SQL script and before calling the next
* updater in the chain.
*
* @param con the database connection to use
*
* @throws DatabaseException if a database statement execution
* failed
*/
protected void updateTableContent(DatabaseConnection con)
throws DatabaseException {
DataSource src = new DataSource(con);
DatabaseQuery query = new DatabaseQuery();
DatabaseResults res;
AttributeData data;
int id;
int revision;
String name;
String str;
// Clean tagged text document attributes
query.setSql("SELECT CONTENT, REVISION, NAME " +
"FROM LS_ATTRIBUTE " +
"WHERE NAME LIKE 'PROPERTYTYPE.%' AND DATA = '2'");
res = con.execute(query);
for (int i = 0; i < res.getRowCount(); i++) {
try {
id = res.getRow(i).getInt(0);
revision = res.getRow(i).getInt(1);
name = "PROPERTY." +
res.getRow(i).getString(2).substring(13);
LOG.info("cleaning tagged text in content " +
id + " revision " + revision +
" property " + name.substring(9));
data = AttributePeer.doSelectByName(src,
id,
revision,
name);
str = data.getString(AttributeData.DATA);
data.setString(AttributeData.DATA,
TaggedFormatter.clean(str));
AttributePeer.doUpdate(src, data);
} catch (DatabaseDataException e) {
throw new DatabaseException(e.getMessage());
} catch (DataObjectException e) {
throw new DatabaseException(e.getMessage());
}
}
}
}
}