### Eclipse Workspace Patch 1.0
#P org.jboss.tools.jst.web.kb
Index: src/org/jboss/tools/jst/web/kb/customtags/FakeURIResolver.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/FakeURIResolver.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/FakeURIResolver.java	(revision 0)
@@ -0,0 +1,97 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+import java.io.InputStream;
+
+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.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin;
+import org.eclipse.wst.sse.core.internal.util.URIResolver;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+@SuppressWarnings({ "restriction", "deprecation" })
+public class FakeURIResolver implements URIResolver {
+	protected String fLocation;
+	protected IPath fPath;
+	protected IProject fProject;
+	final static String SEPARATOR = "/"; //$NON-NLS-1$ 
+	final static String FILE_PREFIX = "file://"; //$NON-NLS-1$
+
+	public FakeURIResolver(URIResolver copy) {
+		fProject = copy.getProject();
+		fLocation = copy.getFileBaseLocation();
+		IPath pathLocation = Path.fromOSString(fLocation);
+		fPath = fProject.getFullPath().append(pathLocation.makeRelativeTo(fProject.getLocation()));
+	}
+
+	public String getFileBaseLocation() {
+		return fLocation;
+	}
+
+	public String getLocationByURI(String uri) {
+		return getLocationByURI(uri, getFileBaseLocation(), false);
+	}
+
+	public String getLocationByURI(String uri, boolean resolveCrossProjectLinks) {
+		return getLocationByURI(uri, getFileBaseLocation(), resolveCrossProjectLinks);
+	}
+
+	public String getLocationByURI(String uri, String baseReference) {
+		return getLocationByURI(uri, baseReference, false);
+	}
+
+	public String getLocationByURI(String uri, String baseReference, boolean resolveCrossProjectLinks) {
+		boolean baseHasPrefix = baseReference != null && baseReference.startsWith(FILE_PREFIX);
+		String reference = null;
+		if (baseHasPrefix) {
+			reference = baseReference;
+		}
+		else {
+			reference = FILE_PREFIX + baseReference;
+		}
+		String result = URIResolverPlugin.createResolver().resolve(reference, null, uri);
+		// Logger.log(Logger.INFO_DEBUG,
+		// "URIResolverPlugin.createResolver().resolve("
+		// + reference + ", null, " +uri+") = " + result);
+		if (!baseHasPrefix && result.startsWith(FILE_PREFIX) && result.length() > FILE_PREFIX.length()) {
+			result = result.substring(FILE_PREFIX.length());
+		}
+		return result;
+	}
+
+	public IProject getProject() {
+		return fProject;
+	}
+
+	public IContainer getRootLocation() {
+		String root = URIResolverPlugin.createResolver().resolve(FILE_PREFIX + getFileBaseLocation(), null, SEPARATOR);
+		IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(new Path(root));
+		for (int i = 0; i < files.length; i++) {
+			if ((files[i].getType() & IResource.FOLDER) == IResource.FOLDER) {
+				if (fPath.isPrefixOf(((IFolder) files[i]).getFullPath())) {
+					return (IFolder) files[i];
+				}
+			}
+		}
+		return getProject();
+	}
+
+	public InputStream getURIStream(String uri) {
+		return null;
+	}
+
+	public void setFileBaseLocation(String newLocation) {
+		fLocation += newLocation;
+	}
+
+	public void setProject(IProject newProject) {
+		fProject = newProject;
+	}
+}
Index: src/org/jboss/tools/jst/web/kb/customtags/JspCustomTagStorageConfig.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/JspCustomTagStorageConfig.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/JspCustomTagStorageConfig.java	(revision 0)
@@ -0,0 +1,51 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+public class JspCustomTagStorageConfig {
+
+	protected String customTagsURI = "urn:jsptagdir:"; //$NON-NLS-1$
+	protected String jarTldFolderRelativePath = "/META-INF/tags/"; //$NON-NLS-1$
+	protected String customTagsWithTldURI = "http://stopka.us/ui"; //$NON-NLS-1$
+	protected String tldFolderRelativePath = "/WEB-INF/tags/ui"; //$NON-NLS-1$
+	
+	public JspCustomTagStorageConfig() {
+	}
+	
+	public JspCustomTagStorageConfig(String customTagsURI, String jarTldFolderRelativePath,
+		String customTagsWithTldURI, String tldFolderRelativePath) {
+		this.customTagsURI = customTagsURI;
+		this.jarTldFolderRelativePath = jarTldFolderRelativePath;
+		this.customTagsWithTldURI = customTagsWithTldURI;
+		this.tldFolderRelativePath = tldFolderRelativePath;
+	}
+
+	public String getCustomTagsURI() {
+		return customTagsURI;
+	}
+	public void setCustomTagsURI(String customTagsURI) {
+		this.customTagsURI = customTagsURI;
+	}
+	
+	public String getJarTldFolderRelativePath() {
+		return jarTldFolderRelativePath;
+	}
+	public void setJarTldFolderRelativePath(String jarTldFolderRelativePath) {
+		this.jarTldFolderRelativePath = jarTldFolderRelativePath;
+	}
+	
+	public String getCustomTagsWithTldURI() {
+		return customTagsWithTldURI;
+	}
+	public void setCustomTagsWithTldURI(String customTagsWithTldURI) {
+		this.customTagsWithTldURI = customTagsWithTldURI;
+	}
+	
+	public String getTldFolderRelativePath() {
+		return tldFolderRelativePath;
+	}
+	public void setTldFolderRelativePath(String tldFolderRelativePath) {
+		this.tldFolderRelativePath = tldFolderRelativePath;
+	}
+}
Index: src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTagFile.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTagFile.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTagFile.java	(revision 0)
@@ -0,0 +1,31 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+public class JSPCustomTagFile {
+	
+	private String tagName;
+	
+	private String tagxPath;
+
+	@XmlElement(name="name")
+	public String getTagName() {
+		return tagName;
+	}
+
+	public void setTagName(String tagName) {
+		this.tagName = tagName;
+	}
+
+	@XmlElement(name="path")
+	public String getTagxPath() {
+		return tagxPath;
+	}
+
+	public void setTagxPath(String tagxPath) {
+		this.tagxPath = tagxPath;
+	}
+}
Index: src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTaglib.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTaglib.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTaglib.java	(revision 0)
@@ -0,0 +1,80 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+@XmlRootElement(name="taglib")
+public class JSPCustomTaglib {
+	
+	private String description;
+	
+	private String version;
+	
+	private String shortName;
+	
+	private String uri;
+	
+	private List<JSPCustomTagFile> jspCustomTagFiles = new ArrayList<JSPCustomTagFile>();
+	
+	private List<JSPCustomTag> jspCustomTags = new ArrayList<JSPCustomTag>();
+
+	@XmlElement(name = "description")
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	@XmlElement(name = "tlib-version")
+	public String getVersion() {
+		return version;
+	}
+
+	public void setVersion(String version) {
+		this.version = version;
+	}
+
+	@XmlElement(name = "short-name")
+	public String getShortName() {
+		return shortName;
+	}
+
+	public void setShortName(String shortName) {
+		this.shortName = shortName;
+	}
+
+	@XmlElement(name = "uri")
+	public String getUri() {
+		return uri;
+	}
+
+	public void setUri(String uri) {
+		this.uri = uri;
+	}
+
+	@XmlElement(name = "tag-file")
+	public List<JSPCustomTagFile> getJspCustomTagFiles() {
+		return jspCustomTagFiles;
+	}
+	
+	public void setJspCustomTagFiles(List<JSPCustomTagFile> jspCustomTagFiles) {
+		this.jspCustomTagFiles = jspCustomTagFiles;
+	}
+
+	@XmlElement(name = "tag")
+	public List<JSPCustomTag> getJspCustomTags() {
+		return jspCustomTags;
+	}
+
+	public void setJspCustomTags(List<JSPCustomTag> jspCustomTags) {
+		this.jspCustomTags = jspCustomTags;
+	}
+}
Index: src/org/jboss/tools/jst/web/kb/PageContextFactory.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/PageContextFactory.java	(revision 36242)
+++ src/org/jboss/tools/jst/web/kb/PageContextFactory.java	(working copy)
@@ -10,6 +10,7 @@
  ******************************************************************************/ 
 package org.jboss.tools.jst.web.kb;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
