[293476] [validation] Attributes do not indicate problems with dynamic content in rtexprvalue attributes
diff --git a/bundles/org.eclipse.jst.jsp.core/META-INF/MANIFEST.MF b/bundles/org.eclipse.jst.jsp.core/META-INF/MANIFEST.MF
index e13e395..4776b60 100644
--- a/bundles/org.eclipse.jst.jsp.core/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.jst.jsp.core/META-INF/MANIFEST.MF
@@ -51,5 +51,7 @@
  org.eclipse.wst.html.core;bundle-version="[1.1.0,1.2.0)",
  org.eclipse.wst.validation;bundle-version="[1.1.0,1.2.0)",
  org.eclipse.wst.sse.ui;bundle-version="[1.0.101,1.1.0)",
+  org.eclipse.wst.common.project.facet.core;bundle-version="[1.1.0,1.2.0)",
+ org.eclipse.wst.common.modulecore;bundle-version="[1.1.0,1.2.0)",
  org.eclipse.equinox.servlet.api;bundle-version="[1.0.0,1.1.0)"
 Eclipse-LazyStart: true; exceptions="org.eclipse.jst.jsp.core.internal.contenttype"
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/JSPCorePlugin.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/JSPCorePlugin.java
index 3956bb3..dcce305 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/JSPCorePlugin.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/JSPCorePlugin.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004 IBM Corporation and others.
+ * Copyright (c) 2004, 2009 IBM 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
@@ -14,6 +14,7 @@
 import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jst.jsp.core.internal.contentmodel.TaglibController;
 import org.eclipse.jst.jsp.core.internal.contentproperties.JSPFContentPropertiesManager;
+import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache;
 import org.eclipse.jst.jsp.core.internal.java.search.JSPIndexManager;
 import org.eclipse.jst.jsp.core.internal.taglib.TaglibHelperManager;
 import org.eclipse.jst.jsp.core.taglib.TaglibIndex;
@@ -66,6 +67,7 @@
 		// listen for resource changes to update content properties keys
 		JSPFContentPropertiesManager.startup();
 
