/******************************************************************************* * Copyright (c) 2006 Oracle Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Oracle Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.bpel.validator.xpath0; /** * Validator BPEL model dependency */ import javax.xml.namespace.QName; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.eclipse.bpel.validator.model.ARule; import org.eclipse.bpel.validator.model.IModelQuery; import org.eclipse.bpel.validator.model.IModelQueryLookups; import org.eclipse.bpel.validator.model.INode; import org.eclipse.bpel.validator.model.IProblem; import org.eclipse.bpel.validator.model.Validator; /** * This validator validates the XPath expressions used in the BPEL source. * <p> * The validation consists of 2 steps: * <ul> * <li>Compilation and syntax check,and * <li>Static analysis on the expression * </ul> * <p> * The static analysis is performed by visiting the parsed expression tree and * checking certain things against the actual BPEL model. * <p> * The nodes which may contain XPath expressions in BPEL 2.0 and their expected * type is as follows: * * <ul> * <li> branches - unsigned integer * <li> condition - boolean * <li> finalCounterValue - unsigned integer * <li> for - duration * <li> from - any * <li> joinCondition - boolean * <li> repeatEvery - duration * <li> startCounterValue - unsigned integer * <li> to - variable reference expression ($var/foo) * <li> transitionCondition - boolean * <li> until - deadline * * </ul> * * * <p> * * @author Michal Chmielewski (michal.chmielewski@oracle.com) * @date Sep 27, 2006 * */ @SuppressWarnings( { "nls", "boxing" }) public class XPathValidator extends Validator { /** The string format of the XPath expression */ String exprString; String exprStringTrimmed; /** The node name in which this expression is placed */ protected String fNodeName; /** expression name by node name */ protected String fExprByNode; /** parent node */ protected INode fParentNode; protected XPathExpression xpathExpression; /** * Start the validation of this XPathExpression */ @Override public void start() { super.start(); exprString = mModelQuery.lookup(mNode, IModelQueryLookups.LOOKUP_TEXT_TEXT, null, null); exprStringTrimmed = exprString.trim(); fNodeName = toString(mNode.nodeName()); fParentNode = mNode.parentNode(); /** fExprByNode is a key to a localization map */ fExprByNode = "text.node." + mNode.nodeName().getLocalPart(); } /** * Do a quick sanity check. */ @ARule( desc = "XPath sanity check", author = "michal.chmielewski@oracle.com", date = "02/16/2007", errors="XPATH_EMPTY_EXPRESSION" ) public void rule_SanityCheck_1() { IProblem problem; if (isEmptyOrWhitespace(exprString)) { problem = createError(); problem.fill("XPATH_EMPTY_EXPRESSION", fNodeName, fExprByNode); exprString = null; disableRules(); } } /** * Check syntax of this expression ... * */ @ARule( desc = "XPath syntax check", author = "michal.chmielewski@oracle.com", date = "02/16/2007", errors="XPATH_EXPRESSION_SYNTAX" ) public void rule_CheckExpressionSyntax_2() { if (exprString == null) { return; } IProblem problem; try { XPathFactory factory = XPathFactory.newInstance(); xpathExpression = factory.newXPath().compile(exprString); } catch (XPathExpressionException e) { String message = getMessageFrom ( e ); problem = createError(); problem.fill("XPATH_EXPRESSION_SYNTAX", fNodeName, exprStringTrimmed, fExprByNode, message); // repointOffsets(problem, e.getPosition(), e.getPosition() + 3); // TODO: Position in the expression ... ? disableRules(); } } /** * Sets the value of the expression result. This is run as the last rule in * pass1 and it examines the top element on the stack that the * visitor/validator combination has produced. * */ public void rule_ExpressionType_999999() { setValue("expression.type",null); } String getMessageFrom (Throwable t) { int i = 10; while (t.getCause() != null && i > 0) { t = t.getCause(); i--; } return t.getMessage(); } // protected void repointOffsets(IProblem problem, Expr expr) { // if (expr.getPosition() < 0 || expr.getEndPosition() < 0) { // return; // } // repointOffsets(problem, expr.getPosition(), expr.getEndPosition()); // } /** * @param problem * @param offset * @param offsetEnd */ protected void repointOffsets(IProblem problem, int offset, int offsetEnd) { int charStart = mModelQuery.lookup(mNode, IModelQueryLookups.LOOKUP_NUMBER_CHAR_START_2, -1); int charEnd = mModelQuery.lookup(mNode, IModelQueryLookups.LOOKUP_NUMBER_CHAR_END_2, -1); int lineNo = mModelQuery.lookup(mNode, IModelQueryLookups.LOOKUP_NUMBER_LINE_NO_2, -1); int columnNo = mModelQuery.lookup(mNode, IModelQueryLookups.LOOKUP_NUMBER_COLUMN_NO_2, -1); if (charStart < 0 || charEnd < 0) { return; } charStart += offset; charEnd += offsetEnd; // Technically that should be it, but there are 3 issues: // - have to re-point column // - have to re-point row // - have to adjust for \n in charStart and charEnd (JAXEN issue). for (int i = 0, j = Math.min(exprString.length(), offset); i < j; i++) { if (exprString.charAt(i) == '\n') { // This is a JAXEN problem/hack charStart += 1; if (lineNo > 0) { lineNo += 1; } columnNo = 0; } if (columnNo >= 0) { columnNo += 1; } } // Now do the same thing for charEnd for (int i = 0, j = Math.min(exprString.length(), offsetEnd); i < j; i++) { // This is a JAXEN problem/hack if (exprString.charAt(i) == '\n') { charEnd += 1; } } problem.setAttribute(IProblem.CHAR_START, charStart); problem.setAttribute(IProblem.CHAR_END, charEnd); if (lineNo > 0) { problem.setAttribute(IProblem.LINE_NUMBER, lineNo); } if (columnNo > 0) { problem.setAttribute(IProblem.COLUMN_NUMBER, columnNo); } } /** * Perform static analysis of this XPath expression. * */ @ARule( sa = 0, desc = "Create the static analysis visitor for expression analysis", author = "michal.chmielewski@oracle.com", date = "01/30/2007", errors="XPATH_EXPRESSION_SYNTAX,XPATH_EXPRESSION_TYPE,XPATH_FN_ARGS,"+ "XPATH_FN_LITERAL_ARGS,XPATH_UNDEF_VARIABLE,XPATH_UNRESOLVED_NAMESPACE_PREFIX"+ "XPATH_INVALID_VARREF_PREFIX,XPATH_VARIABLE_PART,XPATH_UNDEF_VARIABLE_PART"+ "XPATH_FUNCTION_MIN_ARGS,XPATH_FUNCTION_MAX_ARGS,XPATH_EMPTY_EXPRESSION", warnings="XPATH_URI_SYNTAX,XPATH_FUNCTION_UNKNOWN,XPATH_FUNCTION_DEPRECATED,XPATH_EXPRESSION_TYPE" ) public void rule_StaticXPathExpressionAnalysis_10() { // mVisitor = new XPathVisitor(this); // INode typeNode = getValue("expression.type", null); // if (typeNode != null) { // mVisitor.contextPush(typeNode); // } } /** * Check the deadline expression. */ public void checkDeadlineExpression() { } /** * Check the duration expression variant. */ public void checkDurationExpression() { } /** * Check boolean expressions */ public void checkBooleanExpression() { } /** * Check the integer expression. * * Not this is not a rule. */ public void checkIntegerExpression() { } boolean isNamespaceOK(QName qname) { if (qname == null) { return false; } return (isEmpty(qname.getNamespaceURI()) == isEmpty(qname.getPrefix())); } protected boolean isJoinCondition() { /** ND_ are QNames, so make sure you compare apples to apples ... */ return ND_JOIN_CONDITION.equals(mNode.nodeName()); } protected IModelQuery getModelQuery() { return mModelQuery; } protected INode getNode() { return mNode; } /** * This exists here for visibility to XPathVisitor. * * @see org.eclipse.bpel.validator.model.Validator#createError() */ @Override protected IProblem createError() { return super.createError(); } /** * This exists here for visibility to XPathVisitor. * * @see org.eclipse.bpel.validator.model.Validator#createInfo() */ @Override protected IProblem createInfo() { return super.createInfo(); } /** * This exists here for visibility to XPathVisitor. * * @see org.eclipse.bpel.validator.model.Validator#createWarning() */ @Override protected IProblem createWarning() { return super.createWarning(); } /** * @see org.eclipse.bpel.validator.model.Validator#runRules(java.lang.String, * java.lang.Object[]) */ @Override protected void runRules(String tag, Object... args) { // this exists here for visibility to XPathVisitor. super.runRules(tag, args); } protected boolean duplicateThing(String... args) { String key = null; if (args.length == 1) { key = args[0]; } else { StringBuilder sb = new StringBuilder(); for (String a : args) { sb.append(a); } key = sb.toString(); } if (containsValueKey(key)) { return true; } setValue(key, Boolean.TRUE); return false; } protected boolean checkPrefix(String prefix, String name) { if (isEmptyOrWhitespace(prefix)) { return true; } String nsURI = lookupNamespace(prefix); if (isEmpty(nsURI)) { IProblem problem = createError(); problem.fill("XPATH_UNRESOLVED_NAMESPACE_PREFIX", //$NON-NLS-1$ prefix, name); return false; } return true; } protected String lookupNamespace(String prefix) { return mModelQuery.lookup(mNode, IModelQueryLookups.LOOKUP_TEXT_PREFIX2NS, prefix, null); } }