/*******************************************************************************
* 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;
}
}
}