/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.service.routines; import com.foundationdb.ais.model.Routine; import com.foundationdb.ais.model.SQLJJar; import com.foundationdb.ais.model.TableName; import com.foundationdb.server.error.InvalidSQLJDeploymentDescriptorException; import com.foundationdb.sql.StandardException; import com.foundationdb.sql.aisddl.AISDDL; import com.foundationdb.sql.aisddl.DDLHelper; import com.foundationdb.sql.parser.CreateAliasNode; import com.foundationdb.sql.parser.DDLStatementNode; import com.foundationdb.sql.parser.DropAliasNode; import com.foundationdb.sql.parser.SQLParserException; import com.foundationdb.sql.parser.StatementNode; import com.foundationdb.sql.server.ServerQueryContext; import com.foundationdb.sql.server.ServerSession; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.regex.Pattern; public class SQLJJarDeployer { public static final String MANIFEST_ATTRIBUTE = "SQLJDeploymentDescriptor"; public static final String DESCRIPTOR_FILE = "\\s*SQLActions\\s*\\[\\s*\\]\\s*\\=\\s*\\{.*\\}\\s*"; public static final String BEGIN_INSTALL = "BEGIN INSTALL"; public static final String END_INSTALL = "END INSTALL"; public static final String BEGIN_REMOVE = "BEGIN REMOVE"; public static final String END_REMOVE = "END REMOVE"; private ServerQueryContext context; private TableName jarName; private ServerSession server; public SQLJJarDeployer(ServerQueryContext context, TableName jarName) { this.context = context; this.jarName = jarName; server = context.getServer(); } public void deploy() { loadDeploymentDescriptor(false); } public void undeploy() { loadDeploymentDescriptor(true); } private void loadDeploymentDescriptor(boolean undeploy) { if (jarName == null) return; try (JarFile jarFile = server.getRoutineLoader().openSQLJJarFile(context.getSession(), jarName)) { Manifest manifest = jarFile.getManifest(); for (Map.Entry<String,Attributes> entry : manifest.getEntries().entrySet()) { String val = entry.getValue().getValue(MANIFEST_ATTRIBUTE); if ((val != null) && Boolean.parseBoolean(val)) { JarEntry jarEntry = jarFile.getJarEntry(entry.getKey()); if (jarEntry != null) { InputStream istr = jarFile.getInputStream(jarEntry); loadDeploymentDescriptor(istr, undeploy); break; } } } } catch (IOException ex) { throw new InvalidSQLJDeploymentDescriptorException(jarName, ex); } } private void loadDeploymentDescriptor(InputStream istr, boolean undeploy) throws IOException { StringBuilder contents = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(istr, "UTF-8")); while (true) { String line = reader.readLine(); if (line == null) break; contents.append(line).append('\n'); } if (!Pattern.compile(DESCRIPTOR_FILE, Pattern.DOTALL).matcher(contents).matches()) throw new InvalidSQLJDeploymentDescriptorException(jarName, "Incorrect file format"); String header, footer; if (undeploy) { header = BEGIN_REMOVE; footer = END_REMOVE; } else { header = BEGIN_INSTALL; footer = END_INSTALL; } int start = contents.indexOf(header); if (start < 0) throw new InvalidSQLJDeploymentDescriptorException(jarName, "Actions not found"); start += header.length(); int end = contents.indexOf(footer, start); if (end < 0) throw new InvalidSQLJDeploymentDescriptorException(jarName, "Actions not terminated"); String sql = contents.substring(start, end); List<StatementNode> stmts; try { stmts = server.getParser().parseStatements(sql); } catch (SQLParserException ex) { throw new InvalidSQLJDeploymentDescriptorException(jarName, ex); } catch (StandardException ex) { throw new InvalidSQLJDeploymentDescriptorException(jarName, ex); } int nstmts = stmts.size(); List<DDLStatementNode> ddls = new ArrayList<>(nstmts); List<String> sqls = new ArrayList<>(nstmts); for (StatementNode stmt : stmts) { boolean stmtOkay = false, thisjarOkay = false; if (undeploy) { if (stmt instanceof DropAliasNode) { DropAliasNode dropAlias = (DropAliasNode)stmt; switch (dropAlias.getAliasType()) { case PROCEDURE: case FUNCTION: stmtOkay = true; { TableName routineName = DDLHelper.convertName(server.getDefaultSchemaName(), dropAlias.getObjectName()); Routine routine = server.getAIS().getRoutine(routineName); if (routine != null) { SQLJJar sqljjar = routine.getSQLJJar(); thisjarOkay = ((sqljjar != null) && jarName.equals(sqljjar.getName())); } } break; } } } else { if (stmt instanceof CreateAliasNode) { CreateAliasNode createAlias = (CreateAliasNode)stmt; switch (createAlias.getAliasType()) { case PROCEDURE: case FUNCTION: stmtOkay = true; if ((createAlias.getJavaClassName() != null) && createAlias.getJavaClassName().startsWith("thisjar:")) { createAlias.setUserData(jarName); thisjarOkay = true; } break; } } } if (!stmtOkay) throw new InvalidSQLJDeploymentDescriptorException(jarName, "Statement not allowed " + stmt.statementToString()); if (!thisjarOkay) throw new InvalidSQLJDeploymentDescriptorException(jarName, "Must refer to thisjar:"); ddls.add((DDLStatementNode)stmt); sqls.add(sql.substring(stmt.getBeginOffset(), stmt.getEndOffset() + 1)); } for (int i = 0; i < nstmts; i++) { AISDDL.execute(ddls.get(i), sqls.get(i), context); } } }