| /******************************************************************************* |
| * Copyright (c) 2001, 2008 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.jst.jsf.validation.internal.strategy; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.emf.common.util.BasicDiagnostic; |
| import org.eclipse.emf.common.util.Diagnostic; |
| import org.eclipse.jst.jsf.common.dom.AttrDOMAdapter; |
| import org.eclipse.jst.jsf.common.dom.AttributeIdentifier; |
| import org.eclipse.jst.jsf.common.dom.DOMAdapter; |
| import org.eclipse.jst.jsf.common.internal.types.CompositeType; |
| import org.eclipse.jst.jsf.common.internal.types.TypeComparator; |
| import org.eclipse.jst.jsf.common.internal.types.TypeComparatorDiagnosticFactory; |
| import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContext; |
| import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContextFactory; |
| import org.eclipse.jst.jsf.core.internal.JSFCorePlugin; |
| import org.eclipse.jst.jsf.core.internal.region.Region2AttrAdapter; |
| import org.eclipse.jst.jsf.designtime.DTAppManagerUtil; |
| import org.eclipse.jst.jsf.designtime.internal.view.XMLViewDefnAdapter; |
| import org.eclipse.jst.jsf.designtime.internal.view.IDTViewHandler.ViewHandlerException; |
| import org.eclipse.jst.jsf.designtime.internal.view.XMLViewDefnAdapter.DTELExpression; |
| import org.eclipse.jst.jsf.metadataprocessors.MetaDataEnabledProcessingFactory; |
| import org.eclipse.jst.jsf.metadataprocessors.features.ELIsNotValidException; |
| import org.eclipse.jst.jsf.metadataprocessors.features.IValidELValues; |
| import org.eclipse.jst.jsf.metadataprocessors.features.IValidValues; |
| import org.eclipse.jst.jsf.metadataprocessors.features.IValidationMessage; |
| import org.eclipse.jst.jsf.validation.internal.AbstractXMLViewValidationStrategy; |
| import org.eclipse.jst.jsf.validation.internal.JSFValidationContext; |
| import org.eclipse.jst.jsf.validation.internal.el.ELExpressionValidator; |
| import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; |
| import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; |
| |
| /** |
| * An XML view validation strategy that validates |
| * |
| * @author cbateman |
| * |
| */ |
| public class AttributeValidatingStrategy extends |
| AbstractXMLViewValidationStrategy |
| { |
| static final boolean DEBUG; |
| static |
| { |
| final String value = Platform |
| .getDebugOption("org.eclipse.jst.jsf.validation.internal.el/debug/jspsemanticsvalidator"); //$NON-NLS-1$ |
| DEBUG = value != null && value.equalsIgnoreCase("true"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * identifier |
| */ |
| public final static String ID = "org.eclipse.jst.jsf.validation.strategy.AttributeValidatingStrategy"; |
| private final static String DISPLAY_NAME = "Attribute Validator"; |
| |
| private final JSFValidationContext _validationContext; |
| private final TypeComparator _typeComparator; |
| /** |
| * Default constructor |
| * |
| * @param validationContext |
| */ |
| public AttributeValidatingStrategy( |
| final JSFValidationContext validationContext) |
| { |
| super(ID, DISPLAY_NAME); |
| |
| _validationContext = validationContext; |
| _typeComparator = new TypeComparator(new TypeComparatorDiagnosticFactory(validationContext.getPrefs().getTypeComparatorPrefs())); |
| } |
| |
| @Override |
| public boolean isInteresting(final DOMAdapter domAdapter) |
| { |
| return (domAdapter instanceof AttrDOMAdapter); |
| } |
| |
| @Override |
| public void validate(final DOMAdapter domAdapter) |
| { |
| if (domAdapter instanceof AttrDOMAdapter) |
| { |
| final Region2AttrAdapter attrAdapter = (Region2AttrAdapter) domAdapter; |
| //check that this is attribute value region - 221722 |
| if (attrAdapter.getAttributeValueRegion() != null) { |
| final IStructuredDocumentContext context = IStructuredDocumentContextFactory.INSTANCE |
| .getContext(attrAdapter.getDocumentContext() |
| .getStructuredDocument(), attrAdapter |
| .getOwningElement().getDocumentContext() |
| .getDocumentPosition() |
| + attrAdapter.getAttributeValueRegion().getStart()); |
| |
| validateAttributeValue(context, attrAdapter); |
| } |
| } |
| } |
| |
| /** |
| * Validates the attribute value. Reports any problems to the reporter in |
| * the JSFValidationContext. |
| * |
| * @param context |
| * @param attrAdapter |
| */ |
| private void validateAttributeValue( |
| final IStructuredDocumentContext context, |
| final Region2AttrAdapter attrAdapter) |
| { |
| // so of the code in run calls out into extension code or code |
| // dependent on external data (meta-data) |
| SafeRunner.run(new ISafeRunnable() |
| { |
| public void handleException(Throwable exception) |
| { |
| JSFCorePlugin.log(String.format( |
| "Error validating attribute: %s on element %s", |
| attrAdapter.getNodeName(), attrAdapter |
| .getOwningElement().getNodeName()), exception); |
| } |
| |
| public void run() throws Exception |
| { |
| // if there's elText then validate it |
| // TODO: this approach will fail with mixed expressions |
| if (!checkIfELAndValidate(attrAdapter, context)) |
| { |
| validateNonELAttributeValue(context, attrAdapter); |
| } |
| } |
| }); |
| } |
| |
| private boolean checkIfELAndValidate(final Region2AttrAdapter attrAdapter, |
| final IStructuredDocumentContext context) |
| { |
| int offsetOfFirstEL = -1; |
| final String attrValue = attrAdapter.getValue(); |
| |
| // TODO: should find and validate all |
| offsetOfFirstEL = attrValue.indexOf('#'); |
| |
| if (offsetOfFirstEL != -1 && offsetOfFirstEL < attrValue.length() - 1 |
| && attrValue.charAt(offsetOfFirstEL + 1) == '{') |
| { |
| offsetOfFirstEL += 2; |
| } |
| else |
| { |
| offsetOfFirstEL = -1; |
| } |
| |
| final XMLViewDefnAdapter adapter = DTAppManagerUtil |
| .getXMLViewDefnAdapter(context); |
| |
| boolean isEL = false; |
| if (adapter != null && offsetOfFirstEL != -1) |
| { |
| try |
| { |
| // use the attribute's context plus the offset into the |
| // whole attribute value to find where we think the el |
| // expression starts. Add one since the attribute value |
| // string returned by attrAdapter will have the value quotes |
| // removed, but the region offsets include the quotes. |
| IStructuredDocumentContext elContext = IStructuredDocumentContextFactory.INSTANCE |
| .getContext(context.getStructuredDocument(), context |
| .getDocumentPosition() |
| + offsetOfFirstEL + 1); |
| final DTELExpression elExpression = adapter |
| .getELExpression(elContext); |
| if (elExpression != null) |
| { |
| final String elText = elExpression.getText(); |
| |
| if (DEBUG) |
| { |
| System.out.println(addDebugSpacer(3) + "EL attrVal= " |
| + elText); |
| } |
| |
| elContext = elExpression.getDocumentContext(); |
| // EL validation is user configurable because |
| // it can be computationally costly. |
| if (_validationContext.shouldValidateEL()) |
| { |
| // also, skip the validation if the expression is empty |
| // or only whitespace, since the parser doesn't handle it |
| // anyway. |
| if ("".equals(elText.trim())) |
| { |
| final int offset = elContext.getDocumentPosition()-1; |
| final int length = elText.length()+2; |
| final Diagnostic diagnostic = _validationContext |
| .getDiagnosticFactory() |
| .create_EMPTY_EL_EXPRESSION(); |
| // detected empty EL expression |
| if (_validationContext.shouldValidateEL()) |
| { |
| _validationContext.getReporter().report( |
| diagnostic, offset, length); |
| } |
| } |
| else |
| { |
| final List elVals = MetaDataEnabledProcessingFactory |
| .getInstance() |
| .getAttributeValueRuntimeTypeFeatureProcessors( |
| IValidELValues.class, elContext, |
| attrAdapter.getAttributeIdentifier()); |
| final String safeELText = elText.replaceAll("[\n\r\t]", " "); |
| validateELExpression(context, elContext, elVals, |
| attrAdapter.getValue(), safeELText); |
| isEL = true; |
| } |
| } |
| } |
| } |
| catch (final ViewHandlerException e) |
| { |
| // fall through to return false |
| } |
| } |
| |
| // is el if we've already detected it or if the step 2 method |
| // finds it. Run the method first to avoid short-circuiting |
| final boolean isEL2 = checkIfELAndValidate2(attrAdapter, context); |
| |
| return isEL || isEL2; |
| } |
| |
| /** |
| * Checks the region to see if it contains an EL attribute value. If it |
| * does, validates it |
| * |
| * @return true if validated EL, false otherwise |
| */ |
| private boolean checkIfELAndValidate2(final Region2AttrAdapter attrAdapter, |
| final IStructuredDocumentContext sDocContext) |
| { |
| final ITextRegion attrValueRegion = attrAdapter |
| .getAttributeValueRegion(); |
| if (attrValueRegion instanceof ITextRegionCollection) |
| { |
| final ITextRegionCollection parentRegion = ((ITextRegionCollection) attrValueRegion); |
| if (parentRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) |
| { |
| ITextRegionList regionList = parentRegion.getRegions(); |
| |
| if (regionList.size() >= 3) |
| { |
| final ITextRegion openQuote = regionList.get(0); |
| final ITextRegion vblOpen = regionList.get(1); |
| |
| if ((openQuote.getType() == DOMJSPRegionContexts.XML_TAG_ATTRIBUTE_VALUE_DQUOTE || openQuote |
| .getType() == DOMJSPRegionContexts.JSP_VBL_DQUOTE) |
| && vblOpen.getType() == DOMJSPRegionContexts.JSP_VBL_OPEN) |
| { |
| boolean foundClosingQuote = false; |
| for (int i = 2; !foundClosingQuote |
| && i < regionList.size(); i++) |
| { |
| final ITextRegion searchRegion = regionList.get(i); |
| if (searchRegion.getType() == DOMJSPRegionContexts.JSP_VBL_CLOSE) |
| { |
| foundClosingQuote = true; |
| } |
| } |
| |
| if (!foundClosingQuote |
| && _validationContext.shouldValidateEL()) |
| { |
| final int offset = sDocContext |
| .getDocumentPosition() + 1; |
| final int length = parentRegion.getText().length(); |
| final Diagnostic diagnostic = _validationContext |
| .getDiagnosticFactory() |
| .create_MISSING_CLOSING_EXPR_BRACKET(); |
| _validationContext.getReporter().report(diagnostic, |
| offset, length); |
| } |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| private void validateELExpression(final IStructuredDocumentContext context, |
| final IStructuredDocumentContext elContext, final List elVals, |
| final String attributeVal, final String elText) |
| { |
| // Call EL validator which will perform at least the syntactical |
| // validation |
| final ELExpressionValidator elValidator = new ELExpressionValidator( |
| elContext, elText, _validationContext |
| .getSymbolResolverFactory(), _validationContext |
| .getReporter()); |
| elValidator.validateXMLNode(); |
| |
| final CompositeType exprType = elValidator.getExpressionType(); |
| if (exprType != null) |
| { |
| for (final Iterator it = elVals.iterator(); it.hasNext();) |
| { |
| final IValidELValues elval = (IValidELValues) it.next(); |
| CompositeType expectedType; |
| Diagnostic status = null; |
| try |
| { |
| expectedType = elval.getExpectedRuntimeType(); |
| |
| if (expectedType != null) |
| { |
| |
| status = _typeComparator.calculateTypeCompatibility( |
| expectedType, exprType); |
| if (status.getSeverity() != Diagnostic.OK) |
| { |
| reportValidationMessage(status, context, |
| attributeVal); |
| } |
| } |
| } |
| catch (final ELIsNotValidException e) |
| { |
| reportValidationMessage(createValidationMessage(context, |
| attributeVal, IStatus.WARNING, e.getMessage(), |
| _validationContext.getFile()), context, |
| attributeVal); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Validates an attribute value in context using the JSF metadata processing |
| * framework |
| * |
| * @param context |
| * @param region |
| * @param uri |
| * @param tagName |
| * @param attrName |
| * @param attributeVal |
| * @param reporter |
| * @param file |
| */ |
| private void validateNonELAttributeValue( |
| final IStructuredDocumentContext context, |
| final Region2AttrAdapter attrAdapter) |
| { |
| final String attributeValue = attrAdapter.getValue(); |
| // else validate as static attribute value |
| if (DEBUG) |
| { |
| System.out.println(addDebugSpacer(3) + "attrVal= " |
| + (attributeValue != null ? attributeValue : "null")); |
| } |
| |
| final AttributeIdentifier attributeId = attrAdapter |
| .getAttributeIdentifier(); |
| |
| if (attributeId.getTagIdentifier() == null |
| || attributeId.getTagIdentifier().getTagName() == null |
| || attributeId.getTagIdentifier().getTagName() == null |
| || attributeId.getName() == null) |
| { |
| return; |
| } |
| |
| final List vv = MetaDataEnabledProcessingFactory.getInstance() |
| .getAttributeValueRuntimeTypeFeatureProcessors( |
| IValidValues.class, context, attributeId); |
| if (!vv.isEmpty()) |
| { |
| for (final Iterator it = vv.iterator(); it.hasNext();) |
| { |
| final IValidValues v = (IValidValues) it.next(); |
| if (!v.isValidValue(attributeValue.trim())) |
| { |
| if (DEBUG) |
| { |
| System.out.println(addDebugSpacer(4) + "NOT VALID "); |
| } |
| |
| for (final Iterator msgs = v.getValidationMessages() |
| .iterator(); msgs.hasNext();) |
| { |
| final IValidationMessage msg = (IValidationMessage) msgs |
| .next(); |
| reportValidationMessage(createValidationMessage( |
| context, attributeValue, msg.getSeverity(), msg |
| .getMessage(), _validationContext |
| .getFile()), context, attributeValue); |
| } |
| } |
| else if (DEBUG) |
| { |
| System.out.println(addDebugSpacer(5) + "VALID "); |
| } |
| } |
| } |
| else if (DEBUG) |
| { |
| System.out.println(addDebugSpacer(4) + "NO META DATA "); |
| } |
| } |
| |
| private void reportValidationMessage(final Diagnostic problem, |
| final IStructuredDocumentContext context, |
| final String attributeValue) |
| { |
| final int start = context.getDocumentPosition() + 1; |
| final int length = attributeValue.length(); |
| _validationContext.getReporter().report(problem, start, length); |
| } |
| |
| private Diagnostic createValidationMessage( |
| final IStructuredDocumentContext context, |
| final String attributeValue, final int severity, final String msg, |
| final IFile file) |
| { |
| // TODO: need factory |
| final Diagnostic diagnostic = new BasicDiagnostic(severity, "", -1, |
| msg, null); |
| return diagnostic; |
| } |
| |
| private String addDebugSpacer(final int count) |
| { |
| final String TAB = "\t"; |
| final StringBuffer ret = new StringBuffer(""); |
| for (int i = 0; i <= count; i++) |
| { |
| ret.append(TAB); |
| } |
| return ret.toString(); |
| } |
| |
| } |