/**
* Copyright (c) 2009--2016 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.common.db.datasource;
import org.apache.log4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
/**
* A contentHandler that knows how to parse the DataSource XML files.
*
* @version $Rev$
*/
class DataSourceParserHelper implements ContentHandler, Serializable {
private static final long serialVersionUID = 1L;
private static Logger logger = Logger.getLogger(DataSourceParserHelper.class);
private HashMap<String, ParsedQueryImpl> internalQueries;
private HashMap<String, ParsedMode> modes;
private ParsedModeImpl m = null;
private ParsedQueryImpl q = null;
private StringBuilder sqlBuilder = null;
/**
* Create a new DataSourceParserHelper
*/
DataSourceParserHelper() {
}
/**
* Get the modes Map
* @return the modes map.
*/
public HashMap<String, ParsedMode> getModes() {
return modes;
}
/** {@inheritDoc} */
public void characters(char[] text, int start, int length) throws SAXException {
if (sqlBuilder != null) {
sqlBuilder.append(text, start, length);
}
}
/** {@inheritDoc} */
public void startElement(String namespaceURI, String localName, String qualifiedName,
Attributes atts) {
logger.debug("startElement(" + localName + ")");
//sqlStatement = new StringBuffer();
if (localName.equals("datasource_modes")) {
if (m != null || q != null) {
throw new RuntimeException(
"'datasource_modes' element only valid at start of mode file.");
}
modes = new HashMap<String, ParsedMode>();
internalQueries = new HashMap<String, ParsedQueryImpl>();
}
else if (localName.equals("mode")) {
if (q != null) {
throw new RuntimeException(
"'mode' element not valid within mode or elaborator element.");
}
m = new ParsedModeImpl(atts, ParsedMode.ModeType.SELECT);
}
else if (localName.equals("callable-mode")) {
if (q != null) {
throw new RuntimeException("'callable-mode' element not valid within " +
"mode or elaborator element.");
}
m = new ParsedModeImpl(atts, ParsedMode.ModeType.CALLABLE);
}
else if (localName.equals("write-mode")) {
if (q != null) {
throw new RuntimeException("'write-mode' element not valid within mode " +
"or elaborator element.");
}
m = new ParsedModeImpl(atts, ParsedMode.ModeType.WRITE);
}
else if (localName.equals("query")) {
q = new ParsedQueryImpl(atts);
sqlBuilder = new StringBuilder();
}
else if (localName.equals("elaborator")) {
if (m == null) {
throw new RuntimeException(
"Elaborator can only be defined within a mode definition.");
}
q = new ParsedQueryImpl(atts);
sqlBuilder = new StringBuilder();
}
else {
throw new RuntimeException("Invalid element '" + localName + "'");
}
}
/** {@inheritDoc} */
public void endElement(String namespaceURI, String localName, String qualifiedName) {
logger.debug("endElement(" + localName + ")");
switch (localName) {
case "mode":
case "callable-mode":
case "write-mode":
modes.put(m.getName(), m);
m = null;
break;
case "query":
String querySql = sqlBuilder.toString().trim();
if (!querySql.isEmpty()) {
q.setSqlStatement(querySql);
}
if (m == null) {
ParsedQueryImpl pqi = getOrCreateInternalQuery(q);
pqi.setValues(q);
}
else {
if (querySql.isEmpty()) {
m.setParsedQuery(getOrCreateInternalQuery(q));
}
else {
m.setParsedQuery(q);
}
}
sqlBuilder = null;
q = null;
break;
case "elaborator":
String elabSql = sqlBuilder.toString().trim();
if (!elabSql.isEmpty()) {
q.setSqlStatement(elabSql);
}
// NOTE: m can't be null since we checked in startElement()
if (elabSql.isEmpty()) {
m.addElaborator(getOrCreateInternalQuery(q));
}
else {
m.addElaborator(q);
}
sqlBuilder = null;
q = null;
break;
case "datasource_modes":
if (q != null || m != null) {
throw new RuntimeException("Invalid xml");
}
break;
default:
throw new RuntimeException("Invalid end element '" + localName + "'");
}
}
private ParsedQueryImpl getOrCreateInternalQuery(ParsedQueryImpl parsedQuery) {
ParsedQueryImpl pqi = internalQueries.get(parsedQuery.getName());
if (pqi == null) {
pqi = parsedQuery;
internalQueries.put(pqi.getName(), pqi);
}
return pqi;
}
/** {@inheritDoc} */
public void endDocument() {
// Implement sanity check? Look for queries with names but no sql
// statement.
ArrayList<String> errors = new ArrayList<String>();
String errmsg;
for (String modeKey : modes.keySet()) {
logger.debug("Sanity check for mode " + modeKey);
ParsedMode pm = modes.get(modeKey);
if (pm == null) {
errors.add("ParsedMode is null for key '" + modeKey + "'");
}
logger.debug("Checking query");
errmsg = sanityCheckParsedQuery(pm.getParsedQuery(), modeKey);
if (errmsg != null) {
errors.add(errmsg);
}
logger.debug("Checking elaborators");
for (ParsedQuery pq : pm.getElaborators()) {
errmsg = sanityCheckParsedQuery(pq, modeKey);
if (errmsg != null) {
errors.add(errmsg);
}
}
}
if (!errors.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (String e : errors) {
sb.append(e).append("/n");
}
throw new RuntimeException(sb.toString());
}
}
private String sanityCheckParsedQuery(ParsedQuery pq, String modeKey) {
if (pq == null) {
return "ParsedQuery is null for key '" + modeKey + "'";
}
if (pq.getSqlStatement() == null) {
return "ParsedQuery '" + pq.getName() + "' for key '" + modeKey +
"' has null sql statement - instance " + pq;
}
if (pq.getSqlStatement().trim().equals("")) {
return "ParsedQuery '" + pq.getName() + "' for key '" + modeKey +
"' has empty sql statement - instance " + pq;
}
return null;
}
// do-nothing methods
/** {@inheritDoc} */
public void setDocumentLocator(Locator locator) {
}
/** {@inheritDoc} */
public void startDocument() {
}
/** {@inheritDoc} */
public void startPrefixMapping(String prefix, String uri) {
}
/** {@inheritDoc} */
public void endPrefixMapping(String prefix) {
}
/** {@inheritDoc} */
public void ignorableWhitespace(char[] text, int start, int length)
throws SAXException {
}
/** {@inheritDoc} */
public void processingInstruction(String target, String data) {
}
/** {@inheritDoc} */
public void skippedEntity(String name) {
}
/**
* The DataSourceParserHelper creates an instance of this ParsedQueryImpl
* for each query it parses. The ParsedQuery is used by ModeFactory when
* creating new Mode objects and the elaborators and queries contained
* therein.
*/
private class ParsedQueryImpl implements ParsedQuery, Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String alias;
private String elaboratorJoinColumn;
private List<String> parameterList;
private boolean multiple;
private String sqlStatement;
/**
* Constructor used to create an instance of this class and reading
* member variable values from Attributes while parsing the xml.
* @param parsedAttributes
*/
private ParsedQueryImpl(Attributes parsedAttributes) {
name = parsedAttributes.getValue("name");
if (name == null) {
name = "";
}
alias = parsedAttributes.getValue("alias");
if (alias == null) {
alias = "";
}
String column = parsedAttributes.getValue("column");
elaboratorJoinColumn = (column == null) ? "id" : column.toLowerCase();
String mult = parsedAttributes.getValue("multiple");
multiple = (mult != null && mult.equals("t"));
parameterList = new ArrayList<String>();
String parameters = parsedAttributes.getValue("params");
if (parameters != null && !parameters.isEmpty()) {
StringTokenizer st = new StringTokenizer(parameters, ",");
while (st.hasMoreTokens()) {
parameterList.add(st.nextToken().trim());
}
}
}
private void setValues(ParsedQueryImpl parsedQuery) {
// name must already be set.
alias = parsedQuery.getAlias();
elaboratorJoinColumn = parsedQuery.getElaboratorJoinColumn();
multiple = parsedQuery.isMultiple();
parameterList = parsedQuery.getParameterList();
sqlStatement = parsedQuery.getSqlStatement();
}
private void setSqlStatement(String newSqlStatement) {
this.sqlStatement = newSqlStatement;
}
@Override
public String getName() {
return name;
}
@Override
public String getAlias() {
return alias;
}
@Override
public String getSqlStatement() {
return sqlStatement;
}
@Override
public String getElaboratorJoinColumn() {
return elaboratorJoinColumn;
}
@Override
public List<String> getParameterList() {
return parameterList;
}
@Override
public boolean isMultiple() {
return multiple;
}
}
/**
* The DataSourceParserHelper creates an instance of this ParsedModeImpl for
* each mode it parses. The ParsedMode is used by ModeFactory when creating
* new Mode objects and the elaborators and queries contained therein.
*/
private class ParsedModeImpl implements ParsedMode, Serializable {
private static final long serialVersionUID = 1L;
private String name;
private ModeType modeType;
private ParsedQuery parsedQuery;
// SELECT modes only
private String classname;
private List<ParsedQuery> elaborators = new ArrayList<ParsedQuery>();
private ParsedModeImpl(Attributes parsedAttributes, ModeType newModeType) {
name = parsedAttributes.getValue("name");
this.modeType = newModeType;
if (newModeType == ModeType.SELECT) {
classname = parsedAttributes.getValue("class");
}
}
private void setParsedQuery(ParsedQuery newParsedQuery) {
this.parsedQuery = newParsedQuery;
}
private void addElaborator(ParsedQuery elaborator) {
if (modeType != ModeType.SELECT) {
throw new IllegalArgumentException(
"Mode " + name + " must be ModeType SELECT to add elaborator.");
}
elaborators.add(elaborator);
}
@Override
public String getName() {
return name;
}
@Override
public ModeType getType() {
return modeType;
}
@Override
public ParsedQuery getParsedQuery() {
return parsedQuery;
}
@Override
public String getClassname() {
return classname;
}
@Override
public List<ParsedQuery> getElaborators() {
return elaborators;
}
}
} // end DataSourceParserHelper