/**
* Copyright 2010-2015 Axel Fontaine
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.contrastsecurity.cassandra.migration.script;
import com.contrastsecurity.cassandra.migration.CassandraMigrationException;
import com.contrastsecurity.cassandra.migration.logging.Log;
import com.contrastsecurity.cassandra.migration.logging.LogFactory;
import com.contrastsecurity.cassandra.migration.utils.StringUtils;
import com.contrastsecurity.cassandra.migration.utils.scanner.Resource;
import com.datastax.driver.core.Session;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
/**
* Cql script containing a series of statements terminated by a delimiter (eg: ;).
* Single-line (--) and multi-line (/* * /) comments are stripped and ignored.
*/
public class CqlScript {
private static final Log LOG = LogFactory.getLog(CqlScript.class);
/**
* The cql statements contained in this script.
*/
private final List<String> cqlStatements;
/**
* The resource containing the statements.
*/
private final Resource resource;
/**
* Creates a new cql script from this source.
*
* @param cqlScriptSource The cql script as a text block with all placeholders already replaced.
*/
public CqlScript(String cqlScriptSource) {
this.cqlStatements = parse(cqlScriptSource);
this.resource = null;
}
/**
* Creates a new cql script from this resource.
*
* @param cqlScriptResource The resource containing the statements.
* @param encoding The encoding to use.
*/
public CqlScript(Resource cqlScriptResource, String encoding) {
String cqlScriptSource = cqlScriptResource.loadAsString(encoding);
this.cqlStatements = parse(cqlScriptSource);
this.resource = cqlScriptResource;
}
/**
* For increased testability.
*
* @return The cql statements contained in this script.
*/
public List<String> getCqlStatements() {
return cqlStatements;
}
/**
* @return The resource containing the statements.
*/
public Resource getResource() {
return resource;
}
/**
* Executes this script against the database.
* @param session Cassandra session
*/
public void execute(final Session session) {
for (String cqlStatement : cqlStatements) {
LOG.debug("Executing CQL: " + cqlStatement);
session.execute(cqlStatement);
}
}
/**
* Parses this script source into statements.
*
* @param cqlScriptSource The script source to parse.
* @return The parsed statements.
*/
/* private -> for testing */
List<String> parse(String cqlScriptSource) {
return linesToStatements(readLines(new StringReader(cqlScriptSource)));
}
/**
* Turns these lines in a series of statements.
*
* @param lines The lines to analyse.
* @return The statements contained in these lines (in order).
*/
/* private -> for testing */
List<String> linesToStatements(List<String> lines) {
List<String> statements = new ArrayList<>();
Delimiter nonStandardDelimiter = null;
CqlStatementBuilder cqlStatementBuilder = new CqlStatementBuilder();
for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) {
String line = lines.get(lineNumber - 1);
if (cqlStatementBuilder.isEmpty()) {
if (!StringUtils.hasText(line)) {
// Skip empty line between statements.
continue;
}
Delimiter newDelimiter = cqlStatementBuilder.extractNewDelimiterFromLine(line);
if (newDelimiter != null) {
nonStandardDelimiter = newDelimiter;
// Skip this line as it was an explicit delimiter change directive outside of any statements.
continue;
}
cqlStatementBuilder.setLineNumber(lineNumber);
// Start a new statement, marking it with this line number.
if (nonStandardDelimiter != null) {
cqlStatementBuilder.setDelimiter(nonStandardDelimiter);
}
}
cqlStatementBuilder.addLine(line);
if (cqlStatementBuilder.isTerminated()) {
String cqlStatement = cqlStatementBuilder.getCqlStatement();
statements.add(cqlStatement);
LOG.debug("Found statement: " + cqlStatement);
cqlStatementBuilder = new CqlStatementBuilder();
} else if (cqlStatementBuilder.canDiscard()) {
cqlStatementBuilder = new CqlStatementBuilder();
}
}
// Catch any statements not followed by delimiter.
if (!cqlStatementBuilder.isEmpty()) {
statements.add(cqlStatementBuilder.getCqlStatement());
}
return statements;
}
/**
* Parses the textual data provided by this reader into a list of lines.
*
* @param reader The reader for the textual data.
* @return The list of lines (in order).
* @throws IllegalStateException Thrown when the textual data parsing failed.
*/
private List<String> readLines(Reader reader) {
List<String> lines = new ArrayList<String>();
BufferedReader bufferedReader = new BufferedReader(reader);
String line;
try {
while ((line = bufferedReader.readLine()) != null) {
lines.add(line);
}
} catch (IOException e) {
String message = resource == null ?
"Unable to parse lines" :
"Unable to parse " + resource.getLocation() + " (" + resource.getLocationOnDisk() + ")";
throw new CassandraMigrationException(message, e);
}
return lines;
}
}