src/java/deploy/org/postgresql/pljava/deploy/Deployer.java 0100664 0000764 0000764 00000026473 10177643106 023355 0 ustar owner owner /*
* Copyright (c) 2004, 2005 TADA AB - Taby Sweden
* Distributed under the terms shown in the file COPYRIGHT
* found in the root folder of this project or at
* http://eng.tada.se/osprojects/COPYRIGHT.html
*/
package org.postgresql.pljava.deploy;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.util.ArrayList;
/**
* When running the deployer, you must use a classpath that can see the
* deploy.jar found in the Pl/Java distribution and the postgresql.jar from the
* PostgreSQL distribution. The former contains the code for the deployer
* command and the second includes the PostgreSQL JDBC driver. You then run the
* deployer with the command:
*
*
* java -cp <your classpath> org.postgresql.pljava.deploy.Deployer [ options ]
*
*
* It's recommended that create a shell script or a .bat script that does this
* for you so that you don't have to do this over and over again.
*
* Deployer options
*
* | Option |
* Description |
*
* | -install |
* Installs the Java language along with the sqlj procedures. The deployer
* will fail if the language is installed already. |
*
*
* | -reinstall |
* Reinstalls the Java language and the sqlj procedures. This will
* effectively drop all jar files that have been loaded. |
*
*
* | -remove |
* Drops the Java language and the sqjl procedures and loaded jars |
*
*
* | -user <user name> |
* Name of user that connects to the database. Default is current user |
*
*
* | -password <password> |
* Password of user that connects to the database. Default is no password
* |
*
*
* | -database <database> |
* The name of the database to connect to. Default is current user |
*
*
* | -host <hostname> |
* Name of the host. Default is "localhost" |
*
*
* | -windows |
* Use this option if the host runs on a windows platform. Affects the
* name used for the Pl/Java dynamic library |
*
*
*
* @author Thomas Hallgren
*/
public class Deployer
{
private static final int CMD_AMBIGUOUS = -2;
private static final int CMD_UNKNOWN = -1;
private static final int CMD_UNINSTALL = 0;
private static final int CMD_INSTALL = 1;
private static final int CMD_REINSTALL = 2;
private static final int CMD_USER = 3;
private static final int CMD_PASSWORD = 4;
private static final int CMD_DATABASE = 5;
private static final int CMD_HOSTNAME = 6;
private static final int CMD_PORT = 7;
private static final int CMD_WINDOWS = 8;
private final Connection m_connection;
private static final ArrayList s_commands = new ArrayList();
static
{
s_commands.add(CMD_UNINSTALL, "uninstall");
s_commands.add(CMD_INSTALL, "install");
s_commands.add(CMD_REINSTALL, "reinstall");
s_commands.add(CMD_USER, "user");
s_commands.add(CMD_PASSWORD, "password");
s_commands.add(CMD_DATABASE, "database");
s_commands.add(CMD_HOSTNAME, "host");
s_commands.add(CMD_PORT, "port");
s_commands.add(CMD_WINDOWS, "windows");
}
private static final int getCommand(String arg)
{
int top = s_commands.size();
int candidateCmd = CMD_UNKNOWN;
for(int idx = 0; idx < top; ++idx)
{
if(((String)s_commands.get(idx)).startsWith(arg))
{
if(candidateCmd != CMD_UNKNOWN)
return CMD_AMBIGUOUS;
candidateCmd = idx;
}
}
return candidateCmd;
}
public static void printUsage()
{
PrintStream out = System.err;
out.println("usage: java -jar deploy.jar");
out.println(" {-install | -uninstall | -reinstall}");
out.println(" [ -host ] # default is localhost");
out.println(" [ -port ] # default is blank");
out.println(" [ -database ] # default is name of current user");
out.println(" [ -user ] # default is name of current user");
out.println(" [ -password ] # default is no password");
out.println(" [ -windows ] # If the server is on a Windows machine");
}
public static void main(String[] argv)
{
String driverClass = "org.postgresql.Driver";
String hostName = "localhost";
String userName = System.getProperty("user.name", "postgres");
String database = userName;
String subsystem = "postgresql";
String password = null;
String portNumber = null;
boolean unix = true;
int cmd = CMD_UNKNOWN;
int top = argv.length;
for(int idx = 0; idx < top; ++idx)
{
String arg = argv[idx];
if(arg.length() < 2)
{
printUsage();
return;
}
if(arg.charAt(0) == '-')
{
int optCmd = getCommand(arg.substring(1));
switch(optCmd)
{
case CMD_INSTALL:
case CMD_UNINSTALL:
case CMD_REINSTALL:
if(cmd != CMD_UNKNOWN)
{
printUsage();
return;
}
cmd = optCmd;
break;
case CMD_USER:
if(++idx < top)
{
userName = argv[idx];
if(userName.length() > 0
&& userName.charAt(0) != '-')
break;
}
printUsage();
return;
case CMD_PASSWORD:
if(++idx < top)
{
password = argv[idx];
if(password.length() > 0
&& password.charAt(0) != '-')
break;
}
printUsage();
return;
case CMD_DATABASE:
if(++idx < top)
{
database = argv[idx];
if(database.length() > 0
&& database.charAt(0) != '-')
break;
}
printUsage();
return;
case CMD_HOSTNAME:
if(++idx < top)
{
hostName = argv[idx];
if(hostName.length() > 0
&& hostName.charAt(0) != '-')
break;
}
printUsage();
return;
case CMD_PORT:
if(++idx < top)
{
portNumber = argv[idx];
if(portNumber.length() > 0
&& portNumber.charAt(0) != '-')
break;
}
printUsage();
return;
case CMD_WINDOWS:
unix = false;
break;
default:
printUsage();
return;
}
}
}
if(cmd == CMD_UNKNOWN)
{
printUsage();
return;
}
try
{
Class.forName(driverClass);
StringBuffer cc = new StringBuffer();
cc.append("jdbc:");
cc.append(subsystem);
cc.append("://");
cc.append(hostName);
if(portNumber != null)
{
cc.append(':');
cc.append(portNumber);
}
cc.append('/');
cc.append(database);
Connection c = DriverManager.getConnection(
cc.toString(),
userName,
password);
checkIfConnectedAsSuperuser(c);
Deployer deployer = new Deployer(c);
if(cmd == CMD_UNINSTALL || cmd == CMD_REINSTALL)
{
deployer.dropSQLJSchema();
}
if(cmd == CMD_INSTALL || cmd == CMD_REINSTALL)
{
deployer.createSQLJSchema();
deployer.initJavaHandler(unix);
deployer.initializeSQLJSchema();
}
c.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public Deployer(Connection c)
{
m_connection = c;
}
public static void checkIfConnectedAsSuperuser(Connection conn)
throws SQLException
{
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SHOW IS_SUPERUSER");
try {
if (rs.next() && rs.getString(1).equals("on"))
{
return;
}
}
finally
{
rs.close();
stmt.close();
}
throw new SQLException(
"You must be a superuser to deploy/undeploy pl/Java.");
}
public void dropSQLJSchema()
throws SQLException
{
Statement stmt = m_connection.createStatement();
stmt.execute("DROP SCHEMA sqlj CASCADE");
try
{
stmt.execute("DROP LANGUAGE java CASCADE");
}
catch(SQLException e)
{
/* ignore */
}
stmt.close();
}
public void createSQLJSchema()
throws SQLException
{
Statement stmt = m_connection.createStatement();
stmt.execute("CREATE SCHEMA sqlj AUTHORIZATION postgres");
stmt.execute("GRANT USAGE ON SCHEMA sqlj TO public");
stmt.close();
}
public void initializeSQLJSchema()
throws SQLException
{
Statement stmt = m_connection.createStatement();
stmt.execute(
"CREATE TABLE sqlj.jar_repository(" +
" jarId SERIAL PRIMARY KEY," +
" jarName VARCHAR(100) UNIQUE NOT NULL," +
" jarOrigin VARCHAR(500) NOT NULL," +
" deploymentDesc INT" +
")");
stmt.execute("ALTER TABLE sqlj.jar_repository OWNER TO postgres");
stmt.execute("GRANT SELECT ON sqlj.jar_repository TO public");
stmt.execute(
"CREATE TABLE sqlj.jar_entry(" +
" entryId SERIAL PRIMARY KEY," +
" entryName VARCHAR(200) NOT NULL," +
" jarId INT NOT NULL REFERENCES sqlj.jar_repository ON DELETE CASCADE," +
" entryImage BYTEA NOT NULL," +
" UNIQUE(jarId, entryName)" +
")");
stmt.execute("ALTER TABLE sqlj.jar_entry OWNER TO postgres");
stmt.execute("GRANT SELECT ON sqlj.jar_entry TO public");
stmt.execute(
"ALTER TABLE sqlj.jar_repository" +
" ADD FOREIGN KEY (deploymentDesc) REFERENCES sqlj.jar_entry ON DELETE SET NULL");
// Create the table maintaining the class path.
//
stmt.execute(
"CREATE TABLE sqlj.classpath_entry(" +
" schemaName VARCHAR(30) NOT NULL," +
" ordinal INT2 NOT NULL," + // Ordinal in class path
" jarId INT NOT NULL REFERENCES sqlj.jar_repository ON DELETE CASCADE," +
" PRIMARY KEY(schemaName, ordinal)" +
")");
stmt.execute("ALTER TABLE sqlj.classpath_entry OWNER TO postgres");
stmt.execute("GRANT SELECT ON sqlj.classpath_entry TO public");
// These are the proposed SQL standard methods.
//
stmt.execute(
"CREATE FUNCTION sqlj.install_jar(VARCHAR, VARCHAR, BOOLEAN) RETURNS void" +
" AS 'org.postgresql.pljava.management.Commands.installJar'" +
" LANGUAGE java SECURITY DEFINER");
stmt.execute(
"CREATE FUNCTION sqlj.replace_jar(VARCHAR, VARCHAR, BOOLEAN) RETURNS void" +
" AS 'org.postgresql.pljava.management.Commands.replaceJar'" +
" LANGUAGE java SECURITY DEFINER");
stmt.execute(
"CREATE FUNCTION sqlj.remove_jar(VARCHAR, BOOLEAN) RETURNS void" +
" AS 'org.postgresql.pljava.management.Commands.removeJar'" +
" LANGUAGE java SECURITY DEFINER");
// This function is not as proposed. It's more Java'ish. The proposal
// using sqlj.alter_jar_path is in my opinion bloated and will not be
// well received in the Java community. Luckily, the support is suggested
// to be optional.
//
stmt.execute(
"CREATE FUNCTION sqlj.set_classpath(VARCHAR, VARCHAR) RETURNS void" +
" AS 'org.postgresql.pljava.management.Commands.setClassPath'" +
" LANGUAGE java SECURITY DEFINER");
stmt.execute(
"CREATE FUNCTION sqlj.get_classpath(VARCHAR) RETURNS VARCHAR" +
" AS 'org.postgresql.pljava.management.Commands.getClassPath'" +
" LANGUAGE java STABLE SECURITY DEFINER");
stmt.close();
}
public void initJavaHandler(boolean unix)
throws SQLException
{
Statement stmt = m_connection.createStatement();
stmt.execute(
"CREATE FUNCTION sqlj.java_call_handler()" +
" RETURNS language_handler" +
" AS '" + (unix ? "lib" : "") + "pljava'" +
" LANGUAGE C");
stmt.execute("CREATE TRUSTED LANGUAGE java HANDLER sqlj.java_call_handler");
stmt.close();
}
}
src/java/pljava/org/postgresql/pljava/management/SQLDeploymentDescriptor.java 0100664 0000764 0000764 00000033320 10177642452 027122 0 ustar owner owner /*
* Copyright (c) 2004, 2005 TADA AB - Taby Sweden
* Distributed under the terms shown in the file COPYRIGHT
* found in the root folder of this project or at
* http://eng.tada.se/osprojects/COPYRIGHT.html
*/
package org.postgresql.pljava.management;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.logging.Logger;
/**
* This class deals with parsing and executing the deployment descriptor as
* defined in ISO/IEC 9075-13:2003. It has the following format:
* <descriptor file> ::=
* SQLActions <left bracket> <right bracket> <equal sign>
* { [ <double quote> <action group> <double quote>
* [ <comma> <double quote> <action group> <double quote> ] ] }
*
* <action group> ::=
* <install actions>
* | <remove actions>
*
* <install actions> ::=
* BEGIN INSTALL [ <command> <semicolon> ]... END INSTALL
*
* <remove actions> ::=
* BEGIN REMOVE [ <command> <semicolon> ]... END REMOVE
*
* <command> ::=
* <SQL statement>
* | <implementor block>
*
* <SQL statement> ::= <SQL token>...
*
* <implementor block> ::=
* BEGIN <implementor name> <SQL token>... END <implementor name>
*
* <implementor name> ::= <identifier>
*
* <SQL token> ::= an SQL lexical unit specified by the term "<token>" in
* Subclause 5.2, "<token>" and "<separator>", in ISO/IEC 9075-2.
*
* @author Thomas Hallgren
*/
public class SQLDeploymentDescriptor
{
private final ArrayList m_installCommands = new ArrayList();
private final ArrayList m_removeCommands = new ArrayList();
private final StringBuffer m_buffer = new StringBuffer();
private final char[] m_image;
private final String m_implementorName;
private final Logger m_logger;
private int m_position = 0;
private String sessionUser = null;
private String currentUser = null;
private boolean executeAs = false;
private String executeAsFunctionName = null;
/**
* Parses the deployment descriptor descImage using
* implementorName as discriminator for implementor specific
* blocks. The install and remove blocks are remembered for later execution
* with calls to {@link #install install()} and {@link #remove remove()}.
* @param descImage The image to parse
* @param implementorName The discriminator to use for implementor blocks
* @throws ParseException If a parse error is encountered
*/
public SQLDeploymentDescriptor(String descImage, String implementorName)
throws ParseException
{
m_image = descImage.toCharArray();
m_implementorName = implementorName;
m_logger = Logger.getAnonymousLogger();
this.readDescriptor();
}
/**
* Executes the INSTALL actions.
* @param conn The connection to use for the execution.
* @throws SQLException
*/
public void install(Connection conn)
throws SQLException
{
this.executeArray(m_installCommands, conn);
}
/**
* Executes the REMOVE actions.
* @param conn The connection to use for the execution.
* @throws SQLException
*/
public void remove(Connection conn)
throws SQLException
{
this.executeArray(m_removeCommands, conn);
}
/**
* Returns the original image.
*/
public String toString()
{
return new String(m_image);
}
private void executeArray(ArrayList array, Connection conn)
throws SQLException
{
m_logger.entering("org.postgresql.pljava.management.SQLDeploymentDescriptor", "executeArray");
Statement stmt = conn.createStatement();
PreparedStatement pstmt = null;
ResultSet rs = stmt.executeQuery(
"SELECT session_user, current_user");
if (rs.next())
{
sessionUser = rs.getString(1);
currentUser = rs.getString(2);
rs.close();
}
else
{
rs.close();
throw new SQLException(
"Error obtaining session/current user");
}
executeAs = !currentUser.equals(sessionUser);
if (executeAs)
{
createExecuteAsFunction(stmt);
}
int top = array.size();
for(int idx = 0; idx < top; ++idx)
{
String cmd = (String)array.get(idx);
m_logger.finer(cmd);
if (executeAs)
{
pstmt = conn.prepareStatement(
callExecuteAsFunction());
pstmt.setString(1, cmd);
//This strange looking construction is
//here because otherwise nothing happend -
//neither an error not the desired action
pstmt.executeQuery().next();
//This did not work:
//pstmt.execute();
pstmt.close();
}
else
{
stmt.execute(cmd);
}
}
if (executeAs)
{
dropExecuteAsFunction(stmt);
}
stmt.close();
m_logger.exiting("org.postgresql.pljava.management.SQLDeploymentDescriptor", "executeArray");
}
private void createExecuteAsFunction(Statement stmt) throws SQLException
{
//Generate a temporary function name, which is 100% unique.
//This is done by
// backend pid => no confilct with other backends
// current timestamp => no conflict in quick cycles
// random number => no conflic in extra quick cycles
//Thr problem is that if we used only the backend pid,
//we would get "Cache lookup failed" error in some cases.
//When using absolutely unique function IDs, this error
//never occurs.
String uniqueId = null;
ResultSet rs = stmt.executeQuery(
"select pg_backend_pid()||'_'||" +
"trunc(extract(epoch from current_timestamp)*10000)||" +
"'_'||" +
"trunc(random()*100000)");
if (rs.next())
{
uniqueId = rs.getString(1);
executeAsFunctionName =
"\"sqlj\".\"execute_as_" +
sessionUser + "_pid_" + uniqueId + "\"";
stmt.execute(
"CREATE OR REPLACE FUNCTION " +
executeAsFunctionName +
"(varchar) RETURNS VOID AS " +
"'org.postgresql.pljava.management.Commands.executeAs'" +
"LANGUAGE 'java' VOLATILE SECURITY DEFINER");
stmt.execute(
"ALTER FUNCTION " +
executeAsFunctionName + "(varchar)" +
" OWNER TO \"" + sessionUser + "\"");
rs.close();
}
else
{
rs.close();
throw new SQLException("Cannot get unique function ID");
}
}
private String callExecuteAsFunction()
{
return "SELECT " + executeAsFunctionName + "(?)";
}
private void dropExecuteAsFunction(Statement stmt) throws SQLException
{
stmt.execute(
"DROP FUNCTION " + executeAsFunctionName + "(varchar)");
}
private ParseException parseError(String msg)
{
return new ParseException(msg, m_position);
}
private void readDescriptor()
throws ParseException
{
m_logger.entering("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readDescriptor");
if(!"SQLACTIONS".equals(this.readIdentifier()))
throw this.parseError("Excpected keyword 'SQLActions'");
this.readToken('[');
this.readToken(']');
this.readToken('=');
this.readToken('{');
for(;;)
{
readActionGroup();
if(readToken("},") == '}')
{
// Only whitespace allowed now
//
int c = this.skipWhite();
if(c >= 0)
throw this.parseError(
"Extraneous characters at end of descriptor");
m_logger.exiting("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readDescriptor");
return;
}
}
}
private void readActionGroup()
throws ParseException
{
m_logger.entering("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readActionGroup");
this.readToken('"');
if(!"BEGIN".equals(this.readIdentifier()))
throw this.parseError("Excpected keyword 'BEGIN'");
ArrayList commands;
String actionType = this.readIdentifier();
if("INSTALL".equals(actionType))
commands = m_installCommands;
else if("REMOVE".equals(actionType))
commands = m_removeCommands;
else
throw this.parseError("Excpected keyword 'INSTALL' or 'REMOVE'");
for(;;)
{
String cmd = this.readCommand();
// Check if the cmd is in the form:
//
// ::=
// BEGIN ... END
//
// If it is, and if the implementor name corresponds to the one
// defined for this deployment, then extract the SQL token stream.
//
int top = cmd.length();
if(top >= 15
&& "BEGIN ".equalsIgnoreCase(cmd.substring(0, 6))
&& Character.isJavaIdentifierStart(cmd.charAt(6)))
{
int pos;
for(pos = 7; pos < top; ++pos)
if(!Character.isJavaIdentifierPart(cmd.charAt(pos)))
break;
if(cmd.charAt(pos) != ' ')
throw this.parseError(
"Expected whitespace after ");
String implementorName = cmd.substring(6, pos);
int iLen = implementorName.length();
int endNamePos = top - iLen;
int endPos = endNamePos - 4;
if(!implementorName.equalsIgnoreCase(cmd.substring(endNamePos))
|| !"END ".equalsIgnoreCase(cmd.substring(endPos, endNamePos)))
throw this.parseError(
"Implementor block must end with END ");
if(implementorName.equalsIgnoreCase(m_implementorName))
cmd = cmd.substring(pos+1, endPos);
else
// Block is not intended for this implementor.
//
cmd = null;
}
if(cmd != null)
commands.add(cmd.trim());
// Check if we have END INSTALL or END REMOVE
//
int savePos = m_position;
try
{
String tmp = this.readIdentifier();
if("END".equals(tmp))
{
tmp = this.readIdentifier();
if(actionType.equals(tmp))
break;
}
m_position = savePos;
}
catch(ParseException e)
{
m_position = savePos;
}
}
this.readToken('"');
m_logger.exiting("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readActionGroup");
}
private String readCommand()
throws ParseException
{
m_logger.entering("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readCommand");
int startQuotePos = -1;
int inQuote = 0;
int c = this.skipWhite();
m_buffer.setLength(0);
while(c != -1)
{
switch(c)
{
case '\\':
m_buffer.append((char)c);
c = this.read();
if(c != -1)
{
m_buffer.append((char)c);
c = this.read();
}
break;
case '"':
if(inQuote == 0)
throw parseError("Unexpected double qoute in SQL command");
m_buffer.append((char)c);
c = this.read();
break;
case '\'':
if(inQuote == 0)
{
startQuotePos = m_position;
inQuote = c;
}
else if(inQuote == c)
{
startQuotePos = -1;
inQuote = 0;
}
m_buffer.append((char)c);
c = this.read();
break;
case ';':
if(inQuote == 0)
{
String cmd = m_buffer.toString();
m_logger.exiting("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readCommand", cmd);
return cmd;
}
m_buffer.append((char)c);
c = this.read();
break;
default:
if(inQuote == 0 && Character.isWhitespace((char)c))
{
// Change multiple whitespace into one singe space.
//
m_buffer.append(' ');
c = this.skipWhite();
}
else
{
m_buffer.append((char)c);
c = this.read();
}
}
}
if(inQuote != 0)
throw this.parseError("Untermintated " + (char)inQuote +
" starting at position " + startQuotePos);
throw this.parseError("Unexpected EOF. Expecting ';' to end command");
}
private int skipWhite()
throws ParseException
{
int c;
for(;;)
{
c = this.read();
if(c >= 0 && Character.isWhitespace((char)c))
continue;
if(c == '/')
{
switch(this.peek())
{
// "//" starts a line comment. Skip until end of line.
//
case '/':
this.skip();
for(;;)
{
c = this.read();
switch(c)
{
case '\n':
case '\r':
case -1:
break;
default:
continue;
}
break;
}
continue;
// "/*" starts a line comment. Skip until "*/"
//
case '*':
this.skip();
for(;;)
{
c = this.read();
switch(c)
{
case -1:
throw this.parseError(
"Unexpected EOF when expecting end of multi line comment");
case '*':
if(this.peek() == '/')
{
this.skip();
break;
}
continue;
default:
continue;
}
break;
}
continue;
}
}
break;
}
return c;
}
private String readIdentifier()
throws ParseException
{
int c = this.skipWhite();
if(c < 0)
throw this.parseError("Unexpected EOF when expecting start of identifier");
char ch = (char)c;
if(!Character.isJavaIdentifierStart(ch))
throw this.parseError(
"Syntax error at '" + ch +
"', expected identifier");
m_buffer.setLength(0);
m_buffer.append(ch);
for(;;)
{
c = this.peek();
if(c < 0)
break;
ch = (char)c;
if(Character.isJavaIdentifierPart(ch))
{
m_buffer.append(ch);
this.skip();
continue;
}
break;
}
return m_buffer.toString().toUpperCase();
}
private char readToken(String tokens)
throws ParseException
{
int c = this.skipWhite();
if(c < 0)
throw this.parseError("Unexpected EOF when expecting one of \"" + tokens + '"');
char ch = (char)c;
if(tokens.indexOf(ch) < 0)
throw this.parseError(
"Syntax error at '" + ch +
"', expected one of '" + tokens + "'");
return ch;
}
private char readToken(char token)
throws ParseException
{
int c = this.skipWhite();
if(c < 0)
throw this.parseError("Unexpected EOF when expecting token '" + token + '\'');
char ch = (char)c;
if(ch != token)
throw this.parseError(
"Syntax error at '" + ch +
"', expected '" + token + "'");
return ch;
}
private int peek()
{
return (m_position >= m_image.length) ? -1 : m_image[m_position];
}
private void skip()
{
m_position++;
}
private int read()
{
int pos = m_position++;
return (pos >= m_image.length) ? -1 : m_image[pos];
}
}
src/java/pljava/org/postgresql/pljava/management/Commands.java 0100664 0000764 0000764 00000052013 10177442174 024123 0 ustar owner owner /*
* Copyright (c) 2004, 2005 TADA AB - Taby Sweden
* Distributed under the terms shown in the file COPYRIGHT
* found in the root folder of this project or at
* http://eng.tada.se/osprojects/COPYRIGHT.html
*/
package org.postgresql.pljava.management;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import org.postgresql.pljava.internal.Oid;
/**
* This methods of this class are implementations of SQLJ commands.
* SQJL functions
* install_jar
* The install_jar command loads a jarfile from a location appointed by an URL
* into the SQLJ jar repository. It is an error if a jar with the given name
* already exists in the repository.
* Usage
* SELECT sqlj.install_jar(<jar_url>, <jar_name>, ;<deploy>);
*
* Parameters
*
*
* | jar_url |
* The URL that denotes the location of the jar that should be loaded
* |
*
*
* | jar_name |
* This is the name by which this jar can be referenced once it has been
* loaded |
*
*
* | deploy |
* True if the jar should be deployed according to a {@link
* org.postgresql.pljava.management.SQLDeploymentDescriptor deployment descriptor},
* false otherwise |
*
*
* replace_jar
* The replace_jar will replace a loaded jar with another jar. Use this command
* to update already loaded files. It's an error if the jar is not found.
* Usage
* SELECT sqlj.replace_jar(<jar_url>, <jar_name>, ;<redeploy>);
*
* Parameters
*
*
* | jar_url |
* The URL that denotes the location of the jar that should be loaded
* |
*
*
* | jar_name |
* The name of the jar to be replaced |
*
*
* | redeploy |
* True if the old and new jar should be undeployed and deployed according
* to their respective {@link
* org.postgresql.pljava.management.SQLDeploymentDescriptor deployment descriptors},
* false otherwise |
*
*
* remove_jar
* The remove_jar will drop the jar from the jar repository. Any classpath that
* references this jar will be updated accordingly. It's an error if the jar is
* not found.
* Usage
* SELECT sqlj.remove_jar(<jar_name>, <undeploy>);
*
* Parameters
*
*
* | jar_name |
* The name of the jar to be removed |
*
*
* | undeploy |
* True if the jar should be undeployed according to its {@link
* org.postgresql.pljava.management.SQLDeploymentDescriptor deployment descriptor},
* false otherwise |
*
*
* get_classpath
* The get_classpath will return the classpath that has been defined for the
* given schema or NULL if the schema has no classpath. It's an error if the
* given schema does not exist.
* Usage
* SELECT sqlj.get_classpath(<schema>);
*
* Parameters
*
*
* | schema |
* The name of the schema |
*
*
* set_classpath
* The set_classpath will define a classpath for the given schema. A classpath
* consists of a colon separated list of jar names. It's an error if the given
* schema does not exist or if one or more jar names references non existent
* jars.
* Usage
* SELECT sqlj.set_classpath(<schema>, <classpath>);
*
* Parameters
*
*
* | schema |
* The name of the schema |
*
*
* | classpath |
* The colon separated list of jar names |
*
*
*
* @author Thomas Hallgren
*/
public class Commands
{
/**
* Installs a new Jar in the database jar repository under name jarName.
* Once installed classpaths can be defined that refrences this jar. This
* method is exposed in SQL as
* sqlj.install_jar(VARCHAR, VARCHAR, BOOLEAN).
*
* @param urlString
* The location of the jar that will be installed.
* @param jarName
* The name by which the system will refer to this jar.
* @param deploy
* If set, execute install commands found in the deployment descriptor.
* @throws SQLException
* if the jarName contains characters that are
* invalid or if the named jar already exists in the system.
* @see #setClassPath
*/
public static void installJar(String urlString, String jarName, boolean deploy)
throws SQLException
{
assertJarName(jarName);
Connection conn = DriverManager.getConnection("jdbc:default:connection");
try
{
if(getJarId(conn, jarName) >= 0)
throw new SQLException("A jar named '" + jarName + "' already exists");
PreparedStatement stmt = conn.prepareStatement(
"INSERT INTO sqlj.jar_repository(jarName, jarOrigin) VALUES(?, ?)");
try
{
stmt.setString(1, jarName);
stmt.setString(2, urlString);
if(stmt.executeUpdate() != 1)
throw new SQLException("Jar repository insert did not insert 1 row");
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
int jarId = getJarId(conn, jarName);
if(jarId < 0)
throw new SQLException("Unable to obtain id of '" + jarName + "'");
addClassImages(conn, jarId, urlString);
if(deploy)
deployInstall(conn, jarId);
}
finally
{
try { conn.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
/**
* Replaces the image of jar named jarName in the database jar
* repository. This method is exposed in SQL as
* sqlj.replace_jar(VARCHAR, VARCHAR, BOOLEAN).
* @param urlString The location of the jar that will be installed.
* @param jarName The name by which the system referes this jar.
* @param redeploy If set, execute remove commands found in the deployment
* descriptor of the old jar and install commands found in the deployment
* descriptor of the new jar.
* @throws SQLException if the named jar cannot be found in the repository.
*/
public static void replaceJar(String urlString, String jarName, boolean redeploy)
throws SQLException
{
Connection conn = DriverManager.getConnection("jdbc:default:connection");
try
{
int jarId = getJarId(conn, jarName);
if(jarId < 0)
throw new SQLException("No Jar named '" + jarName + "' is known to the system");
if(redeploy)
deployRemove(conn, jarId);
PreparedStatement stmt = conn.prepareStatement(
"UPDATE sqlj.jar_repository SET jarOrigin = ?, deploymentDesc = NULL WHERE jarId = ?");
try
{
stmt.setString(1, urlString);
stmt.setInt(2, jarId);
if(stmt.executeUpdate() != 1)
throw new SQLException("Jar repository update did not update 1 row");
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
stmt = conn.prepareStatement("DELETE FROM sqlj.jar_entry WHERE jarId = ?");
try
{
stmt.setInt(1, jarId);
stmt.executeUpdate();
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
addClassImages(conn, jarId, urlString);
if(redeploy)
deployInstall(conn, jarId);
}
finally
{
try { conn.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
/**
* Removes the jar named jarName from the database jar
* repository. Class path entries that references this jar will also be
* removed (just the entry, not the whole path). This method is exposed in
* SQL as sqlj.remove_jar(VARCHAR, BOOLEAN).
* @param jarName The name by which the system referes this jar.
* @param undeploy If set, execute remove commands found in the deployment
* descriptor of the jar.
* @throws SQLException if the named jar cannot be found in the repository.
*/
public static void removeJar(String jarName, boolean undeploy)
throws SQLException
{
assertJarName(jarName);
Connection conn = DriverManager.getConnection("jdbc:default:connection");
try
{
int jarId = getJarId(conn, jarName);
if(jarId < 0)
throw new SQLException("No Jar named '" + jarName + "' is known to the system");
if(undeploy)
deployRemove(conn, jarId);
PreparedStatement stmt = conn.prepareStatement(
"DELETE FROM sqlj.jar_repository WHERE jarId = ?");
try
{
stmt.setInt(1, jarId);
if(stmt.executeUpdate() != 1)
throw new SQLException("Jar repository update did not update 1 row");
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
finally
{
try { conn.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
/**
* Define the class path to use for Java functions, triggers, and procedures
* that are created in the schema named schemaName
*
* This method is exposed in SQL as sqlj.set_classpath(VARCHAR, VARCHAR).
*
* @param schemaName
* Name of the schema for which this path is valid.
* @param path
* Colon separated list of names. Each name must denote the name
* of a jar that is present in the jar repository.
* @throws SQLException
* If no schema can be found with the givene name, or if one or
* several names of the path denotes a nonexistant jar file.
*/
public static void setClassPath(String schemaName, String path)
throws SQLException
{
Connection conn = DriverManager.getConnection("jdbc:default:connection");
try
{
if(schemaName == null || schemaName.length() == 0)
schemaName = "public";
else
schemaName = schemaName.toLowerCase();
if(getSchemaId(conn, schemaName) == null)
throw new SQLException("No such schema: " + schemaName);
PreparedStatement stmt;
ArrayList entries = null;
if(path != null && path.length() > 0)
{
// Collect and verify that all entries in the path represents a
// valid jar
//
entries = new ArrayList();
stmt = conn.prepareStatement(
"SELECT jarId FROM sqlj.jar_repository WHERE jarName = ?");
try
{
for(;;)
{
int colon = path.indexOf(':');
String jarName;
if(colon >= 0)
{
jarName = path.substring(0, colon);
path = path.substring(colon + 1);
}
else
jarName = path;
int jarId = getJarId(stmt, jarName);
if(jarId < 0)
throw new SQLException("No such jar: " + jarName);
entries.add(new Integer(jarId));
if(colon < 0)
break;
}
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
// Delete the old classpath
//
stmt = conn.prepareStatement(
"DELETE FROM sqlj.classpath_entry WHERE schemaName = ?");
try
{
stmt.setString(1, schemaName);
stmt.executeUpdate();
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
if(entries != null)
{
// Insert the new path.
//
stmt = conn.prepareStatement(
"INSERT INTO sqlj.classpath_entry(schemaName, ordinal, jarId) VALUES(?, ?, ?)");
try
{
int top = entries.size();
for(int idx = 0; idx < top; ++idx)
{
int jarId = ((Integer)entries.get(idx)).intValue();
stmt.setString(1, schemaName);
stmt.setInt(2, idx + 1);
stmt.setInt(3, jarId);
stmt.executeUpdate();
}
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
}
finally
{
try { conn.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
/**
* Return the classpath that has been defined for the schema named schemaName
*
* This method is exposed in SQL as sqlj.get_classpath(VARCHAR).
*
* @param schemaName
* Name of the schema for which this path is valid.
* @return The defined classpath or null if this schema has no classpath.
* @throws SQLException
*/
public static String getClassPath(String schemaName)
throws SQLException
{
Connection conn = DriverManager.getConnection("jdbc:default:connection");
try
{
if(schemaName == null || schemaName.length() == 0)
schemaName = "public";
else
schemaName = schemaName.toLowerCase();
PreparedStatement stmt = conn.prepareStatement(
"SELECT r.jarName" +
" FROM sqlj.jar_repository r INNER JOIN sqlj.classpath_entry c ON r.jarId = c.jarId" +
" WHERE c.schemaName = ? ORDER BY c.ordinal");
try
{
stmt.setString(1, schemaName);
ResultSet rs = stmt.executeQuery();
try
{
StringBuffer buf = null;
while(rs.next())
{
if(buf == null)
buf = new StringBuffer();
else
buf.append(':');
buf.append(rs.getString(1));
}
return (buf == null) ? null : buf.toString();
}
finally
{
try { rs.close(); } catch(SQLException e) { /* ignore */ }
}
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore */ }
}
}
finally
{
try { conn.close(); } catch(SQLException e) { /* ignore */ }
}
}
protected static void addClassImages(Connection conn, int jarId, String urlString)
throws SQLException
{
InputStream urlStream = null;
PreparedStatement stmt = null;
PreparedStatement descIdStmt = null;
ResultSet rs = null;
try
{
int deployImageId = -1;
URL url = new URL(urlString);
urlStream = url.openStream();
byte[] buf = new byte[1024];
ByteArrayOutputStream img = new ByteArrayOutputStream();
stmt = conn.prepareStatement(
"INSERT INTO sqlj.jar_entry(entryName, jarId, entryImage) VALUES(?, ?, ?)");
JarInputStream jis = new JarInputStream(urlStream);
for(;;)
{
JarEntry je = jis.getNextJarEntry();
if(je == null)
break;
if(je.isDirectory())
continue;
String entryName = je.getName();
Attributes attrs = je.getAttributes();
boolean isDepDescr = false;
if(attrs != null)
{
isDepDescr = "true".equalsIgnoreCase(
attrs.getValue("SQLJDeploymentDescriptor"));
if(isDepDescr && deployImageId >= 0)
throw new SQLException("Only one SQLJDeploymentDescriptor allowed");
}
int nBytes;
img.reset();
while((nBytes = jis.read(buf)) > 0)
img.write(buf, 0, nBytes);
jis.closeEntry();
stmt.setString(1, entryName);
stmt.setInt(2, jarId);
stmt.setBytes(3, img.toByteArray());
if(stmt.executeUpdate() != 1)
throw new SQLException("Jar entry insert did not insert 1 row");
if(isDepDescr)
{
descIdStmt = conn.prepareStatement(
"SELECT entryId FROM sqlj.jar_entry" +
" WHERE jarId = ? AND entryName = ?");
descIdStmt.setInt(1, jarId);
descIdStmt.setString(2, entryName);
rs = descIdStmt.executeQuery();
if(!rs.next())
throw new SQLException("Failed to refecth row in sqlj.jar_entry");
deployImageId = rs.getInt(1);
}
}
if(deployImageId >= 0)
{
stmt.close();
stmt = conn.prepareStatement(
"UPDATE sqlj.jar_repository SET deploymentDesc = ? WHERE jarId = ?");
stmt.setInt(1, deployImageId);
stmt.setInt(2, jarId);
if(stmt.executeUpdate() != 1)
throw new SQLException("Jar repository update did not insert 1 row");
}
}
catch(IOException e)
{
throw new SQLException("I/O exception reading jar file: " + e.getMessage());
}
finally
{
if(urlStream != null)
try { urlStream.close(); } catch(IOException e) { /* ignore */ }
if(rs != null)
try { rs.close(); } catch(SQLException e) { /* ignore */ }
if(descIdStmt != null)
try { descIdStmt.close(); } catch(SQLException e) { /* ignore */ }
if(stmt != null)
try { stmt.close(); } catch(SQLException e) { /* ignore */ }
}
}
protected static void deployInstall(Connection conn, int jarId)
throws SQLException
{
SQLDeploymentDescriptor depDesc = getDeploymentDescriptor(conn, jarId);
if(depDesc != null)
depDesc.install(conn);
}
protected static void deployRemove(Connection conn, int jarId)
throws SQLException
{
SQLDeploymentDescriptor depDesc = getDeploymentDescriptor(conn, jarId);
if(depDesc != null)
depDesc.remove(conn);
}
/**
* Throws an exception if the given name cannot be used as the name of
* a jar.
* @param jarName The naem to check.
* @throws IOException
*/
protected static void assertJarName(String jarName)
throws SQLException
{
if(jarName != null)
{
int len = jarName.length();
if(len > 0 && Character.isJavaIdentifierStart(jarName.charAt(0)))
{
int idx = 1;
for(; idx < len; ++idx)
if(!Character.isJavaIdentifierPart(jarName.charAt(idx)))
break;
if(idx == len)
return;
}
}
throw new SQLException("The jar name '" + jarName + "' is not a valid name");
}
/**
* Returns the primary key identifier for the given Jar.
* @param conn The connection to use for the query.
* @param jarName The name of the jar.
* @return The primary key value of the given jar or -1 if no
* such jar is found.
* @throws SQLException
*/
protected static int getJarId(Connection conn, String jarName)
throws SQLException
{
PreparedStatement stmt = conn.prepareStatement(
"SELECT jarId FROM sqlj.jar_repository WHERE jarName = ?");
try
{
return getJarId(stmt, jarName);
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
protected static int getJarId(PreparedStatement stmt, String jarName)
throws SQLException
{
stmt.setString(1, jarName);
ResultSet rs = stmt.executeQuery();
try
{
if(!rs.next())
return -1;
return rs.getInt(1);
}
finally
{
try { rs.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
protected static SQLDeploymentDescriptor getDeploymentDescriptor(Connection conn, int jarId)
throws SQLException
{
PreparedStatement stmt = conn.prepareStatement(
"SELECT e.entryImage" +
" FROM sqlj.jar_repository r INNER JOIN sqlj.jar_entry e" +
" ON r.deploymentDesc = e.entryId" +
" WHERE r.jarId = ?");
try
{
stmt.setInt(1, jarId);
ResultSet rs = stmt.executeQuery();
try
{
if(!rs.next())
return null;
byte[] bytes = rs.getBytes(1);
if(bytes.length == 0)
return null;
// Accodring to the SQLJ standard, this entry must be
// UTF8 encoded.
//
return new SQLDeploymentDescriptor(new String(bytes, "UTF8"), "postgresql");
}
catch (UnsupportedEncodingException e)
{
// Excuse me? No UTF8 encoding?
//
throw new SQLException("JVM does not support UTF8!!");
}
catch(ParseException e)
{
throw new SQLException(e.getMessage() + " at " + e.getErrorOffset());
}
finally
{
try { rs.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
/**
* Returns the Oid for the given Schema.
* @param conn The connection to use for the query.
* @param schemaName The name of the schema.
* @return The Oid of the given schema or null if no such
* schema is found.
* @throws SQLException
*/
protected static Oid getSchemaId(Connection conn, String schemaName)
throws SQLException
{
PreparedStatement stmt = conn.prepareStatement(
"SELECT oid FROM pg_namespace WHERE nspname = ?");
try
{
stmt.setString(1, schemaName);
ResultSet rs = stmt.executeQuery();
try
{
if(!rs.next())
return null;
return (Oid)rs.getObject(1);
}
finally
{
try { rs.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
finally
{
try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ }
}
}
/**
Executes an SQL command. The purpose of this method is
to be executed as a "SECURITY DEFINER" function, so that
the effective user is different from session user.
*/
public static void executeAs(String cmd)
throws SQLException
{
Connection conn = DriverManager.getConnection("jdbc:default:connection");
Statement stmt = conn.createStatement();
try
{
stmt.executeUpdate(cmd);
}
catch(SQLException e)
{
throw new SQLException(
"Cannot execute: " + cmd + ", " +
"reason: " + e.getMessage(),
e.getSQLState(),
e.getErrorCode());
}
finally
{
stmt.close();
}
}
}