+		DeploymentDescriptorPropertyCache.start();
 	}
 
 	/*
@@ -74,6 +76,7 @@
 	 * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext)
 	 */
 	public void stop(BundleContext context) throws Exception {
+		DeploymentDescriptorPropertyCache.stop();
 		// stop listenning for resource changes to update content properties
 		// keys
 		JSPFContentPropertiesManager.shutdown();
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/contenttype/DeploymentDescriptorPropertyCache.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/contenttype/DeploymentDescriptorPropertyCache.java
new file mode 100644
index 0000000..9914204
--- /dev/null
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/contenttype/DeploymentDescriptorPropertyCache.java
@@ -0,0 +1,945 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM 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:
+ *     IBM Corporation - initial API and implementation
+ *     
+ *******************************************************************************/
+package org.eclipse.jst.jsp.core.internal.contenttype;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.jst.jsp.core.internal.Logger;
+import org.eclipse.jst.jsp.core.internal.util.CommonXML;
+import org.eclipse.jst.jsp.core.internal.util.FacetModuleCoreSupport;
+import org.eclipse.jst.jsp.core.internal.util.FileContentCache;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.EntityReference;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * A cache fo property group information stored in web.xml files. Information
+ * is not persisted.
+ */
+public final class DeploymentDescriptorPropertyCache {
+	private static final PropertyGroup[] NO_PROPERTY_GROUPS = new PropertyGroup[0];
+
+	static class DeploymentDescriptor {
+		PropertyGroup[] groups;
+		long modificationStamp;
+		StringMatcher[] urlPatterns;
+		Float version = new Float(defaultWebAppVersion);
+	}
+
+	/**
+	 * Representation of the JSP 2.0 property-group definitions from a servlet
+	 * deployment descriptor.
+	 */
+	public static final class PropertyGroup {
+		static PropertyGroup createFrom(IPath path, Node propertyGroupNode, int groupNumber) {
+			PropertyGroup group = new PropertyGroup(path, groupNumber);
+			Node propertyGroupID = propertyGroupNode.getAttributes().getNamedItem(ID);
+			if (propertyGroupID != null) {
+				group.setId(propertyGroupID.getNodeValue());
+			}
+			Node node = propertyGroupNode.getFirstChild();
+			while (node != null) {
+				if (node.getNodeType() == Node.ELEMENT_NODE) {
+					String name = node.getLocalName();
+					if (name == null) {
+						name = node.getNodeName();
+					}
+					if (IS_XML.equals(name)) {
+						group.setIsXML(getContainedText(node));
+					}
+					else if (EL_IGNORED.equals(name)) {
+						group.setElignored(getContainedText(node));
+					}
+					else if (INCLUDE_CODA.equals(name)) {
+						group.addCoda(getContainedText(node));
+					}
+					else if (INCLUDE_PRELUDE.equals(name)) {
+						group.addPrelude(getContainedText(node));
+					}
+					else if (SCRIPTING_INVALID.equals(name)) {
+						group.setScriptingInvalid(getContainedText(node));
+					}
+					else if (PAGE_ENCODING.equals(name)) {
+						group.setPageEncoding(getContainedText(node));
+					}
+					else if (URL_PATTERN.equals(name)) {
+						group.setUrlPattern(getContainedText(node));
+					}
+				}
+
+				node = node.getNextSibling();
+			}
+
+			return group;
+		}
+
+		private boolean el_ignored;
+
+		private String id;
+
+		private IPath[] include_coda = new IPath[0];
+
+		private IPath[] include_prelude = new IPath[0];
+		private boolean is_xml;
+		private StringMatcher matcher;
+		private String page_encoding;
+		private boolean scripting_invalid;
+		String url_pattern;
+		private IPath webxmlPath;
+
+		int number;
+
+		private PropertyGroup(IPath path, int number) {
+			super();
+			this.webxmlPath = path;
+			this.number = number;
+		}
+
+		private void addCoda(String containedText) {
+			if (containedText.length() > 0) {
+				IPath[] codas = new IPath[include_coda.length + 1];
+				System.arraycopy(include_coda, 0, codas, 0, include_coda.length);
+				codas[include_coda.length] = webxmlPath.removeLastSegments(2).append(containedText);
+				include_coda = codas;
+			}
+		}
+
+		private void addPrelude(String containedText) {
+			if (containedText.length() > 0) {
+				IPath[] preludes = new IPath[include_prelude.length + 1];
+				System.arraycopy(include_prelude, 0, preludes, 0, include_prelude.length);
+				preludes[include_prelude.length] = webxmlPath.removeLastSegments(2).append(containedText);
+				include_prelude = preludes;
+			}
+		}
+
+		public String getId() {
+			return id;
+		}
+
+		public IPath[] getIncludeCoda() {
+			return include_coda;
+		}
+
+		public IPath[] getIncludePrelude() {
+			return include_prelude;
+		}
+
+		public String getPageEncoding() {
+			return page_encoding;
+		}
+
+		public String getUrlPattern() {
+			return url_pattern;
+		}
+
+		public boolean isELignored() {
+			return el_ignored;
+		}
+
+		public boolean isIsXML() {
+			return is_xml;
+		}
+
+		public boolean isScriptingInvalid() {
+			return scripting_invalid;
+		}
+
+		boolean matches(String pattern, boolean optimistic) {
+			if (matcher == null)
+				return optimistic;
+			return matcher.match(pattern);
+		}
+
+		private void setElignored(String el_ignored) {
+			this.el_ignored = Boolean.valueOf(el_ignored).booleanValue();
+		}
+
+		private void setId(String id) {
+			this.id = id;
+		}
+
+		private void setIsXML(String is_xml) {
+			this.is_xml = Boolean.valueOf(is_xml).booleanValue();
+		}
+
+		private void setPageEncoding(String page_encoding) {
+			this.page_encoding = page_encoding;
+		}
+
+		private void setScriptingInvalid(String scripting_invalid) {
+			this.scripting_invalid = Boolean.valueOf(scripting_invalid).booleanValue();
+		}
+
+		private void setUrlPattern(String url_pattern) {
+			this.url_pattern = url_pattern;
+			if (url_pattern != null && url_pattern.length() > 0) {
+				this.matcher = new StringMatcher(url_pattern);
+			}
+		}
+
+		public String toString() {
+			return number + ":" + url_pattern; //$NON-NLS-1$
+		}
+	}
+
+	static class ResourceChangeListener implements IResourceChangeListener {
+		public void resourceChanged(IResourceChangeEvent event) {
+			IResourceDelta delta = event.getDelta();
+			if (event.getType() != IResourceChangeEvent.POST_CHANGE)
+				return;
+			if (delta.getKind() == IResourceDelta.CHANGED && (delta.getFlags() == IResourceDelta.ENCODING || delta.getFlags() == IResourceDelta.MARKERS))
+				return;
+
+			IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() {
+				public boolean visit(IResourceDelta delta) {
+					IResource resource = delta.getResource();
+					if (resource.getType() == IResource.FILE) {
+						if (delta.getKind() == IResourceDelta.CHANGED && (delta.getFlags() == IResourceDelta.ENCODING || delta.getFlags() == IResourceDelta.MARKERS))
+							return false;
+
+						IPath path = resource.getFullPath();
+						int segmentCount = path.segmentCount();
+						if (segmentCount > 1 && path.lastSegment().equals(WEB_XML) && path.segment(segmentCount - 2).equals(WEB_INF)) {
+							getInstance().deploymentDescriptorChanged(path);
+						}
+					}
+					else if (resource.getType() == IResource.PROJECT) {
+						String name = resource.getName();
+						if (_debugResolutionCache) {
+							System.out.println("Removing DeploymentDescriptorPropertyCache resolution cache for project " + name); //$NON-NLS-1$ 
+						}
+						getInstance().resolvedMap.remove(name);
+					}
+					return true;
+				}
+			};
+			try {
+				delta.accept(visitor);
+			}
+			catch (CoreException e) {
+				Logger.logException(e);
+			}
+		}
+	}
+
+	private static class ResourceErrorHandler implements ErrorHandler {
+		private boolean fDoLogExceptions = false;
+		private IPath fPath;
+
+		ResourceErrorHandler(boolean logExceptions) {
+			super();
+			fDoLogExceptions = logExceptions;
+		}
+
+		public void error(SAXParseException exception) throws SAXException {
+			if (fDoLogExceptions)
+				Logger.log(Logger.WARNING, "SAXParseException with " + fPath + " (error) while reading descriptor: " + exception.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		}
+
+		public void fatalError(SAXParseException exception) throws SAXException {
+			if (fDoLogExceptions)
+				Logger.log(Logger.WARNING, "SAXParseException with " + fPath + " (fatalError) while reading descriptor: " + exception.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		}
+
+		public void setPath(IPath path) {
+			fPath = path;
+		}
+
+		public void warning(SAXParseException exception) throws SAXException {
+			if (fDoLogExceptions)
+				Logger.log(Logger.WARNING, "SAXParseException with " + fPath + " (warning) while reading descriptor: " + exception.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		}
+	}
+
+	/**
+	 * Copied from org.eclipse.core.internal.propertytester.StringMatcher, but
+	 * should be replaced with a more accurate implementation of the rules in
+	 * Servlet spec SRV.11.2 and RFC 2396
+	 */
+	private static class StringMatcher {
+		private static final char SINGLE_WILD_CARD = '\u0000';
+
+		/**
+		 * Boundary value beyond which we don't need to search in the text
+		 */
+		private int bound = 0;
+
+		private boolean hasLeadingStar;
+
+		private boolean hasTrailingStar;
+
+		final String pattern;
+
+		private final int patternLength;
+
+		/**
+		 * The pattern split into segments separated by *
+		 */
+		private String segments[];
+
+		/**
+		 * StringMatcher constructor takes in a String object that is a simple
+		 * pattern which may contain '*' for 0 and many characters and '?' for
+		 * exactly one character.
+		 * 
+		 * Literal '*' and '?' characters must be escaped in the pattern e.g.,
+		 * "\*" means literal "*", etc.
+		 * 
+		 * Escaping any other character (including the escape character
+		 * itself), just results in that character in the pattern. e.g., "\a"
+		 * means "a" and "\\" means "\"
+		 * 
+		 * If invoking the StringMatcher with string literals in Java, don't
+		 * forget escape characters are represented by "\\".
+		 * 
+		 * @param pattern
+		 *            the pattern to match text against
+		 */
+		StringMatcher(String pattern) {
+			if (pattern == null)
+				throw new IllegalArgumentException();
+			this.pattern = pattern;
+			patternLength = pattern.length();
+			parseWildCards();
+		}
+
+		/**
+		 * @param text
+		 *            a simple regular expression that may only contain '?'(s)
+		 * @param start
+		 *            the starting index in the text for search, inclusive
+		 * @param end
+		 *            the stopping point of search, exclusive
+		 * @param p
+		 *            a simple regular expression that may contain '?'
+		 * @return the starting index in the text of the pattern , or -1 if
+		 *         not found
+		 */
+		private int findPosition(String text, int start, int end, String p) {
+			boolean hasWildCard = p.indexOf(SINGLE_WILD_CARD) >= 0;
+			int plen = p.length();
+			for (int i = start, max = end - plen; i <= max; ++i) {
+				if (hasWildCard) {
+					if (regExpRegionMatches(text, i, p, 0, plen))
+						return i;
+				}
+				else {
+					if (text.regionMatches(true, i, p, 0, plen))
+						return i;
+				}
+			}
+			return -1;
+		}
+
+		/**
+		 * Given the starting (inclusive) and the ending (exclusive) positions
+		 * in the <code>text</code>, determine if the given substring matches
+		 * with aPattern
+		 * 
+		 * @return true if the specified portion of the text matches the
+		 *         pattern
+		 * @param text
+		 *            a String object that contains the substring to match
+		 */
+		public boolean match(String text) {
+			if (text == null)
+				return false;
+			final int end = text.length();
+			final int segmentCount = segments.length;
+			if (segmentCount == 0 && (hasLeadingStar || hasTrailingStar)) // pattern
+				// contains
+				// only
+				// '*'(s)
+				return true;
+			if (end == 0)
+				return patternLength == 0;
+			if (patternLength == 0)
+				return false;
+			int currentTextPosition = 0;
+			if ((end - bound) < 0)
+				return false;
+			int segmentIndex = 0;
+			String current = segments[segmentIndex];
+
+			/* process first segment */
+			if (!hasLeadingStar) {
+				int currentLength = current.length();
+				if (!regExpRegionMatches(text, 0, current, 0, currentLength))
+					return false;
+				segmentIndex++;
+				currentTextPosition = currentTextPosition + currentLength;
+			}
+			if ((segmentCount == 1) && (!hasLeadingStar) && (!hasTrailingStar)) {
+				// only one segment to match, no wild cards specified
+				return currentTextPosition == end;
+			}
+			/* process middle segments */
+			while (segmentIndex < segmentCount) {
+				current = segments[segmentIndex];
+				int currentMatch = findPosition(text, currentTextPosition, end, current);
+				if (currentMatch < 0)
+					return false;
+				currentTextPosition = currentMatch + current.length();
+				segmentIndex++;
+			}
+
+			/* process final segment */
+			if (!hasTrailingStar && currentTextPosition != end) {
+				int currentLength = current.length();
+				return regExpRegionMatches(text, end - currentLength, current, 0, currentLength);
+			}
+			return segmentIndex == segmentCount;
+		}
+
+		/**
+		 * Parses the pattern into segments separated by wildcard '*'
+		 * characters.
+		 */
+		private void parseWildCards() {
+			if (pattern.startsWith("*"))//$NON-NLS-1$
+				hasLeadingStar = true;
+			if (pattern.endsWith("*")) {//$NON-NLS-1$
+				/* make sure it's not an escaped wildcard */
+				if (patternLength > 1 && pattern.charAt(patternLength - 2) != '\\') {
+					hasTrailingStar = true;
+				}
+			}
+
+			ArrayList temp = new ArrayList();
+
+			int pos = 0;
+			StringBuffer buf = new StringBuffer();
+			while (pos < patternLength) {
+				char c = pattern.charAt(pos++);
+				switch (c) {
+					case '\\' :
+						if (pos >= patternLength) {
+							buf.append(c);
+						}
+						else {
+							char next = pattern.charAt(pos++);
+							/* if it's an escape sequence */
+							if (next == '*' || next == '?' || next == '\\') {
+								buf.append(next);
+							}
+							else {
+								/*
+								 * not an escape sequence, just insert
+								 * literally
+								 */
+								buf.append(c);
+								buf.append(next);
+							}
+						}
+						break;
+					case '*' :
+						if (buf.length() > 0) {
+							/* new segment */
+							temp.add(buf.toString());
+							bound += buf.length();
+							buf.setLength(0);
+						}
+						break;
+					case '?' :
+						/*
+						 * append special character representing single match
+						 * wildcard
+						 */
+						buf.append(SINGLE_WILD_CARD);
+						break;
+					default :
+						buf.append(c);
+				}
+			}
+
+			/* add last buffer to segment list */
+			if (buf.length() > 0) {
+				temp.add(buf.toString());
+				bound += buf.length();
+			}
+			segments = (String[]) temp.toArray(new String[temp.size()]);
+		}
+
+		/**
+		 * 
+		 * @return boolean
+		 * @param text
+		 *            a String to match
+		 * @param tStart
+		 *            the starting index of match, inclusive
+		 * @param p
+		 *            a simple regular expression that may contain '?'
+		 * @param pStart
+		 *            The start position in the pattern
+		 * @param plen
+		 *            The length of the pattern
+		 */
+		private boolean regExpRegionMatches(String text, int tStart, String p, int pStart, int plen) {
+			while (plen-- > 0) {
+				char tchar = text.charAt(tStart++);
+				char pchar = p.charAt(pStart++);
+
+				// process wild cards, skipping single wild cards
+				if (pchar == SINGLE_WILD_CARD)
+					continue;
+				if (pchar == tchar)
+					continue;
+				if (Character.toUpperCase(tchar) == Character.toUpperCase(pchar))
+					continue;
+				// comparing after converting to upper case doesn't handle all
+				// cases;
+				// also compare after converting to lower case
+				if (Character.toLowerCase(tchar) == Character.toLowerCase(pchar))
+					continue;
+				return false;
+			}
+			return true;
+		}
+		
+		public String toString() {
+			return "StringMatcher: " + pattern; //$NON-NLS-1$
+		}
+	}
+
+	private static DeploymentDescriptorPropertyCache _instance = new DeploymentDescriptorPropertyCache();
+	private static final boolean _debugResolutionCache = false;
+
+	private static final float defaultWebAppVersion = 2.4f;
+	static String EL_IGNORED = "el-ignored"; //$NON-NLS-1$
+	private static String ID = "id"; //$NON-NLS-1$
+	static String INCLUDE_CODA = "include-coda"; //$NON-NLS-1$
+	static String INCLUDE_PRELUDE = "include-prelude"; //$NON-NLS-1$
+
+	static String IS_XML = "is-xml"; //$NON-NLS-1$
+	private static String JSP_PROPERTY_GROUP = "jsp-property-group"; //$NON-NLS-1$
+	static String PAGE_ENCODING = "page-encoding"; //$NON-NLS-1$
+
+	static String SCRIPTING_INVALID = "scripting-invalid"; //$NON-NLS-1$
+	static String URL_PATTERN = "url-pattern"; //$NON-NLS-1$
+	private static final String WEB_APP_ELEMENT_LOCAL_NAME = ":web-app"; //$NON-NLS-1$
+	private static final String WEB_APP_ELEMENT_NAME = "web-app"; //$NON-NLS-1$
+
+	private static final String WEB_APP_VERSION_NAME = "version"; //$NON-NLS-1$
+	private static final String WEB_INF = "WEB-INF"; //$NON-NLS-1$
+	private static final String WEB_XML = "web.xml"; //$NON-NLS-1$
+	// private static final String WEB_INF_WEB_XML = WEB_INF + IPath.SEPARATOR
+	// + WEB_XML;
+	private static final String SLASH_WEB_INF_WEB_XML = Path.ROOT.toString() + WEB_INF + IPath.SEPARATOR + WEB_XML;
+
+	static String getContainedText(Node parent) {
+		NodeList children = parent.getChildNodes();
+		if (children.getLength() == 1) {
+			return children.item(0).getNodeValue().trim();
+		}
+		StringBuffer s = new StringBuffer();
+		Node child = parent.getFirstChild();
+		while (child != null) {
+			if (child.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
+				String reference = ((EntityReference) child).getNodeValue();
+				if (reference == null && child.getNodeName() != null) {
+					reference = "&" + child.getNodeName() + ";"; //$NON-NLS-1$ //$NON-NLS-2$
+				}
+				if (reference != null) {
+					s.append(reference.trim());
+				}
+			}
+			else {
+				s.append(child.getNodeValue().trim());
+			}
+			child = child.getNextSibling();
+		}
+		return s.toString().trim();
+	}
+
+	public static DeploymentDescriptorPropertyCache getInstance() {
+		return _instance;
+	}
+
+	/**
+	 * This method is not meant to be called by clients.
+	 */
+	public static void start() {
+		ResourcesPlugin.getWorkspace().addResourceChangeListener(getInstance().fResourceChangeListener, IResourceChangeEvent.POST_CHANGE);
+	}
+
+	/**
+	 * This method is not meant to be called by clients.
+	 */
+	public static void stop() {
+		ResourcesPlugin.getWorkspace().removeResourceChangeListener(getInstance().fResourceChangeListener);
+	}
+
+	private ResourceErrorHandler errorHandler;
+
+	private Map fDeploymentDescriptors = new Hashtable();
+
+	private IResourceChangeListener fResourceChangeListener = new ResourceChangeListener();
+
+	// for use when reading TLDs
+	private EntityResolver resolver;
+
+	Map resolvedMap = new HashMap();
+
+	private DeploymentDescriptorPropertyCache() {
+		super();
+	}
+
+	private void _parseDocument(IPath path, Float[] version, List groupList, List urlPatterns, SubProgressMonitor subMonitor, Document document) {
+		Element webapp = document.getDocumentElement();
+		if (webapp != null) {
+			if (webapp.getTagName().equals(WEB_APP_ELEMENT_NAME) || webapp.getNodeName().endsWith(WEB_APP_ELEMENT_LOCAL_NAME)) {
+				// this convention only started with 2.4?
+				if (webapp.hasAttribute(WEB_APP_VERSION_NAME)) {
+					String versionValue = webapp.getAttribute(WEB_APP_VERSION_NAME);
+					versionValue = versionValue.trim();
+					if (versionValue.length() > 0) {
+						try {
+							version[0] = Float.valueOf(versionValue);
+						}
+						catch (NumberFormatException e) {
+							// doesn't matter
+						}
+					}
+				}
+				if (version[0] == null) {
+					// try determining the version from the doctype reference
+					DocumentType doctype = document.getDoctype();
+					if (doctype != null) {
+						String systemId = doctype.getSystemId();
+						String publicId = doctype.getPublicId();
+						if ((systemId != null && systemId.endsWith("web-app_2_3.dtd")) || (publicId != null && publicId.indexOf("Web Application 2.3") > 0)) { //$NON-NLS-1$ //$NON-NLS-2$
+							version[0] = new Float(2.3);
+						}
+						else if ((systemId != null && systemId.endsWith("web-app_2_2.dtd")) || (publicId != null && publicId.indexOf("Web Application 2.2") > 0)) { //$NON-NLS-1$ //$NON-NLS-2$
+							version[0] = new Float(2.2);
+						}
+						else if ((systemId != null && systemId.endsWith("web-app_2_1.dtd")) || (publicId != null && publicId.indexOf("Web Application 2.1") > 0)) { //$NON-NLS-1$ //$NON-NLS-2$
+							version[0] = new Float(2.1);
+						}
+					}
+				}
+			}
+		}
+		NodeList propertyGroupElements = document.getElementsByTagName(JSP_PROPERTY_GROUP);
+		int length = propertyGroupElements.getLength();
+		subMonitor.beginTask("Reading Property Groups", length); //$NON-NLS-1$
+		for (int i = 0; i < length; i++) {
+			PropertyGroup group = PropertyGroup.createFrom(path, propertyGroupElements.item(i), i);
+			subMonitor.worked(1);
+			if (group != null) {
+				groupList.add(group);
+			}
+		}
+		
+		NodeList urlPatternElements = document.getElementsByTagName(URL_PATTERN);
+		for (int i = 0; i < urlPatternElements.getLength(); i++) {
+			String urlPattern = getContainedText(urlPatternElements.item(i));
+			if(urlPattern != null && urlPattern.length() > 0) {
+				urlPatterns.add(new StringMatcher(urlPattern));
+			}
+		}
+	}
+
+	/**
+	 * Convert the SRV spec version to the JSP spec version
+	 */
+	private float convertSpecVersions(float version) {
+		if (version > 0) {
+			if (version == 2.5f)
+				return 2.1f;
+			else if (version == 2.4f)
+				return 2.0f;
+			else if (version == 2.3f)
+				return 1.2f;
+			else if (version == 2.2f)
+				return 1.1f;
+			else if (version == 2.1f)
+				return 1.0f;
+		}
+		return convertSpecVersions(defaultWebAppVersion);
+	}
+
+	void deploymentDescriptorChanged(final IPath fullPath) {
+		if (fDeploymentDescriptors.containsKey(fullPath.makeAbsolute())) {
+			updateCacheEntry(fullPath);
+		}
+	}
+
+	/**
+	 * parse the specified resource using Xerces, and if that fails, use the
+	 * SSE XML parser to find the property groups.
+	 */
+	private DeploymentDescriptor fetchDescriptor(IPath path, IProgressMonitor monitor) {
+		monitor.beginTask("", 3); //$NON-NLS-1$
+		IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
+
+		PropertyGroup groups[] = null;
+
+		IStructuredModel model = null;
+		List groupList = new ArrayList();
+		List urlPatterns = new ArrayList();
+		Float[] version = new Float[1];
+		SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, 2);
+		DocumentBuilder builder = CommonXML.getDocumentBuilder(false);
+		builder.setEntityResolver(getEntityResolver());
+		builder.setErrorHandler(getErrorHandler(path));
+		try {
+			InputSource inputSource = new InputSource();
+			String s = FileContentCache.getInstance().getContents(path);
+			inputSource.setCharacterStream(new StringReader(s));
+			inputSource.setSystemId(path.toString());
+			Document document = builder.parse(inputSource);
+			_parseDocument(path, version, groupList, urlPatterns, subMonitor, document);
+		}
+		catch (SAXException e1) {
+			/* encountered a fatal parsing error, try our own parser */
+			try {
+				/**
+				 * Chiefly because the web.xml file itself is editable, use
+				 * SSE to get the DOM Document because it is more fault
+				 * tolerant.
+				 */
+				model = StructuredModelManager.getModelManager().getModelForRead(file);
+				monitor.worked(1);
+				if (model instanceof IDOMModel) {
+					IDOMDocument document = ((IDOMModel) model).getDocument();
+					_parseDocument(path, version, groupList, urlPatterns, subMonitor, document);
+				}
+			}
+			catch (Exception e) {
+				Logger.logException(e);
+			}
+			finally {
+				if (model != null) {
+					model.releaseFromRead();
+				}
+			}
+		}
+		catch (IOException e1) {
+			/* file is unreadable, create no property groups */
+		}
+		finally {
+			groups = (PropertyGroup[]) groupList.toArray(new PropertyGroup[groupList.size()]);
+			subMonitor.done();
+		}
+
+		if (groups == null) {
+			groups = NO_PROPERTY_GROUPS;
+		}
+
+		DeploymentDescriptor deploymentDescriptor = new DeploymentDescriptor();
+		deploymentDescriptor.modificationStamp = file.getModificationStamp();
+		deploymentDescriptor.groups = groups;
+		deploymentDescriptor.urlPatterns = ((StringMatcher[]) urlPatterns.toArray(new StringMatcher[urlPatterns.size()]));
+		deploymentDescriptor.version = version[0];
+		monitor.done();
+		fDeploymentDescriptors.put(path, new SoftReference(deploymentDescriptor));
+		return deploymentDescriptor;
+	}
+
+	private DeploymentDescriptor getCachedDescriptor(IPath jspFilePath) {
+		IPath webxmlPath = getWebXMLPath(jspFilePath);
+		if (webxmlPath == null)
+			return null;
+
+		IFile webxmlFile = ResourcesPlugin.getWorkspace().getRoot().getFile(webxmlPath);
+		if (!webxmlFile.isAccessible())
+			return null;
+
+		Reference descriptorHolder = (Reference) fDeploymentDescriptors.get(webxmlPath);
+		DeploymentDescriptor descriptor = null;
+
+		if (descriptorHolder == null || ((descriptor = (DeploymentDescriptor) descriptorHolder.get()) == null) || (descriptor.modificationStamp == IResource.NULL_STAMP) || (descriptor.modificationStamp != webxmlFile.getModificationStamp())) {
+			descriptor = fetchDescriptor(webxmlPath, new NullProgressMonitor());
+		}
+		return descriptor;
+	}
+
+	private EntityResolver getEntityResolver() {
+		if (resolver == null) {
+			resolver = new EntityResolver() {
+				public InputSource resolveEntity(String publicID, String systemID) throws SAXException, IOException {
+					InputSource result = new InputSource(new ByteArrayInputStream(new byte[0]));
+					result.setPublicId(publicID);
+					result.setSystemId(systemID != null ? systemID : "/_" + getClass().getName()); //$NON-NLS-1$
+					return result;
+				}
+			};
+		}
+		return resolver;
+	}
+
+	/**
+	 * Returns an ErrorHandler that will not stop the parser on reported
+	 * errors
+	 */
+	private ErrorHandler getErrorHandler(IPath path) {
+		if (errorHandler == null) {
+			errorHandler = new ResourceErrorHandler(false);
+		}
+		errorHandler.setPath(path);
+		return errorHandler;
+	}
+
+	/**
+	 * @param fullPath
+	 * @return the JSP version supported by the web application containing the
+	 *         path. A value stated within a web.xml file takes priority.
+	 */
+	public float getJSPVersion(IPath fullPath) {
+		float version = defaultWebAppVersion;
+		/* try applicable web.xml file first */
+		DeploymentDescriptor descriptor = getCachedDescriptor(fullPath);
+		if (descriptor != null && descriptor.version != null) {
+			version = descriptor.version.floatValue();
+			return convertSpecVersions(version);
+		}
+
+		/* check facet settings */
+		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(fullPath.segment(0));
+		version = FacetModuleCoreSupport.getDynamicWebProjectVersion(project);
+
+		return convertSpecVersions(version);
+	}
+
+	/**
+	 * @param jspFilePath
+	 * @return PropertyGroups matching the file at the given path or an empty
+	 *         array if no web.xml file exists or no matching property group
+	 *         was defined. A returned PropertyGroup object should be
+	 *         considered short-lived and not saved for later use.
+	 */
+	public PropertyGroup[] getPropertyGroups(IPath jspFilePath) {
+		List matchingGroups = new ArrayList(1);
+		DeploymentDescriptor descriptor = getCachedDescriptor(jspFilePath);
+		if (descriptor == null)
+			return NO_PROPERTY_GROUPS;
+
+		for (int i = 0; i < descriptor.groups.length; i++) {
+			if (descriptor.groups[i].matches(FacetModuleCoreSupport.getRuntimePath(jspFilePath).toString(), false)) {
+				matchingGroups.add(descriptor.groups[i]);
+			}
+		}
+		if (matchingGroups.isEmpty()) {
+			for (int i = 0; i < descriptor.groups.length; i++) {
+				if (descriptor.groups[i].matches(FacetModuleCoreSupport.getRuntimePath(jspFilePath).toString(), true)) {
+					matchingGroups.add(descriptor.groups[i]);
+				}
+			}
+		}
+		return (PropertyGroup[]) matchingGroups.toArray(new PropertyGroup[matchingGroups.size()]);
+	}
+
+	/**
+	 * @param jspFilePath
+	 *            the path of the JSP file
+	 * @param reference
+	 *            a path reference to test for
+	 * @return a matching url-mapping value in the corresponding deployment
+	 *         descriptor for the given JSP file path, if a deployment
+	 *         descriptor could be found, null otherwise
+	 */
+	public String getURLMapping(IPath jspFilePath, String reference) {
+		DeploymentDescriptor descriptor = getCachedDescriptor(jspFilePath);
+		if (descriptor == null)
+			return null;
+		StringMatcher[] mappings = descriptor.urlPatterns;
+		for (int i = 0; i < mappings.length; i++) {
+			if (mappings[i].match(reference)) {
+				return mappings[i].pattern;
+			}
+		}
+		return null;
+	}
+	
+	private IPath getWebXMLPath(IPath fullPath) {
+		/*
+		 * It can take the better part of a full second to do this, so cache
+		 * the result.
+		 */
+		IPath resolved = null;
+		Map mapForProject = null;
+		mapForProject = (Map) resolvedMap.get(fullPath.segment(0));
+		if (mapForProject != null) {
+			resolved = (IPath) mapForProject.get(fullPath);
+		}
+		else {
+			mapForProject = new HashMap();
+			resolvedMap.put(fullPath.segment(0), mapForProject);
+		}
+
+		if (resolved != null) {
+			if (_debugResolutionCache) {
+				System.out.println("DeploymentDescriptorPropertyCache resolution cache hit for " + fullPath); //$NON-NLS-1$ 
+			}
+		}
+		else {
+			if (_debugResolutionCache) {
+				System.out.println("DeploymentDescriptorPropertyCache resolution cache miss for " + fullPath); //$NON-NLS-1$ 
+			}
+			resolved = FacetModuleCoreSupport.resolve(fullPath, SLASH_WEB_INF_WEB_XML);
+			mapForProject.put(fullPath, resolved);
+		}
+		return resolved;
+	}
+
+	public IFile getWebXML(IPath jspFilePath) {
+		IPath webxmlPath = getWebXMLPath(jspFilePath);
+		if (webxmlPath == null)
+			return null;
+
+		return ResourcesPlugin.getWorkspace().getRoot().getFile(webxmlPath);
+	}
+
+	private void updateCacheEntry(IPath fullPath) {
+		/* don't update right now; remove and wait for another query to update */
+		fDeploymentDescriptors.remove(fullPath);
+	}
+}
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/document/PageDirectiveAdapterImpl.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/document/PageDirectiveAdapterImpl.java
index 5a383f8..06656b4 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/document/PageDirectiveAdapterImpl.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/document/PageDirectiveAdapterImpl.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004-2006 IBM Corporation and others.
+ * Copyright (c) 2004, 2009 IBM 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
@@ -143,6 +143,7 @@
 	private String cachedLanguage;
 	private String cachedContentType;
 	private INodeNotifier notifierAtCreation;
+	private String elIgnored = null;
 
 	private int firstLanguagePosition = -1;
 	private int firstContentTypePosition = -1;
@@ -701,4 +702,12 @@
 		}
 		return desc;
 	}
+
+	public String getElIgnored() {
+		return elIgnored;
+	}
+
+	public void setElIgnored(String ignored) {
+		elIgnored = ignored;
+	}
 }
