/******************************************************************************* * Copyright (c) 2011 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is 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: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.jsf.web.validation.composite; import java.io.IOException; import java.util.HashSet; import java.util.Set; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; 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.IStatus; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.validation.internal.core.ValidationException; import org.eclipse.wst.validation.internal.provisional.core.IReporter; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; import org.jboss.tools.common.el.core.resolver.ELContext; import org.jboss.tools.common.validation.ContextValidationHelper; import org.jboss.tools.common.validation.IPreferenceInfo; import org.jboss.tools.common.validation.IProjectValidationContext; import org.jboss.tools.common.validation.IValidatingProjectTree; import org.jboss.tools.common.validation.PreferenceInfoManager; import org.jboss.tools.common.validation.ValidatorManager; import org.jboss.tools.jsf.JSFModelPlugin; import org.jboss.tools.jsf.jsf2.model.CompositeComponentConstants; import org.jboss.tools.jsf.project.JSFNature; import org.jboss.tools.jsf.web.validation.JSFSeverityPreferences; import org.jboss.tools.jsf.web.validation.JSFValidationMessage; import org.jboss.tools.jst.web.kb.IPageContext; import org.jboss.tools.jst.web.kb.KbProjectFactory; import org.jboss.tools.jst.web.kb.PageContextFactory; import org.jboss.tools.jst.web.kb.internal.KbBuilder; import org.jboss.tools.jst.web.kb.internal.validation.KBValidator; import org.jboss.tools.jst.web.kb.internal.validation.WebValidator; import org.jboss.tools.jst.web.kb.taglib.IComponent; import org.jboss.tools.jst.web.kb.taglib.ICompositeTagLibrary; import org.jboss.tools.jst.web.kb.taglib.ITagLibrary; import org.jboss.tools.jst.web.kb.taglib.TagLibraryManager; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * JSF 2 composite component validator. * * @author Alexey Kazakov */ public class CompositeComponentValidator extends WebValidator { public static final String ID = "org.jboss.tools.jsf.CompositeComponentValidator"; //$NON-NLS-1$ public static final String SHORT_ID = "jboss.jsf.core"; //$NON-NLS-1$ public static final String PREFERENCE_PAGE_ID = "org.jboss.tools.jsf.ui.preferences.JSFValidationPreferencePage"; //$NON-NLS-1$ public static final String PROPERTY_PAGE_ID = "org.jboss.tools.jsf.ui.propertyPages.JSFValidationPreferencePage"; //$NON-NLS-1$ public static final String MESSAGE_ID_ATTRIBUTE_NAME = "JSF2_message_id"; //$NON-NLS-1$ public static final int UNKNOWN_COMPOSITE_COMPONENT_NAME_ID = 1; public static final int UNKNOWN_COMPOSITE_COMPONENT_ATTRIBUTE_ID = 2; /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.validation.IValidator#validate(java.util.Set, org.eclipse.core.resources.IProject, org.jboss.tools.jst.web.kb.internal.validation.ContextValidationHelper, org.jboss.tools.jst.web.kb.validation.IProjectValidationContext, org.jboss.tools.jst.web.kb.internal.validation.ValidatorManager, org.eclipse.wst.validation.internal.provisional.core.IReporter) */ public IStatus validate(Set<IFile> changedFiles, IProject project, ContextValidationHelper validationHelper, IProjectValidationContext validationContext, ValidatorManager manager, IReporter reporter) throws ValidationException { init(project, validationHelper, validationContext, manager, reporter); displaySubtask(JSFValidationMessage.SEARCHING_RESOURCES, new String[]{project.getName()}); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); Set<IFile> filesToValidate = new HashSet<IFile>(); Set<IPath> pathesToClean = new HashSet<IPath>(); for (IFile file : changedFiles) { pathesToClean.add(file.getFullPath()); // If the changed file is a composition component then collect all the pages which use this component. if(notValidatedYet(file)) { if(file.exists()) { filesToValidate.add(file); ITagLibrary[] libs = TagLibraryManager.getLibraries(file.getParent()); for (ITagLibrary lib : libs) { if(lib instanceof ICompositeTagLibrary) { collectRelatedPages(root, filesToValidate, pathesToClean, getComponentUri(lib.getURI(), file)); } } } else { // In case of deleted resource file IContainer folder = file.getParent(); if(folder!=null) { String[] segemnts = folder.getFullPath().segments(); StringBuilder libUri = new StringBuilder(); for (String segment : segemnts) { if(libUri.length()==0) { if(segment.equalsIgnoreCase("resources")) { libUri.append(CompositeComponentConstants.COMPOSITE_XMLNS); } } else { libUri.append('/').append(segment); } } if(libUri.length() > CompositeComponentConstants.COMPOSITE_XMLNS.length()) { collectRelatedPages(root, filesToValidate, pathesToClean, getComponentUri(libUri.toString(), file)); } } } } } // Remove all links between collected resources because they will be // linked again during validation. getValidationContext().removeLinkedCoreResources(SHORT_ID, pathesToClean); for (IFile file : filesToValidate) { validateResource(file); } return OK_STATUS; } private void collectRelatedPages(IWorkspaceRoot root, Set<IFile> filesToValidate, Set<IPath> pathesToClean, String uri) { Set<IPath> pathes = getValidationContext().getCoreResourcesByVariableName(SHORT_ID, uri, false); if(pathes!=null) { for (IPath path : pathes) { IFile page = root.getFile(path); if(page!=null && page.isAccessible()) { filesToValidate.add(page); pathesToClean.add(page.getFullPath()); } } } } /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.validation.IValidator#validateAll(org.eclipse.core.resources.IProject, org.jboss.tools.jst.web.kb.internal.validation.ContextValidationHelper, org.jboss.tools.jst.web.kb.validation.IProjectValidationContext, org.jboss.tools.jst.web.kb.internal.validation.ValidatorManager, org.eclipse.wst.validation.internal.provisional.core.IReporter) */ public IStatus validateAll(IProject project, ContextValidationHelper validationHelper, IProjectValidationContext validationContext, ValidatorManager manager, IReporter reporter) throws ValidationException { init(project, validationHelper, validationContext, manager, reporter); displaySubtask(JSFValidationMessage.VALIDATING_PROJECT, new String[]{project.getName()}); Set<IFile> files = validationHelper.getProjectSetRegisteredFiles(); Set<IFile> filesToValidate = new HashSet<IFile>(); for (IFile file : files) { if(file.isAccessible()) { if(notValidatedYet(file)) { filesToValidate.add(file); } } } for (IFile file : filesToValidate) { validateResource(file); } return OK_STATUS; } private String getComponentUri(String libUri, IFile file) { String fullName = file.getName(); String name = fullName; String ext = file.getFileExtension(); if(ext!=null) { name = name.substring(0, name.lastIndexOf("." + ext)); } return libUri + ":" + name; } private void validateResource(IFile file) { if(shouldFileBeValidated(file)) { displaySubtask(JSFValidationMessage.VALIDATING_RESOURCE, new String[]{file.getProject().getName(), file.getName()}); removeAllMessagesFromResource(file); coreHelper.getValidationContextManager().addValidatedProject(this, file.getProject()); ELContext context = PageContextFactory.createPageContext(file); if(context!=null && context instanceof IPageContext) { IPageContext pageContext = (IPageContext)context; Set<String> uris = pageContext.getURIs(); for (String uri : uris) { // Validate pages which use http://java.sun.com/jsf/composite/* name spaces only. if(uri.startsWith(CompositeComponentConstants.COMPOSITE_XMLNS) || uri.startsWith(CompositeComponentConstants.COMPOSITE_XMLNS_2_2)) { IModelManager manager = StructuredModelManager.getModelManager(); if (manager != null) { IStructuredModel model = null; try { model = manager.getModelForRead(file); if (model instanceof IDOMModel) { IDOMModel domModel = (IDOMModel) model; IDOMDocument document = domModel.getDocument(); validateNode(file, document.getDocumentElement()); } } catch (CoreException e) { JSFModelPlugin.getPluginLog().logError(e); } catch (IOException e) { JSFModelPlugin.getPluginLog().logError(e); } finally { if (model != null) { model.releaseFromRead(); } } } break; } } } } } private void validateNode(IFile file, Node node) { if (node instanceof Element) { String namespaceURI = node.getNamespaceURI(); if (namespaceURI != null && (namespaceURI.startsWith(CompositeComponentConstants.COMPOSITE_XMLNS) || namespaceURI.startsWith(CompositeComponentConstants.COMPOSITE_XMLNS_2_2))) { validateComponent(file, node); } NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { validateNode(file, children.item(i)); } } } private void validateComponent(IFile file, Node xmlComponent) { String tagName = xmlComponent.getLocalName(); if(tagName==null) { return; } String tagLibUri = xmlComponent.getNamespaceURI(); // Save the link between the composition component URI and the validating page getValidationContext().addLinkedCoreResource(SHORT_ID, tagLibUri + ":" + tagName, file.getFullPath(), false); ITagLibrary[] libs = KbProjectFactory.getKbProject(file.getProject(), true).getTagLibraries(tagLibUri); if(libs.length>0) { IComponent kbComponent = libs[0].getComponent(tagName); if(kbComponent!=null) { NamedNodeMap map = xmlComponent.getAttributes(); for (int i = 0; i < map.getLength(); i++) { Node xmlAttribute = map.item(i); String attributeName = xmlAttribute.getNodeName(); if(!"id".equals(attributeName) && kbComponent.getAttribute(attributeName)==null && xmlAttribute instanceof IndexedRegion) { // Mark unknown attribute name IndexedRegion region = (IndexedRegion)xmlAttribute; int offset = region.getStartOffset(); int length = attributeName.length(); addError(JSFValidationMessage.UNKNOWN_COMPOSITE_COMPONENT_ATTRIBUTE, JSFSeverityPreferences.UNKNOWN_COMPOSITE_COMPONENT_ATTRIBUTE, new String[]{attributeName, tagName}, length, offset, file, UNKNOWN_COMPOSITE_COMPONENT_ATTRIBUTE_ID); } } } else { addError(file, xmlComponent, tagName); } } else { addError(file, xmlComponent, tagName); } } /** * Mark unknown tag name * @param file * @param xmlComponent * @param tagName */ private void addError(IFile file, Node xmlComponent, String tagName) { if(xmlComponent instanceof IndexedRegion) { IndexedRegion region = (IndexedRegion)xmlComponent; int offset = region.getStartOffset(); int length = xmlComponent.getNodeName().length() + 1; addError(JSFValidationMessage.UNKNOWN_COMPOSITE_COMPONENT_NAME, JSFSeverityPreferences.UNKNOWN_COMPOSITE_COMPONENT_NAME, new String[]{tagName}, length, offset, file, UNKNOWN_COMPOSITE_COMPONENT_NAME_ID); } } /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.validation.IValidator#getId() */ public String getId() { return ID; } /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.validation.IValidator#getBuilderId() */ public String getBuilderId() { return KbBuilder.BUILDER_ID; } /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.validation.IValidator#getValidatingProjects(org.eclipse.core.resources.IProject) */ public IValidatingProjectTree getValidatingProjects(IProject project) { return createSimpleValidatingProjectTree(project); } /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.validation.IValidator#shouldValidate(org.eclipse.core.resources.IProject) */ public boolean shouldValidate(IProject project) { try { return project != null && project.isAccessible() && project.hasNature(JSFNature.NATURE_ID) && validateBuilderOrder(project) && isEnabled(project); } catch (CoreException e) { JSFModelPlugin.getDefault().logError(e); } return false; } private boolean validateBuilderOrder(IProject project) throws CoreException { return KBValidator.validateBuilderOrder(project, getBuilderId(), getId(), JSFSeverityPreferences.getInstance()); } /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.validation.IValidator#isEnabled(org.eclipse.core.resources.IProject) */ public boolean isEnabled(IProject project) { return JSFSeverityPreferences.isValidationEnabled(project); } /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.internal.validation.ValidationErrorManager#getPreference(org.eclipse.core.resources.IProject, java.lang.String) */ protected String getPreference(IProject project, String preferenceKey) { return JSFSeverityPreferences.getInstance().getProjectPreference(project, preferenceKey); } /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.internal.validation.ValidationErrorManager#getMaxNumberOfMarkersPerFile(org.eclipse.core.resources.IProject) */ public int getMaxNumberOfMarkersPerFile(IProject project) { return JSFSeverityPreferences.getMaxNumberOfProblemMarkersPerFile(project); } public IMarker addError(String message, String preferenceKey, String[] messageArguments, int length, int offset, IResource target, int messageId) { IMarker marker = addError(message, preferenceKey, messageArguments, length, offset, target); try { if(marker!=null) { marker.setAttribute(MESSAGE_ID_ATTRIBUTE_NAME, new Integer(messageId)); } } catch(CoreException e) { JSFModelPlugin.getDefault().logError(e); } return marker; } /* * (non-Javadoc) * @see org.jboss.tools.jst.web.kb.internal.validation.WebValidator#shouldValidateJavaSources() */ @Override protected boolean shouldValidateJavaSources() { return false; } /* * (non-Javadoc) * @see org.jboss.tools.common.validation.TempMarkerManager#getMessageBundleName() */ @Override protected String getMessageBundleName() { // TODO return null; } @Override public void registerPreferenceInfo() { PreferenceInfoManager.register(getProblemType(), new CompositeComponentPreferenceInfo()); } public static class CompositeComponentPreferenceInfo implements IPreferenceInfo{ @Override public String getPreferencePageId() { return PREFERENCE_PAGE_ID; } @Override public String getPropertyPageId() { return PROPERTY_PAGE_ID; } @Override public String getPluginId() { return JSFModelPlugin.PLUGIN_ID; } } }