@@ -20,6 +21,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 import org.eclipse.core.internal.resources.ICoreConstants;
 import org.eclipse.core.resources.IFile;
@@ -28,6 +30,7 @@
 import org.eclipse.core.resources.IResourceChangeEvent;
 import org.eclipse.core.resources.IResourceChangeListener;
 import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IStorage;
 import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
@@ -47,6 +50,7 @@
 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.parser.JSPSourceParser;
 import org.eclipse.ui.ide.IDE;
 import org.eclipse.wst.common.componentcore.internal.ComponentResource;
 import org.eclipse.wst.common.componentcore.internal.StructureEdit;
@@ -61,9 +65,12 @@
 import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
 import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
 import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
+import org.eclipse.wst.sse.core.internal.text.JobSafeStructuredDocument;
+import org.eclipse.wst.sse.core.internal.util.URIResolver;
 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
@@ -88,6 +95,11 @@
 import org.jboss.tools.common.util.EclipseUIUtil;
 import org.jboss.tools.common.util.FileUtil;
 import org.jboss.tools.common.validation.ValidationELReference;
+import org.jboss.tools.jst.web.kb.customtags.CustomTagName;
+import org.jboss.tools.jst.web.kb.customtags.FakeURIResolver;
+import org.jboss.tools.jst.web.kb.customtags.JspCustomTagStorage;
+import org.jboss.tools.jst.web.kb.customtags.JspCustomTagStorageConfig;
+import org.jboss.tools.jst.web.kb.customtags.ReadContentUtil;
 import org.jboss.tools.jst.web.kb.include.IncludeContextBuilder;
 import org.jboss.tools.jst.web.kb.internal.FaceletPageContextImpl;
 import org.jboss.tools.jst.web.kb.internal.JspContextImpl;