\ No newline at end of file
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/document/PageDirectiveWatcherImpl.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/document/PageDirectiveWatcherImpl.java
index 2fc219a..4ba19e6 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/document/PageDirectiveWatcherImpl.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/document/PageDirectiveWatcherImpl.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004 IBM Corporation and others.
+ * Copyright (c) 2004, 2009 IBM 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
@@ -47,7 +47,11 @@
 			PageDirectiveAdapterImpl pageDirectiveAdapter = (PageDirectiveAdapterImpl) ((IDOMDocument) targetElement.getOwnerDocument()).getAdapterFor(PageDirectiveAdapter.class);
 			pageDirectiveAdapter.changedLanguage(((IndexedRegion) targetElement).getStartOffset(), languageValue);
 		}
-
+		if (target.hasAttribute("isELIgnored")) { //$NON-NLS-1$
+			String elIgnored = target.getAttribute("isELIgnored"); //$NON-NLS-1$
+			PageDirectiveAdapterImpl pageDirectiveAdapter = (PageDirectiveAdapterImpl) ((IDOMDocument) targetElement.getOwnerDocument()).getAdapterFor(PageDirectiveAdapter.class);
+			pageDirectiveAdapter.setElIgnored(elIgnored);
+		}
 
 	}
 
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/util/FacetModuleCoreSupport.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/util/FacetModuleCoreSupport.java
new file mode 100644
index 0000000..f712793
--- /dev/null
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/util/FacetModuleCoreSupport.java
@@ -0,0 +1,235 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM 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:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jst.jsp.core.internal.util;
+
+import org.eclipse.core.filebuffers.FileBuffers;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+
+/**
+ * This class encapsulates any used Module Core and Facets APIs along with
+ * fallbacks for use on non-compliant projects and when those services are not
+ * available at runtime.
+ * 
+ * Because ModuleCore API calls can result in locks needing to be acquired,
+ * none of these methods should be called while other thread locks have
+ * already been acquired.
+ */
+public final class FacetModuleCoreSupport {
+	static final boolean _dump_NCDFE = false;
+	private static final String WEB_INF = "WEB-INF"; //$NON-NLS-1$
+	private static final IPath WEB_INF_PATH = new Path(WEB_INF);
+
+	/**
+	 * @param project
+	 * @return the computed IPath to the "root" of the web contents, either from facet knowledge or hueristics, or null if one can not be determined
+	 */
+	public static IPath computeWebContentRootPath(IPath path) {
+		IPath root = null;
+		try {
+			root = FacetModuleCoreSupportDelegate.getWebContentRootPath(ResourcesPlugin.getWorkspace().getRoot().getProject(path.segment(0)));
+		}
+		catch (NoClassDefFoundError e) {
+			if (_dump_NCDFE)
+				e.printStackTrace();
+		}
+		if(root == null) {
+			/*
+			 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=213245
+			 * 
+			 * NPE in JSPTaglibDirectiveContentAssistProcessor with
+			 * non-faceted project
+			 */
+			root = getLocalRoot(path);
+		}
+		return root;
+	}
+
+	/**
+	 * @param project
+	 * @return -1 if the project does not have the JST Web facet, the version
+	 *         number of it otherwise
+	 * @throws org.eclipse.core.runtime.CoreException
+	 */
+	public static float getDynamicWebProjectVersion(IProject project) {
+		// In the absence of any facet information, assume the highest level
+		float version = 2.5f;
+		try {
+			version = FacetModuleCoreSupportDelegate.getDynamicWebProjectVersion(project);
+		}
+		catch (NoClassDefFoundError e) {
+			if (_dump_NCDFE)
+				e.printStackTrace();
+		}
+		return version;
+	}
+
+	/**
+	 * @param project
+	 * @return the IPath to the "root" of the web contents
+	 */
+	public static IPath getWebContentRootPath(IProject project) {
+		if (project == null)
+			return null;
+
+		IPath path = null;
+		try {
+			path = FacetModuleCoreSupportDelegate.getWebContentRootPath(project);
+		}
+		catch (NoClassDefFoundError e) {
+			if (_dump_NCDFE)
+				e.printStackTrace();
+		}
+		return path;
+	}
+
+	/**
+	 * @param path -
+	 *            the full path to a resource within the workspace
+	 * @return - the runtime path of the resource if one exists, null
+	 *         otherwise
+	 */
+	public static IPath getRuntimePath(IPath path) {
+		IPath result = null;
+		try {
+			result = FacetModuleCoreSupportDelegate.getRuntimePath(path);
+		}
+		catch (NoClassDefFoundError e) {
+			if (_dump_NCDFE)
+				e.printStackTrace();
+		}
+		if (result == null) {
+			IPath root = getLocalRoot(path);
+			result = path.removeFirstSegments(root.segmentCount()).makeAbsolute();
+		}
+		return result;
+	}
+
+	/**
+	 * @param project
+	 * @return
+	 * @throws CoreException
+	 */
+	public static boolean isDynamicWebProject(IProject project) {
+		if (project == null)
+			return false;
+		
+		try {
+			return FacetModuleCoreSupportDelegate.isDynamicWebProject(project);
+		}
+		catch (NoClassDefFoundError e) {
+			if (_dump_NCDFE)
+				e.printStackTrace();
+		}
+		return true;
+	}
+
+	/**
+	 * @param basePath -
+	 *            the full path to a resource within the workspace
+	 * @param reference -
+	 *            the reference string to resolve
+	 * @return - the full path within the workspace that corresponds to the
+	 *         given reference according to the virtual pathing support
+	 */
+	public static IPath resolve(IPath basePath, String reference) {
+		IPath resolvedPath = null;
+		try {
+			resolvedPath = FacetModuleCoreSupportDelegate.resolve(basePath, reference);
+		}
+		catch (NoClassDefFoundError e) {
+			if (_dump_NCDFE)
+				e.printStackTrace();
+		}
+
+		if (resolvedPath == null) {
+			IPath rootPath = getLocalRoot(basePath);
+			if (reference.startsWith(Path.ROOT.toString())) {
+				resolvedPath = rootPath.append(reference);
+			}
+			else {
+				resolvedPath = basePath.removeLastSegments(1).append(reference);
+			}
+		}
+
+		return resolvedPath;
+	}
+
+	/**
+	 * @param basePath
+	 * @return the applicable Web context root path, if one exists
+	 */
+	private static IPath getLocalRoot(IPath basePath) {
+		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+
+		// existing workspace resources - this is the 93% case
+		IResource file = FileBuffers.getWorkspaceFileAtLocation(basePath);
+
+		// Try the base path as a folder first
+		if (file == null && basePath.segmentCount() > 1) {
+			file = workspaceRoot.getFolder(basePath);
+		}
+		// If not a folder, then try base path as a file
+		if (file != null && !file.exists() && basePath.segmentCount() > 1) {
+			file = workspaceRoot.getFile(basePath);
+		}
+
+		if (file == null && basePath.segmentCount() == 1) {
+			file = workspaceRoot.getProject(basePath.segment(0));
+		}
+
+		if (file == null) {
+			/*
+			 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=116529
+			 * 
+			 * This method produces a less accurate result, but doesn't
+			 * require that the file exist yet.
+			 */
+			IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(basePath);
+			if (files.length > 0)
+				file = files[0];
+		}
+
+		while (file != null) {
+			/**
+			 * Treat any parent folder with a WEB-INF subfolder as a web-app
+			 * root
+			 */
+			IContainer folder = null;
+			if ((file.getType() & IResource.FOLDER) != 0) {
+				folder = (IContainer) file;
+			}
+			else {
+				folder = file.getParent();
+			}
+			// getFolder on a workspace root must use a full path, skip
+			if (folder != null && (folder.getType() & IResource.ROOT) == 0) {
+				IFolder webinf = folder.getFolder(WEB_INF_PATH);
+				if (webinf != null && webinf.exists()) {
+					return folder.getFullPath();
+				}
+			}
+			file = file.getParent();
+		}
+
+		return basePath.uptoSegment(1);
+	}
+
+
+}
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/util/FacetModuleCoreSupportDelegate.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/util/FacetModuleCoreSupportDelegate.java
new file mode 100644
index 0000000..f47a1f8
--- /dev/null
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/util/FacetModuleCoreSupportDelegate.java
@@ -0,0 +1,191 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jst.jsp.core.internal.util;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jst.jsp.core.internal.Logger;
+import org.eclipse.wst.common.componentcore.ComponentCore;
+import org.eclipse.wst.common.componentcore.ModuleCoreNature;
+import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
+import org.eclipse.wst.common.componentcore.resources.IVirtualFile;
+import org.eclipse.wst.common.componentcore.resources.IVirtualResource;
+import org.eclipse.wst.common.project.facet.core.IFacetedProject;
+import org.eclipse.wst.common.project.facet.core.IProjectFacet;
+import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager;
+
+/**
+ * Wrapper class for all Facet-related calls. If the Facet or ModuleCore
+ * bundles are not available, this class will not load, or if it does, its
+ * methods will cause NoClassDefFoundErrors. This allows us to
+ * compartmentalize the dependencies.
+ * 
+ */
+final class FacetModuleCoreSupportDelegate {
+	private static final String SLASH = "/";
+
+	/**
+	 * Copied to avoid unneeded extra dependency (plus it's unclear why the
+	 * value is in that plug-in).
+	 * 
+	 * @see org.eclipse.wst.common.componentcore.internal.util.IModuleConstants.JST_WEB_MODULE
+	 */
+	private final static String JST_WEB_MODULE = "jst.web"; //$NON-NLS-1$
+
+	/**
+	 * @param project
+	 * @return -1 if the project does not have the JST Web facet, the version
+	 *         number of it otherwise
+	 * @throws CoreException
+	 */
+	static float getDynamicWebProjectVersion(IProject project) {
+		if (project == null)
+			return 2.5f;
+
+		// In the absence of any facet information, assume the highest level
+		float version = 2.5f;
+		try {
+			IFacetedProject faceted = ProjectFacetsManager.create(project);
+			if (faceted != null && ProjectFacetsManager.isProjectFacetDefined(JST_WEB_MODULE)) {
+				IProjectFacet webModuleFacet = ProjectFacetsManager.getProjectFacet(JST_WEB_MODULE);
+				if (faceted.hasProjectFacet(webModuleFacet)) {
+					version = Float.parseFloat(faceted.getInstalledVersion(webModuleFacet).getVersionString());
+				}
+			}
+		}
+		catch (NumberFormatException e) {
+			Logger.logException(e);
+		}
+		catch (CoreException e) {
+			Logger.logException(e);
+		}
+		return version;
+	}
+
+	/**
+	 * @param path -
+	 *            the full path to a resource within the workspace
+	 * @return - the runtime path of the resource if one exists, null
+	 *         otherwise
+	 */
+	static IPath getRuntimePath(IPath path) {
+		if (path == null)
+			return null;
+
+		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.segment(0));
+
+		if (!ModuleCoreNature.isFlexibleProject(project))
+			return null;
+
+		IVirtualResource[] virtualResources = ComponentCore.createResources(ResourcesPlugin.getWorkspace().getRoot().getFile(path));
+		if (virtualResources != null && virtualResources.length > 0) {
+			return virtualResources[0].getRuntimePath();
+		}
+		return null;
+	}
+
+	/**
+	 * @param project
+	 * @return the IPath to the "root" of the web contents
+	 */
+	static IPath getWebContentRootPath(IProject project) {
+		if (project == null)
+			return null;
+
+		if (!ModuleCoreNature.isFlexibleProject(project))
+			return null;
+
+		IPath path = null;
+		IVirtualComponent component = ComponentCore.createComponent(project);
+		if (component != null && component.exists()) {
+			path = component.getRootFolder().getWorkspaceRelativePath();
+		}
+		return path;
+	}
+
+	/**
+	 * @param project
+	 * @return
+	 * @throws CoreException
+	 */
+	static boolean isDynamicWebProject(IProject project) {
+		if (project == null)
+			return false;
+		
+		try {
+			if (ProjectFacetsManager.isProjectFacetDefined(JST_WEB_MODULE)) {
+				IFacetedProject faceted = ProjectFacetsManager.create(project);
+				IProjectFacet webModuleFacet = ProjectFacetsManager.getProjectFacet(JST_WEB_MODULE);
+				if (faceted != null && faceted.hasProjectFacet(webModuleFacet)) {
+					return true;
+				}
+			}
+		}
+		catch (CoreException e) {
+			Logger.logException(e);
+		}
+		return false;
+	}
+
+	/**
+	 * @param basePath -
+	 *            the full path to a resource within the workspace
+	 * @param reference -
+	 *            the reference string to resolve
+	 * @return - the full path within the workspace that corresponds to the
+	 *         given reference according to the virtual pathing support
+	 */
+	static IPath resolve(IPath basePath, String reference) {
+		if (reference == null || basePath == null || basePath.segmentCount() == 0)
+			return null;
+
+		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(basePath.segment(0));
+
+		if (!ModuleCoreNature.isFlexibleProject(project))
+			return null;
+
+		if (basePath.segmentCount() > 1) {
+			IFile baseFile = ResourcesPlugin.getWorkspace().getRoot().getFile(basePath);
+			IVirtualResource[] virtualResources = ComponentCore.createResources(baseFile);
+			for (int i = 0; i < virtualResources.length; i++) {
+				IPath baseRuntimePath = virtualResources[i].getRuntimePath();
+				IPath referenceRuntimePath = null;
+				if (reference.startsWith(SLASH)) {
+					referenceRuntimePath = new Path(reference);
+				}
+				else {
+					referenceRuntimePath = baseRuntimePath.removeLastSegments(1).append(reference);
+				}
+				IVirtualFile virtualFile = ComponentCore.createFile(project, referenceRuntimePath);
+				if (virtualFile != null && virtualFile.exists()) {
+					IFile[] underlyingFiles = virtualFile.getUnderlyingFiles();
+					for (int j = 0; j < underlyingFiles.length; j++) {
+						if (underlyingFiles[j].getProject().equals(project) && underlyingFiles[j].exists()) {
+							return underlyingFiles[j].getFullPath();
+						}
+
+					}
+				}
+			}
+		}
+		else {
+			IVirtualFile virtualFile = ComponentCore.createFile(project, new Path(reference));
+			if (virtualFile != null && virtualFile.exists()) {
+				return virtualFile.getUnderlyingFile().getFullPath();
+			}
+		}
+		return null;
+	}
+}
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/validation/JSPActionValidator.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/validation/JSPActionValidator.java
index 5f9e0c7..6b0c133 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/validation/JSPActionValidator.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/validation/JSPActionValidator.java
@@ -15,12 +15,18 @@
 import java.util.List;
 
 import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IPath;
 import org.eclipse.jface.text.IRegion;
 import org.eclipse.jface.text.Region;
 import org.eclipse.jst.jsp.core.internal.Logger;
 import org.eclipse.jst.jsp.core.internal.contentmodel.TaglibController;
 import org.eclipse.jst.jsp.core.internal.contentmodel.tld.TLDCMDocumentManager;
 import org.eclipse.jst.jsp.core.internal.contentmodel.tld.TaglibTracker;
