blob: 3d664a75eafcacdcec050d67ebfb24afe7bc64f6 [file] [log] [blame]
/*******************************************************************************
* 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 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Oracle Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.bpel.validator.model;
/**
* Java JDK dependency
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
/**
* BPEL Validation model dependency
*/
import org.eclipse.bpel.validator.model.Rules.Rule;
/**
* The base Validator class.
*
* A validator basically encapsulates an INode and runs some special methods (called rules)
* to check the INode element. The output is a set of IProblem (s).
*
* <p>
*
* Rules are special methods that are discovered by reflection in two ways:
* <ol>
* <li> They either have form "rule_<name>_<index>", or
* <li> The have the ARule annotation on them.
* </ol>
*
* <p>
*
* Order of execution of the rules on a given validator is user defined -
* that's what the "index" means above. A rule may also be turned off by
* the validator code during execution, so that logically exclusive conditions
* can be simply "turned" off by rules.
* <p>
* If the ARule annotation is used then the order() method returns the order of execution.
* Rules are simply discovered for every validator (only once), then sorted, and then run
* during invocations.
*
* <p>
*
* Beyond that, there are just 2 other items that govern how rules are executed.
* <ol>
* <li> The rule tag (simple string), and
* <li> the arguments (if any) to the rule method.
* </ol>
* <p>
* Rule tags are just strings which help organize the rules in some way and force their execution
* at specific times. There are two tags reserved for the system, one is "pass1", the other is "pass2".
* User that write validators can invoke other rule sets by calling {@see runRules() }
* and passing the appropriate tag and arguments. Return values from rule methods are never used.
*
* <p>
* Validators can be chained, so that you have the following scenario:
* <pre>
* 1 <-> 2 <-> 3 <-> 4 ... N
* </pre>
* For every INode there is the first validator that is always created. Other validators for the same
* INode can be created by simply calling the factory and then attaching the returned validator to the chain.
* This allows for separation of concerns. For example, "query" nodes may validate in the BPEL and WSDL contexts, and
* also validate in the Query language context (the same physical node).
*
* <p>
*
* Validators can keep certain state between passes, in the data hash map which is available to
* the super-classes. This data is erased every time a "pass1" tag is triggered on the validator but
* remains on for the duration of the object's lifetime.
*
* <p>
* This simple hash map mechanism is the way that various validators pass data to each other. This is primitive
* but allows for very loose coupling between the code. When a validator asks for getData() the query goes
* to its state data and if not found travels in the validator chain in the opposite direction to
* execution (always to previous).
*
* <p>
* And finally a note about INode. INode represents the generic tree node that some source material
* is sitting behind. This could be BPEL of course, but it could be any other thing as well. There are
* adapters which adapt DOM nodes to INodes and EMF nodes to INodes (though there is fewer of those).
*
* @author Michal Chmielewski (michal.chmielewski@oracle.com)
* @date Sep 14, 2006
*
*/
@SuppressWarnings({"unchecked","nls","boxing"})
public class Validator implements IConstants {
Logger mLogger = Logger.getLogger( getClass().getName() );
/** The runner which will run our rules */
RuleRunner fRuleRunner;
/** Problems produced by these rules */
private List<IProblem> fProblems = new ArrayList<IProblem>(4);
/** An empty problem array */
final static IProblem EMPTY_ARRAY[] = {};
/** The node that we are validating */
protected INode mNode ;
/** Answer interesting things about the model */
protected IModelQuery mModelQuery;
/** a list of state information that any validator can keep */
private Map<Object,Object> mData = new HashMap<Object,Object>(5);
/** The selector that can be used to query the INode facade */
static protected Selector mSelector = new Selector();
/** Pass 1 tag for rules */
static public final String PASS1 = "pass1";
/** PAss 2 tag for rules */
static public final String PASS2 = "pass2";
/** Support chains of validators for the same element */
private Validator fNext = null;
private Validator fPrev = null;
/** A set denoting the list of Static Analysis checks actually done */
private Set<ARule> mSAChecks = null;
/**
* Create an instance of the validator.
* Discover and setup the rules that we will be running. Primarily this
* includes roaming through the methods and ordering them in the correct
* order.
*/
protected Validator () {
fRuleRunner = new RuleRunner ( this );
}
/**
* @param node
*/
public void setNode (INode node) {
mNode = node;
}
/**
* Set the model query.
* @param query
*/
public void setModelQuery (IModelQuery query) {
mModelQuery = query;
}
/**
* Use this set to determine coverage of the validators.
*
* @param ruleSet
*/
public void setSAChecks ( Set<ARule> ruleSet ) {
mSAChecks = ruleSet;
}
/**
* Add a validator to the chain. It is always added to the end of the validator chain.
* The validators form a chain starting at the very first one like so
* <pre>
* 1 <-> 2 <-> 3 <-> 4 <-> 5 ... N
* </pre>
*
* When the main dispatcher code runs the validator for the given node, it starts running it at 1
* and continues to N.
* <p>
* A validator can be attached in 2 ways
* <ul>
* <li>During the initial factory create calls where all validators for the node
* are created and the initial chain is built, and
* <li>During execution of a rule.
* </ul>
* For that reason it is important to realize that the validator mNode, mModelQuery
* references may not be yet set.
*
* @param next
*/
protected void attach ( Validator next ) {
// no duplicates in chain
if (this == next) {
return ;
}
if (fNext == null) {
fNext = next;
next.fNext = null;
next.fPrev = this;
} else {
fNext.attach ( next );
}
}
/**
* Validate the node using the rules provided in this validator.
*
* @param tag the rules marked with the tag will be run
* model
*/
final public void validate ( String tag ) {
try {
if (tag.equals(PASS1)) {
start();
}
fRuleRunner.runRules ( tag );
if (tag.equals(PASS2)) {
end();
}
} catch (Throwable t) {
mLogger.logp(Level.SEVERE, getClass().getName(),
"validate",
"Problem executing this validator.",
t);
}
if (fNext != null) {
fNext.validate(tag);
}
}
/**
* Return the problems found as a result of validation
* in the last validation pass.
*
* @return the list of problems found
*/
@SuppressWarnings("unchecked")
final public IProblem[] getProblems () {
// Next is empty, just return what we have.
if (fProblems.size() == 0) {
return EMPTY_ARRAY;
}
return fProblems.toArray( EMPTY_ARRAY );
}
/**
*
* Return true if this node validator has captured any problems regarding
* the node in question.
*
* Chained validators are also consulted.
*
* @return true if there are problems, false if there are no problems reported.
*
*/
public boolean hasProblems () {
return fProblems.isEmpty() == false;
}
/**
*
* @param node
* @return true if there are problems, false otherwise.
*/
public boolean hasProblems ( INode node ) {
if (isDefined(node)) {
Validator validator = node.nodeValidator();
if (validator != null) {
return validator.hasProblems();
}
return false;
}
return true;
}
/**
* Disable all rules.
*/
protected void disableRules ( ) {
disableRules(0,65536);
}
/**
* @param startIdx
* @param endIdx
*/
protected void disableRules (int startIdx, int endIdx ) {
fRuleRunner.addFilter ( new Rules.IndexFilter ( startIdx,endIdx) );
}
/**
* Start the validation pass. This is run before all the rules are run.
*
*/
protected void start () {
/** reset any disabled rules */
fRuleRunner.start();
/**
* If we are the first validator in the chain
* Then we reset any problems list and clear any data that we
* have.
*/
if (fPrev == null) {
fProblems.clear();
mData.clear();
} else {
/**
* We point ourselves at the first validator in the chain.
*
*/
Validator first = getFirst();
/**
* These are in fact shared between the validators
* in the chain.
*/
fProblems = first.fProblems;
mData = first.mData;
mNode = first.mNode;
mModelQuery = first.mModelQuery;
mSAChecks = first.mSAChecks;
}
}
/**
* The validation pass has ended for this object
*/
protected void end ( ) {
}
/**
* Runs the rules matching the tag and and the arguments given.
*
* @param tag
* @param args
*/
protected void runRules ( String tag, Object ... args ) {
fRuleRunner.runRules (tag,args);
}
/**
* Add problems derived from contained validators into the
* problems we are reporting.
*
* @param problems
*/
protected void addProblems ( IProblem[] problems ) {
for(IProblem p : problems) {
fProblems.add(p);
}
}
@ARule(
author = "michal.chmielewski@oracle.com",
desc = "Internal error: no validator.",
date = "10/2/2006",
sa = -1,
warnings="BPELC__INTERNAL"
)
protected void internalProblem ( Rule rule, Throwable t ) {
IProblem problem = createWarning();
while ( t.getCause() != null ) {
t = t.getCause();
}
problem.fill("BPELC__INTERNAL",
toString(mNode.nodeName()),
rule.getFullName(),
rule.getIndex(),
rule.getTag(),
t);
problem.setAttribute(IProblem.EXCEPTION, t);
}
/**
* Mark that an SA check has been performed.
* This is done automatically when create*() methods are called to
* create an error/warning/info but you can call this method directly
* too.
*
* The is purely for testing reasons to make sure that cases
* are correctly run. This method may be a noop if the validator does not
* have the property "internal.sa.checks" set to the right value.
*
* @param arule the annotation to record as having been executed
*/
protected void markSAExecution (ARule arule) {
if (mSAChecks == null) {
return ;
}
if (arule == null) {
arule = fRuleRunner.getExecutingRule().method.getAnnotation( ARule.class );
}
if (arule != null) {
mSAChecks.add(arule);
}
}
/**
* Adopt this problem as one of ours. In this case, we simply fill in the location information
* in the problem and add it to a list of problems that we keep.
*
* @param problem
* @param node
*/
public void adopt ( IProblem problem , INode node ) {
// remember it
fProblems.add (problem);
problem.setAttribute(IProblem.NODE, node );
problem.setAttribute(IProblem.LINE_NUMBER,
mModelQuery.lookup(node, IModelQueryLookups.LOOKUP_NUMBER_LINE_NO,-1));
problem.setAttribute(IProblem.COLUMN_NUMBER,
mModelQuery.lookup(node, IModelQueryLookups.LOOKUP_NUMBER_COLUMN_NO,-1));
problem.setAttribute(IProblem.CHAR_END,
mModelQuery.lookup(node, IModelQueryLookups.LOOKUP_NUMBER_CHAR_END,-1));
problem.setAttribute(IProblem.CHAR_START,
mModelQuery.lookup(node, IModelQueryLookups.LOOKUP_NUMBER_CHAR_START,-1));
problem.setAttribute(IProblem.LOCATION,
mModelQuery.lookup(node, IModelQueryLookups.LOOKUP_TEXT_LOCATION,null,null));
problem.setAttribute(IProblem.ADDRESS_MODEL,
mModelQuery.lookup(node, IModelQueryLookups.LOOKUP_TEXT_HREF,null,null));
problem.setAttribute(IProblem.ADDRESS_XPATH,
mModelQuery.lookup(node, IModelQueryLookups.LOOKUP_TEXT_HREF_XPATH,null,null));
}
/**
* Create a problem based on the context node passed.
*
* @param node the context node from which the problem will be
* created.
* @return the IProblem marker for the node indicated.
*/
protected IProblem createProblem ( INode node ) {
IProblem problem = new Problem ( this );
adopt (problem, node);
Rule r = fRuleRunner.getExecutingRule();
if (r != null) {
problem.setAttribute(IProblem.RULE, r.getFullName() );
ARule a = r.method.getAnnotation( ARule.class );
if (a != null) {
markSAExecution(a);
problem.setAttribute( IProblem.SA_CODE, a.sa() );
problem.setAttribute( IProblem.RULE_DESC, a.desc() );
}
}
return problem;
}
/**
* Create an error problem on the current node
*
* @return the problem created
*/
protected IProblem createError ( ) {
return createError ( mNode );
}
/**
* Create an error problem. This does the same as this as
* createProblem plus it sets the problem object to severity error.
*
* @param node
* @return the problem to be recorded.
*/
protected IProblem createError ( INode node ) {
IProblem problem = createProblem (node);
problem.setAttribute(IProblem.SEVERITY, IProblem.SEVERITY_ERROR);
return problem;
}
/**
* Create a warning problem on the current node
*
* @return the problem created
*/
protected IProblem createWarning( ) {
return createWarning ( mNode );
}
/**
* Create a warning problem. This does the same as this as
* createProblem plus it sets the problem object to severity warning.
*
* @param node
* @return the problem to be recorded.
*/
protected IProblem createWarning ( INode node ) {
IProblem problem = createProblem (node);
problem.setAttribute(IProblem.SEVERITY, IProblem.SEVERITY_WARNING);
return problem;
}
/**
* Create an information problem. This does the same as this as
* createProblem plus it sets the problem object to severity information.
*
* @param node
* @return the problem to be recorded.
*/
protected IProblem createInfo ( INode node ) {
IProblem problem = createProblem (node);
problem.setAttribute(IProblem.SEVERITY, IProblem.SEVERITY_INFO);
return problem;
}
/**
* Create an information problem on the current node
*
* @return the problem created
*/
protected IProblem createInfo ( ) {
return createInfo ( mNode );
}
/**
* Is the node defined ? The check is to see if the node is empty and is resolved.
* If so, then we return true. Otherwise we return false.
*
* @param node
* @return true if defined, false otherwise
*/
protected boolean isDefined ( INode node ) {
return node != null && node.isResolved();
}
/**
* Is the node undefined ? The check is to see if the node is empty or it is unresolved.
* If that's the case, we return true, otherwise we return false.
*
* @param node
* @return true of undefined, false if defined.
*/
protected boolean isUndefined ( INode node ) {
return node == null || node.isResolved() == false;
}
/**
*
* @param <T>
* @param key
* @return the value
*/
public <T extends Object> T getValue ( Object key ) {
return (T) getValue( key , null );
}
/**
* @param <T>
* @param key
* @param def
* @return the value
*/
public <T extends Object> T getValue ( Object key, T def ) {
if (mData.containsKey(key)) {
Object obj = mData.get(key);
if (obj instanceof IValue) {
return (T) ((IValue)obj).get();
}
return (T) obj;
}
return def;
}
/**
* Return the data stored under the key keyName for the node node
* on its connected validator.
*
* @param <T>
* @param node the reference node
* @param key the key name
* @param def the default value
* @return the object stored or the default value passed
*/
public <T extends Object> T getValue ( INode node, Object key, T def ) {
Validator validator = validatorForNode(node);
if (validator != null) {
return validator.getValue(key,def);
}
return def;
}
/**
* @param <T> the type of the object
* @param keyName the key name to use
* @param value the value to set
* @return the previous value under the key
*/
public <T extends Object> T setValue ( Object keyName, T value) {
return (T) mData.put(keyName, value);
}
/**
* @param <T> the object
* @param node the node on which this value ought to be set
* @param keyName the key name to use
* @param value the value to set
* @return the previous value held under that key or null
*/
public <T extends Object> T setValue ( INode node, Object keyName, T value) {
Validator validator = validatorForNode(node);
if (validator != null) {
return validator.setValue(keyName,value);
}
return null;
}
/**
* Return true if the value key is present on us
* @param key
* @return true if present, false if not
*/
public boolean containsValueKey ( String key ) {
return mData.containsKey(key);
}
/**
* Return true if the value key is contained on the node node.
* @param node the node
* @param key the key
* @return true if value key is present, false otherwise.
*/
public boolean containsValueKey ( INode node, String key ) {
Validator validator = validatorForNode (node);
return validator != null ? validator.containsValueKey(key) : false;
}
Validator validatorForNode ( INode node ) {
if (isDefined(node)) {
return node.nodeValidator();
}
return null;
}
/**
* @return Return the first validator in the chain.
*/
private Validator getFirst() {
Validator first = this;
while (first.fPrev != null) {
first = first.fPrev;
}
return first;
}
protected String toString (QName qname) {
/** No namespace, just return the local part, sans the {} */
if (isEmptyOrWhitespace(qname.getNamespaceURI())) {
return qname.getLocalPart();
}
/** Lookup the prefix in the model query for the context node */
StringBuilder sb = new StringBuilder(32);
String prefix = qname.getPrefix();
if (isEmptyOrWhitespace(prefix)) {
prefix = mModelQuery.lookup(mNode, IModelQueryLookups.LOOKUP_TEXT_NS2PREFIX, qname.getNamespaceURI(), null );
}
/** No prefix, then exit with the default QName */
if (prefix == null) {
return qname.toString();
}
return sb.append(prefix).append(":").append(qname.getLocalPart()).toString();
}
/**
* Returns true if the string is either null or contains just whitespace.
* @param value
* @return true if empty or whitespace, false otherwise.
*/
static public boolean isEmptyOrWhitespace( String value )
{
if( value == null || value.length() == 0) {
return true;
}
for( int i = 0, j = value.length(); i < j; i++ )
{
if( ! Character.isWhitespace( value.charAt(i) ) ) {
return false;
}
}
return true;
}
/**
* Test to see if a string is empty or has a value that is empty.
*
* @param value
* @return true if empty or null, false otherwise.
*/
static public boolean isEmpty ( String value ) {
return value == null || value.length() == 0;
}
/** Test to see if a string is non empty
*
* @param value the value to test
* @return true if non empty, false if empty
*/
static public boolean isNonEmpty ( String value ) {
return value != null && value.length () > 0;
}
}