@@ -108,7 +120,7 @@
  * 
  * @author Alexey Kazakov
  */
-@SuppressWarnings("restriction")
+@SuppressWarnings({ "restriction", "deprecation" })
 public class PageContextFactory implements IResourceChangeListener {
 	private static PageContextFactory fInstance = new PageContextFactory();
 	private static final String XHTML_TAG_LIB_URI = "http://www.w3.org/1999/xhtml"; //$NON-NLS-1$
@@ -237,7 +249,7 @@
 		context.setResource(file);
 		context.setElResolvers(ELResolverFactoryManager.getInstance().getResolvers(file));
 		String content = FileUtil.getContentFromEditorOrFile(file);
-		if(content.indexOf('{')>-1 && content.indexOf("#{") >-1 || content.indexOf("${")>-1 ) { //$NON-NLS-1$
+		if(content.indexOf('{')>-1 && content.indexOf("#{") >-1 || content.indexOf("${")>-1 ) { //$NON-NLS-1$ //$NON-NLS-2$
 			ELParser parser = ELParserUtil.getJbossFactory().createParser();
 			ELModel model = parser.parse(content);
 			List<SyntaxError> errors = model.getSyntaxErrors();
@@ -314,6 +326,7 @@
 	}
 
 //	long ctm = 0;
+	protected TreeSet<String> processedTemplates = new TreeSet<String>();
 
 	/**
 	 * Creates a page context for the specified context type
@@ -356,9 +369,11 @@
 									// Fill JSP namespaces defined in TLDCMDocumentManager 
 									fillJSPNameSpaces((JspContextImpl)context);
 								}
+								processedTemplates.clear();
 								// The subsequently called functions may use the file and document
 								// already stored in context for their needs
-								fillContextForChildNodes(model.getStructuredDocument(), document, context, parents);
+								fillContextForChildNodes(model.getStructuredDocument(), document, context, parents, domModel.getResolver());
+								processedTemplates.clear();
 							}
 						}
 					} catch (CoreException e) {
@@ -374,12 +389,51 @@
 			}
 	
 			if (context != null && isContextCachingAllowed) {
-					saveConvext(context);
+				saveConvext(context);
 			}
 		}
 		return context;
 	}
 
+	private void fillContextReferenceForChildNodes(IDOMNode node, ELContext context, List<String> parents, URIResolver resolver) {
+		IFile file = context.getResource();
+		if (file == null) {
+			return;
+		}
+		IProject project = file.getProject();
+		JspCustomTagStorageConfig config = new JspCustomTagStorageConfig();
+		IStorage storage = JspCustomTagStorage.INSTANCE.getCustomTemplateStorage(
+				config, project, new CustomTagName(node.getNamespaceURI(), node.getLocalName()));
+		String customTemplate = ReadContentUtil.getContent(storage);
+		if (customTemplate == null || customTemplate.trim().isEmpty()) {
+			return;
+		}
+		if (processedTemplates.contains(customTemplate)) {
+			return;
+		}
+		processedTemplates.add(customTemplate);
+		IStructuredDocument document = new JobSafeStructuredDocument((new JSPSourceParser()).newInstance());
+		document.set(customTemplate);
+		//
+		IStructuredModel model = null;
+		try {
+			ByteArrayInputStream bais = new ByteArrayInputStream(customTemplate.getBytes());
+			IModelManager manager = StructuredModelManager.getModelManager();
+			model = manager.getModelForRead("x", bais, new FakeURIResolver(resolver)); //$NON-NLS-1$
+		} catch (UnsupportedEncodingException e) {
+			WebKbPlugin.getDefault().logError(e);
+		} catch (IOException e) {
+			WebKbPlugin.getDefault().logError(e);
+		}
+		//
+		if (model instanceof IDOMModel) {
+			IDOMModel domModel = (IDOMModel)model;
+			IDOMDocument domDoc = domModel.getDocument();
+			//
+			fillContextForChildNodes(document, domDoc, context, parents, resolver);
+		}
+	}
+
 	private static ELContext createPageContextInstance(String contentType) {
 		String contextType = IncludeContextBuilder.getContextType(contentType);
 		return createContextInstanceOfType(contextType);
@@ -421,17 +475,18 @@
 		}
 	}
 
-	private void fillContextForChildNodes(IDocument document, IDOMNode parent, ELContext context, List<String> parents) {
+	private void fillContextForChildNodes(IDocument document, IDOMNode parent, ELContext context, List<String> parents, URIResolver resolver) {
 		NodeList children = parent.getChildNodes();
 		for(int i = 0; children != null && i < children.getLength(); i++) {
 			Node child = children.item(i);
 			if (child instanceof IDOMNode) {
 				fillContextForNode(document, (IDOMNode)child, context, parents);
-				fillContextForChildNodes(document, (IDOMNode)child, context, parents);
+				fillContextForChildNodes(document, (IDOMNode)child, context, parents, resolver);
+				fillContextReferenceForChildNodes((IDOMNode)child, context, parents, resolver);
 			}
 		}
 	}
-
+	
 	private void fillContextForNode(IDocument document, IDOMNode node, ELContext context, List<String> parents) {
 		if (context instanceof XmlContextImpl) {
 			XmlContextImpl xmlContext = (XmlContextImpl)context;
@@ -511,7 +566,7 @@
 
 	private static void fillElReferencesForRegionNode(IDocument document, IDOMNode node, IStructuredDocumentRegion regionNode, ITextRegion region, XmlContextImpl context) {
 		String text = regionNode.getFullText(region);
-		if(text.indexOf('{')>-1 && (text.indexOf("#{") > -1 || text.indexOf("${") > -1)) { //$NON-NLS-1$
+		if(text.indexOf('{')>-1 && (text.indexOf("#{") > -1 || text.indexOf("${") > -1)) { //$NON-NLS-1$ //$NON-NLS-2$
 			int offset = regionNode.getStartOffset() + region.getStart();
 			if (context.getELReference(offset) != null) return; // prevent the duplication of EL references while iterating thru the regions 
 
Index: src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTagAttribute.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTagAttribute.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTagAttribute.java	(revision 0)
@@ -0,0 +1,43 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+public class JSPCustomTagAttribute {
+	
+	private String attributeName;
+	
+	private boolean required = false;
+	
+	private boolean rtexprvalue = false;
+
+	@XmlElement(name="name")
+	public String getAttributeName() {
+		return attributeName;
+	}
+
+	public void setAttributeName(String attributeName) {
+		this.attributeName = attributeName;
+	}
+
+	@XmlElement(name="required")
+	public boolean isRequired() {
+		return required;
+	}
+
+	public void setRequired(boolean required) {
+		this.required = required;
+	}
+
+	@XmlElement(name="rtexprvalue")
+	public boolean isRtexprvalue() {
+		return rtexprvalue;
+	}
+
+	public void setRtexprvalue(boolean rtexprvalue) {
+		this.rtexprvalue = rtexprvalue;
+	} 
+
+}
Index: src/org/jboss/tools/jst/web/kb/customtags/CustomStringStorage.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/CustomStringStorage.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/CustomStringStorage.java	(revision 0)
@@ -0,0 +1,94 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+public class CustomStringStorage implements IStorage {
+
+	private String jarFileContent;
+	private String name;
+
+	public CustomStringStorage(String input, String resourceName) {
+		this.jarFileContent = input;
+		this.name = resourceName;
+	}
+
+	@Override
+	public InputStream getContents() throws CoreException {
+		return new ByteArrayInputStream(this.jarFileContent.getBytes());
+	}
+
+	@Override
+	public IPath getFullPath() {
+		return null;
+	}
+
+	@Override
+	public String getName() {
+		return this.name;
+	}
+
+	/**
+	 * 
+	 * @return content of file in jar archive
+	 */
+	public String getContentString() {
+		return this.jarFileContent;
+	}
+
+	@Override
+	public boolean isReadOnly() {
+		return true;
+	}
+
+	@SuppressWarnings("rawtypes")
+	@Override
+	public Object getAdapter(Class adapter) {
+		return null;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + (jarFileContent == null ? 0 : jarFileContent.hashCode());
+		result = prime * result + (name == null ? 0 : name.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (getClass() != obj.getClass()) {
+			return false;
+		}
+		CustomStringStorage other = (CustomStringStorage) obj;
+		if (jarFileContent == null) {
+			if (other.jarFileContent != null) {
+				return false;
+			}
+		} else if (!jarFileContent.equals(other.jarFileContent)) {
+			return false;
+		}
+		if (name == null) {
+			if (other.name != null) {
+				return false;
+			}
+		} else if (!name.equals(other.name)) {
+			return false;
+		}
+		return true;
+	}
+}
Index: src/org/jboss/tools/jst/web/kb/customtags/ReadContentUtil.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/ReadContentUtil.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/ReadContentUtil.java	(revision 0)
@@ -0,0 +1,48 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+public class ReadContentUtil {
+	
+	public static String getContent(InputStream inputStream) {
+		StringBuilder buffer = new StringBuilder();
+		if (inputStream != null) {
+			Scanner in = new Scanner(inputStream);
+			while (in.hasNextLine()) {
+				buffer.append(in.nextLine());
+			}
+			in.close();
+		}
+		return buffer.toString();
+	}
+	
+	public static String getContent(IStorage storage) {
+		String res = ""; //$NON-NLS-1$
+		if (storage == null) {
+			return res;
+		}
+		InputStream inputStream = null;
+		try {
+			inputStream = storage.getContents();
+			res = getContent(inputStream);
+		} catch (CoreException e) {
+		} finally {
+			if (inputStream != null) {
+				try {
+					inputStream.close();
+				} catch (IOException e) {
+				}
+			}
+		}
+		return res;
+	}
+
+}
Index: src/org/jboss/tools/jst/web/kb/customtags/TaglibUtil.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/TaglibUtil.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/TaglibUtil.java	(revision 0)
@@ -0,0 +1,35 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+import java.io.File;
+import java.io.InputStream;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+public class TaglibUtil {
+
+	public static JSPCustomTaglib unmarshalTldFile(IPath tldFilePath) throws JAXBException {
+		IFile tldFile = ResourcesPlugin.getWorkspace().getRoot().getFile(tldFilePath);
+		return unmarshalTldFile(tldFile.getLocation().toFile());
+	}
+
+	public static JSPCustomTaglib unmarshalTldFile(File tldFile) throws JAXBException {
+		JAXBContext jc = JAXBContext.newInstance(JSPCustomTaglib.class);
+		Unmarshaller um = jc.createUnmarshaller();
+		return (JSPCustomTaglib) um.unmarshal(tldFile);
+	}
+	
+	public static JSPCustomTaglib unmarshalTldFile(InputStream is) throws JAXBException {
+		JAXBContext jc = JAXBContext.newInstance(JSPCustomTaglib.class);
+		Unmarshaller um = jc.createUnmarshaller();
+		return (JSPCustomTaglib) um.unmarshal(is);
+	}
+}
Index: src/org/jboss/tools/jst/web/kb/customtags/CustomTagName.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/CustomTagName.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/CustomTagName.java	(revision 0)
@@ -0,0 +1,50 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+public class CustomTagName {
+
+	private final String name;
+	private final String namespace;
+
+	public CustomTagName(String namespace, String name) {
+		this.namespace = namespace;
+		this.name = name;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (!(obj instanceof CustomTagName)) {
+			return false;
+		}
+		final CustomTagName other = (CustomTagName) obj;
+		final boolean nameEquals = (name == null)
+				? other.name == null
+				: name.equals(other.name);
+		final boolean namespaceEquals = (namespace == null)
+				? other.namespace == null
+				: namespace.equals(other.namespace);
+		return nameEquals && namespaceEquals;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public String getNamespace() {
+		return namespace;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 17;
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		result = prime * result + ((namespace == null) ? 0 : namespace.hashCode());
+		return result;
+	}
+}
Index: src/org/jboss/tools/jst/web/kb/customtags/JspCustomTagStorage.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/JspCustomTagStorage.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/JspCustomTagStorage.java	(revision 0)
@@ -0,0 +1,253 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+
+import javax.xml.bind.JAXBException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.core.JarEntryFile;
+import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
+import org.eclipse.wst.common.componentcore.ComponentCore;
+import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
+import org.jboss.tools.jst.web.kb.WebKbPlugin;
+
+/**
+ * This is a singleton class implemented with enum-singleton approach.
+ *
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+@SuppressWarnings("restriction")
+public enum JspCustomTagStorage {
+	INSTANCE;
+
+	public static final String EXT_TAGX = ".tagx"; //$NON-NLS-1$
+	public static final String EXT_TLD = ".tld"; //$NON-NLS-1$
+
+	private final Map<CustomTagName, CustomTag> classpathTagCache = new HashMap<CustomTagName, CustomTag>();
+	private final Map<String, JSPCustomTaglib> taglibMap = new HashMap<String, JSPCustomTaglib>();
+	
+	private JspCustomTagStorageConfig config;
+	
+	public IStorage getCustomTemplateStorage(JspCustomTagStorageConfig config, final IProject project, final CustomTagName customTagName) {
+		if (customTagName == null || customTagName.getNamespace() == null || customTagName.getName() == null) {
+			return null;
+		}
+		final IPath webAppPath = getWebAppFolderFullPath(project);
+		if (webAppPath == null) {
+			return null;
+		}
+		this.config = config;
+		IStorage storage = null;
+		if (!customTagName.getNamespace().startsWith(config.getCustomTagsWithTldURI())) {
+			storage = getTagx(webAppPath, customTagName);
+		}
+		if (storage == null) {
+			// search in web root
+			storage = findTagxInWebRoot(webAppPath, customTagName);
+		}
+		if (storage == null) {
+			// search in class path
+			storage = findTagxInClasspath(project, customTagName);
+		}
+		return storage;
+	}
+
+	public static IPath getWebAppFolderFullPath(IProject project) {
+		IVirtualComponent component = ComponentCore.createComponent(project);
+		if (component == null) {
+			return null;
+		}
+		IPath fullPath = component.getRootFolder().getUnderlyingFolder().getFullPath();
+		return fullPath;
+	}
+
+	private IFile getTagx(IPath webAppPath, CustomTagName customTagName) {
+		final String dir = customTagName.getNamespace().trim()
+				.replaceFirst(config.getCustomTagsURI(), ""); //$NON-NLS-1$
+		final String file = customTagName.getName() + EXT_TAGX;
+		final IPath tagxPath = webAppPath.append(dir).append(file);
+		return ResourcesPlugin.getWorkspace().getRoot().getFile(tagxPath);
+	}
+
+	private IStorage findTagxInWebRoot(final IPath webAppPath, CustomTagName customTagName) {
+		final IPath tldFromWebroot = findTldInWebRoot(customTagName.getNamespace(), webAppPath);
+		return (tldFromWebroot != null)
+				? getTagx(webAppPath, tldFromWebroot, customTagName)
+				: null;
+	}
+
+	private IStorage findTagxInClasspath(final IProject project, CustomTagName customTagName) {
+		IStorage res = null;
+		final String tagxFileName = getTagxFileName(customTagName);
+		if (classpathTagCache.containsKey(customTagName)) {
+			CustomTag customTag = classpathTagCache.get(customTagName);
+			if (new File(customTag.getJarName()).exists()) {
+				res = new CustomStringStorage(customTag.getContent(),
+						customTag.getResourcePath());
+			} else {
+				classpathTagCache.remove(customTag);
+			}
+		}
+		if (res != null) {
+			return res;
+		}
+		final ClasspathFile tagxFile = getFileFromClasspathJar(project,
+				tagxFileName.replaceFirst("/", "")); //$NON-NLS-1$ //$NON-NLS-2$
+		if (tagxFile != null) {
+			classpathTagCache.put(customTagName, new CustomTag(tagxFile.getJarName(),
+					tagxFile.getContent(), tagxFileName));
+			res = new CustomStringStorage(tagxFile.getContent(), tagxFileName);
+		}
+		return res;
+	}
+
+	/**
+	 * check is paht resolve real eclipse resouces
+	 * 
+	 * @param path
+	 * @return true - if path resource accessible
+	 */
+	public static boolean isPathAccessible(IPath path) {
+		IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
+		return file.isAccessible();
+	}
+
+	private IPath findTldInWebRoot(String namespace, IPath webAppPath) {
+		final String tldFileRelativePath = namespace.replaceFirst(config.getCustomTagsWithTldURI(),
+				config.getTldFolderRelativePath()) + EXT_TLD;
+		final IPath tldFilePath = webAppPath.append(tldFileRelativePath);
+		// check file in WEB-INF folder
+		return (isPathAccessible(tldFilePath)) ? tldFilePath : null;
+	}
+
+	private JSPCustomTagFile getCustomTagDescription(String tagName, IPath tldFilePath) {
+		String tldFileName = tldFilePath.toFile().getName();
+		JSPCustomTaglib customTaglib = taglibMap.get(tldFileName);
+		if (customTaglib == null) {
+			try {
+				customTaglib = TaglibUtil.unmarshalTldFile(tldFilePath);
+				taglibMap.put(tldFileName, customTaglib);
+			} catch (JAXBException e) {
+				WebKbPlugin.getDefault().logError(
+						"JSPCustomTagTemplate: unmarshal " + tldFileName + " error", e); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+		}
+		JSPCustomTagFile res = null;
+		if (customTaglib != null) {
+			for (JSPCustomTagFile customTagFile : customTaglib.getJspCustomTagFiles()) {
+				if (tagName.equalsIgnoreCase(customTagFile.getTagName())) {
+					res = customTagFile;
+					break;
+				}
+			}
+		}
+		return res;
+	}
+
+	private ClasspathFile getFileFromClasspathJar(final IProject project, String pathInJar) {
+		ClasspathFile res = null;
+		if (project == null) {
+			return res;
+		}
+		IJavaProject javaProject = JavaCore.create(project);
+		if (javaProject == null) {
+			return res;
+		}
+		String jarName = null;
+		JarEntryFile fileInJar = null;
+		try {
+			for (IPackageFragmentRoot fragmentRoot : javaProject.getAllPackageFragmentRoots()) {
+				if (fragmentRoot instanceof JarPackageFragmentRoot) {
+					final JarPackageFragmentRoot jarFragmentRoot = (JarPackageFragmentRoot) fragmentRoot;
+					final String jarNameTmp = jarFragmentRoot.getJar().getName();
+					final ZipEntry entry = jarFragmentRoot.getJar().getEntry(pathInJar);
+					if (entry != null) {
+						fileInJar = new JarEntryFile(pathInJar);
+						fileInJar.setParent(jarFragmentRoot);
+						jarName = jarNameTmp;
+						break;
+					}
+				}
+			}
+		} catch (JavaModelException e) {
+			WebKbPlugin.getDefault().logError(e);
+		} catch (CoreException e) {
+			WebKbPlugin.getDefault().logError(e);
+		}
+		if (fileInJar != null) {
+			res = new ClasspathFile(jarName, ReadContentUtil.getContent(fileInJar));
+		}
+		return res;
+	}
+
+	private IFile getTagx(IPath webAppPath, IPath tldFilePath, CustomTagName customTagName) {
+		IFile res = null;
+		final JSPCustomTagFile customTag = getCustomTagDescription(customTagName.getName(), tldFilePath);
+		if (customTag != null) {
+			final IPath tagxPath = webAppPath.append(customTag.getTagxPath());
+			res = ResourcesPlugin.getWorkspace().getRoot().getFile(tagxPath);
+		}
+		return res;
+	}
+
+	private String getTagxFileName(CustomTagName customTagName) {
+		return config.getJarTldFolderRelativePath()
+				+ customTagName.getNamespace().replaceFirst(config.getCustomTagsWithTldURI() + "/", "") + "/" + customTagName.getName() + EXT_TAGX; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+	}
+
+	private static class ClasspathFile {
+
+		private final String jarName;
+		private final String content;
+
+		private ClasspathFile(String jarName, String content) {
+			this.jarName = jarName;
+			this.content = content;
+		}
+
+		public String getContent() {
+			return content;
+		}
+
+		public String getJarName() {
+			return jarName;
+		}
+	}
+
+	private static class CustomTag {
+		
+		private final String content;
+		private final String jarName;
+		private final String resourcePath;
+
+		private CustomTag(String jarName, String content, String resoucePath) {
+			this.jarName = jarName;
+			this.content = content;
+			this.resourcePath = resoucePath;
+		}
+
+		public String getContent() {
+			return content;
+		}
+
+		public String getJarName() {
+			return jarName;
+		}
+
+		public String getResourcePath() {
+			return resourcePath;
+		}
+	}
+}
Index: src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTag.java
===================================================================
--- src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTag.java	(revision 0)
+++ src/org/jboss/tools/jst/web/kb/customtags/JSPCustomTag.java	(revision 0)
@@ -0,0 +1,56 @@
+package org.jboss.tools.jst.web.kb.customtags;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * @author Vitali Yemialyanchyk (vyemialyanchyk@gmail.com)
+ */
+public class JSPCustomTag {
+	
+	private String tagName;
+	
+	private String tagClass;
+	
+	private String bodyContent;
+	
+	private List<JSPCustomTagAttribute> tagAttributes = new ArrayList<JSPCustomTagAttribute>();
+
+	@XmlElement(name="name")
+	public String getTagName() {
+		return tagName;
+	}
+
+	public void setTagName(String tagName) {
+		this.tagName = tagName;
+	}
+
+	@XmlElement(name="tag-class")
+	public String getTagClass() {
+		return tagClass;
+	}
+
+	public void setTagClass(String tagClass) {
+		this.tagClass = tagClass;
+	}
+
+	@XmlElement(name="body-content")
+	public String getBodyContent() {
+		return bodyContent;
+	}
+
+	public void setBodyContent(String bodyContent) {
+		this.bodyContent = bodyContent;
+	}
+
+	@XmlElement(name="attribute")
+	public List<JSPCustomTagAttribute> getTagAttributes() {
+		return tagAttributes;
+	}
+
+	public void setTagAttributes(List<JSPCustomTagAttribute> tagAttributes) {
+		this.tagAttributes = tagAttributes;
+	}
+}