+import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDAttributeDeclaration;
+import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache;
+import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache.PropertyGroup;
+import org.eclipse.jst.jsp.core.internal.document.PageDirectiveAdapter;
+import org.eclipse.jst.jsp.core.internal.document.PageDirectiveAdapterImpl;
 import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.wst.html.core.internal.HTMLCoreMessages;
@@ -44,6 +50,7 @@
 import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil;
 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
 import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
 import org.w3c.dom.Attr;
@@ -58,6 +65,7 @@
 	private int fSeverityUnknownAttribute = IMessage.NORMAL_SEVERITY;
 	private IValidator fMessageOriginator;
 	private HashSet fTaglibPrefixes = new HashSet();
+	private boolean fIsELIgnored = false;
 
 	public JSPActionValidator() {
 		this.fMessageOriginator = this;
@@ -67,6 +75,51 @@
 		this.fMessageOriginator = validator;
 	}
 
+	/**
+	 * Checks an attribute for runtime expressions
+	 * @param a The attribute to check for runetime expressions
+	 * @return true if the attribute contains a runtime expression, false otherwise
+	 */
+	private boolean checkRuntimeValue(IDOMAttr a) {
+		// 1. Check isElIgnored page directive
+		// 2. Check property groups
+		// 3. Check JSP version
+		ITextRegion value = a.getValueRegion();
+		if (value instanceof ITextRegionContainer) {
+			Iterator it = ((ITextRegionContainer) value).getRegions().iterator();
+			while (it.hasNext()) {
+				String type = ((ITextRegion) it.next()).getType();
+				if (type == DOMJSPRegionContexts.JSP_EL_OPEN || type == DOMJSPRegionContexts.JSP_VBL_OPEN)
+					return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Determines if EL should be ignored. Checks
+	 * <ol>
+	 *  <li>JSP version</li>
+	 * 	<li>Page directive isELIgnored</li>
+	 *  <li>Deployment descriptor's el-ignored</li>
+	 * </ol>
+	 * @return true if EL should be ignored, false otherwise. If the JSP version is < 2.0, EL is ignored by default
+	 */
+	private boolean isElIgnored(IPath path, IStructuredModel model) {
+		if (DeploymentDescriptorPropertyCache.getInstance().getJSPVersion(path) < 2.0f)
+			return true;
+		String directiveIsELIgnored = ((PageDirectiveAdapterImpl)(((IDOMModel) model).getDocument().getAdapterFor(PageDirectiveAdapter.class))).getElIgnored();
+		// isELIgnored directive found
+		if (directiveIsELIgnored != null)
+			return Boolean.valueOf(directiveIsELIgnored).booleanValue();
+		// Check the deployment descriptor for el-ignored
+		PropertyGroup[] groups = DeploymentDescriptorPropertyCache.getInstance().getPropertyGroups(path);
+		if (groups.length > 0)
+			return groups[0].isELignored();
+		// JSP version >= 2.0 defaults to evaluating EL
+		return false;
+	}
+
 	private void checkRequiredAttributes(IDOMElement element, CMNamedNodeMap attrMap, IReporter reporter, IFile file, IStructuredDocument document, IStructuredDocumentRegion documentRegion) {
 		Iterator it = attrMap.iterator();
 		CMAttributeDeclaration attr = null;
@@ -117,6 +170,27 @@
 					foundjspattribute = true;
 				}
 			}
+			else {
+				if (adec instanceof TLDAttributeDeclaration) {
+					// The attribute cannot have a runtime evaluation of an expression
+					if (!Boolean.valueOf(((TLDAttributeDeclaration) adec).getRtexprvalue()).booleanValue()) {
+						IDOMAttr attr = (IDOMAttr) a;
+						if(checkRuntimeValue(attr) && !fIsELIgnored) {
+							String msg = NLS.bind(HTMLCoreMessages.Invalid_attribute___0____ERROR_, a.getName());
+							LocalizedMessage message = new LocalizedMessage(IMessage.NORMAL_SEVERITY, msg, file);
+							ITextRegion region = attr.getValueRegion();
+							int start = attr.getValueRegionStartOffset();
+							int length = region != null ? region.getTextLength() : 0;
+							int lineNo = document.getLineOfOffset(start);
+							message.setLineNo(lineNo);
+							message.setOffset(start);
+							message.setLength(length);
+
+							reporter.addMessage(fMessageOriginator, message);
+						}
+					}
+				}
+			}
 		}
 		return foundjspattribute;
 	}
@@ -195,7 +269,7 @@
 
 	protected void performValidation(IFile f, IReporter reporter, IStructuredModel model, IRegion validateRegion) {
 		IStructuredDocument sDoc = model.getStructuredDocument();
-
+		fIsELIgnored = isElIgnored(f.getFullPath(), model);
 		// iterate all document regions
 		IStructuredDocumentRegion region = sDoc.getRegionAtCharacterOffset(validateRegion.getOffset());
 		while (region != null && !reporter.isCancelled() && (region.getStartOffset() <= (validateRegion.getOffset() + validateRegion.getLength()))) {