/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.metamodels.xsd.validator;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xsd.XSDDiagnostic;
import org.eclipse.xsd.XSDDiagnosticSeverity;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDSchemaDirective;
import org.eclipse.xsd.util.XSDResourceImpl;
import org.teiid.core.designer.ModelerCoreException;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.ValidationPreferences;
import org.teiid.designer.core.builder.ResourceValidator;
import org.teiid.designer.core.container.ResourceFinder;
import org.teiid.designer.core.validation.ValidationContext;
import org.teiid.designer.core.validation.ValidationProblem;
import org.teiid.designer.core.validation.ValidationProblemImpl;
import org.teiid.designer.core.validation.ValidationResult;
import org.teiid.designer.core.validation.ValidationResultImpl;
import org.teiid.designer.core.workspace.ModelFileUtil;
import org.teiid.designer.core.workspace.ModelResource;
import org.teiid.designer.core.workspace.ModelUtil;
import org.teiid.designer.core.workspace.ModelWorkspace;
import org.teiid.designer.metamodels.xsd.XsdPlugin;
/**
* XsdResourceValidator
*
* @since 8.0
*/
public class XsdResourceValidator implements ResourceValidator {
// ===========================================================================================================================
// Variables
private int prefStatus = IStatus.WARNING;
private Set unloaded, xsds;
private Set allResources, modifiedResources;
// ==================================================================================
// I N T E R F A C E M E T H O D S
// ==================================================================================
/**
* @see org.teiid.designer.core.builder.ResourceValidator#isValidatorForObject(java.lang.Object)
* @since 4.2
*/
@Override
public boolean isValidatorForObject( final Object obj ) {
if (obj instanceof IResource) {
final IResource iResource = (IResource)obj;
if (ModelUtil.isXsdFile(iResource)) {
return true;
}
} else if (obj instanceof Resource) {
final Resource eResource = (Resource)obj;
if (ModelUtil.isXsdFile(eResource)) {
return true;
}
} else if (obj instanceof File) {
final File f = (File)obj;
if (f.exists() && ModelFileUtil.isXsdFile(f)) {
return true;
}
}
return false;
}
/**
* @see org.teiid.designer.core.builder.ResourceValidator#validate(org.eclipse.core.runtime.IProgressMonitor,
* java.lang.Object, org.teiid.designer.core.validation.ValidationContext)
* @since 4.2
*/
@Override
public void validate( final IProgressMonitor monitor,
final Object obj,
final ValidationContext context ) throws ModelerCoreException {
if (!isValidatorForObject(obj)) {
final Object[] params = new Object[] {this.getClass().getName(), (obj != null ? obj.getClass().getName() : null)};
final String msg = XsdPlugin.Util.getString("XsdResourceValidator.validator_cannot_be_used_to_validate_the_object", params); //$NON-NLS-1$
throw new ModelerCoreException(msg);
}
final IProgressMonitor progressMonitor = (monitor != null ? monitor : new NullProgressMonitor());
if (obj instanceof IResource) {
final IResource iResource = (IResource)obj;
final ModelWorkspace workspace = ModelerCore.getModelWorkspace();
final ModelResource mResource = workspace.findModelResource(iResource);
// jh 6/9/2006 Defect 21047 Fixing unreported NPE
if (mResource != null) {
final Resource eResource = mResource.getEmfResource();
this.validate(progressMonitor, eResource, context);
}
} else if (obj instanceof Resource) {
final Resource eResource = (Resource)obj;
this.validate(progressMonitor, eResource, context);
} else if (obj instanceof File) {
try {
final File f = (File)obj;
final URI uri = URI.createURI(f.getAbsolutePath());
final Resource eResource = ModelerCore.getModelContainer().getResource(uri, false);
this.validate(progressMonitor, eResource, context);
} catch (Exception err) {
throw new ModelerCoreException(err);
}
}
}
/**
* @see org.teiid.designer.core.builder.ResourceValidator#addMarkers(org.teiid.designer.core.validation.ValidationContext,
* org.eclipse.core.resources.IResource)
* @since 4.2
*/
@Override
public void addMarkers( final ValidationContext context,
final IResource iResource ) throws ModelerCoreException {
if (context != null && context.hasResults()) {
final List results = context.getValidationResults();
try {
for (final Iterator iter = results.iterator(); iter.hasNext();) {
final ValidationResult result = (ValidationResult)iter.next();
if (result != null && result.hasProblems()) {
ValidationProblem[] problems = result.getProblems();
String locationPath = result.getLocationPath();
String locationUri = result.getLocationUri();
String targetUri = result.getTargetUri();
for (int probCnt = 0; probCnt < problems.length; probCnt++) {
createProblemMarker(locationPath, locationUri, targetUri, problems[probCnt], iResource);
}
if (result.isFatalResource()) {
return;
}
}
}
} catch (CoreException err) {
throw new ModelerCoreException(err);
}
}
}
/**
* @see org.teiid.designer.core.builder.ResourceValidator#isValidatorForResource(org.eclipse.emf.ecore.resource.Resource)
*/
@Override
public boolean isValidatorForResource( final IResource iResource ) {
if (ModelUtil.isXsdFile(iResource)) {
return true;
}
return false;
}
/**
* @see org.teiid.designer.core.builder.ResourceValidator#validate(org.eclipse.emf.ecore.resource.Resource)
*/
@Override
public void validate( final IProgressMonitor monitor,
final Resource resource,
final IResource iResource,
final ValidationContext context ) throws ModelerCoreException {
CoreArgCheck.isNotNull(iResource);
if (!ModelUtil.isXsdFile(iResource)) {
final String msg = XsdPlugin.Util.getString("XsdResourceValidator.XsdResourceValidator_may_only_be_used_to_validate_XsdResources_1"); //$NON-NLS-1$
throw new ModelerCoreException(msg);
}
this.validate(monitor, resource, context);
}
/**
* @see org.teiid.designer.core.builder.ResourceValidator#validationStarted(java.util.Collection)
* @since 4.3.2
*/
@Override
public void validationStarted( final Collection resources,
final ValidationContext context ) {
// If the validation preferences is IGNORE then return
if (!canValidate(context)) {
return;
}
this.unloaded = new HashSet();
this.xsds = new HashSet();
allResources = new HashSet();
modifiedResources = new HashSet();
final ModelWorkspace workspace = ModelerCore.getModelWorkspace();
// Load all XSD resources to track down dependencies, remembering which resources started out unloaded
ResourceSet ctnr = null;
for (final Iterator iter = resources.iterator(); iter.hasNext();) {
final Object obj = iter.next();
Resource emfResrc = null;
try {
if (obj instanceof IResource) {
final ModelResource model = workspace.findModelResource((IResource)obj);
emfResrc = (model != null ? model.getEmfResource() : null);
} else if (obj instanceof Resource) {
emfResrc = (Resource)obj;
}
if (!ModelUtil.isXsdFile(emfResrc)) {
continue;
}
// All XSD resources must be saved prior to validation.
if (emfResrc instanceof XSDResourceImpl && emfResrc.isModified()) {
final String msg = XsdPlugin.Util.getString("XsdResourceValidator.Can_not_perform_validation_on_unsaved_Xsd_Resources.__Please_save_and_revalidate_1"); //$NON-NLS-1$
// Add a marker to the schema and return. Don't try to perform validation as it will
// reload the schema causing any unsaved changes to be lost.
this.addProblem(((XSDResourceImpl)emfResrc).getSchema(), 0, IStatus.ERROR, msg, context);
return;
}
// Add resource to list of XSD resources to unload/reload. Continue working with resource only if it wasn't
// already in the list.
if (this.xsds.add(emfResrc)) {
allResources.add(emfResrc);
// Determine container if not already known
if (ctnr == null) {
ctnr = emfResrc.getResourceSet();
if (ctnr == null) {
ctnr = ModelerCore.getModelContainer();
}
if (ctnr != null) {
// Record which resources started out as unloaded
for (Iterator iter2 = ctnr.getResources().iterator(); iter2.hasNext();) {
Resource r = (Resource)iter2.next();
if (!r.isLoaded()) {
this.unloaded.add(r);
}
allResources.add(r);
if (r.isModified()) {
modifiedResources.add(r);
}
}
}
}
// Load resource if unloaded
if (!emfResrc.isLoaded()) {
emfResrc.load(null);
// Remember resource started out unloaded
// this.unloaded.add(emfResrc);
}
// Load referenced XSD resources
loadReferencedXsdResources(emfResrc, this.xsds, ctnr);
}
} catch (final Exception err) {
XsdPlugin.Util.log(err);
}
}
// Unload all XSD resources
for (final Iterator iter = this.xsds.iterator(); iter.hasNext();) {
((Resource)iter.next()).unload();
}
// Load all XSD resources
for (final Iterator iter = this.xsds.iterator(); iter.hasNext();) {
try {
((Resource)iter.next()).load(null);
} catch (final IOException err) {
XsdPlugin.Util.log(err);
}
}
}
/**
* @see org.teiid.designer.core.builder.ResourceValidator#validationEnded()
* @since 4.3.2
*/
@Override
public void validationEnded( final ValidationContext context ) {
// If the validation preferences is IGNORE then return
if (!canValidate(context)) {
return;
}
// Unload resources that started out unloaded before validation.
for (final Iterator iter = this.unloaded.iterator(); iter.hasNext();) {
((Resource)iter.next()).unload();
}
this.unloaded = this.xsds = null;
// reset modified status
Iterator iter = allResources.iterator();
while (iter.hasNext()) {
Resource resource = (Resource)iter.next();
if (modifiedResources.contains(resource)) {
resource.setModified(true);
} else {
resource.setModified(false);
}
}
modifiedResources = allResources = null;
}
// ==================================================================================
// P R I V A T E M E T H O D S
// ==================================================================================
private void loadReferencedXsdResources( Resource resource,
final Set resources,
final ResourceSet container ) throws Exception {
// Get a ResourceFinder to use when resolving dependent resource references
CoreArgCheck.isNotNull(ModelerCore.getContainer(resource));
ResourceFinder finder = ModelerCore.getContainer(resource).getResourceFinder();
if (!resource.isLoaded()) {
ResourceSet ctnr = resource.getResourceSet();
if (ctnr == null) {
ctnr = ModelerCore.getModelContainer();
}
final Map options = (ctnr == null ? null : ctnr.getLoadOptions());
resource.load(options);
}
XSDSchema schema = ((XSDResourceImpl)resource).getSchema();
for (final Iterator contents = schema.getContents().iterator(); contents.hasNext();) {
final Object obj = contents.next();
if (obj instanceof XSDSchemaDirective) {
resource = finder.findByImport((XSDSchemaDirective)obj, false);
if (resource != null && resource.getResourceSet() == container) {
// Load referenced XSD resources
if (resources.add(resource)) {
// Recurse to get the referenced resource
loadReferencedXsdResources(resource, resources, container);
}
}
}
}
}
/**
* @see org.teiid.designer.core.builder.ResourceValidator#validate(org.eclipse.emf.ecore.resource.Resource)
*/
private void validate( final IProgressMonitor monitor,
final Resource resource,
final ValidationContext context ) throws ModelerCoreException {
CoreArgCheck.isNotNull(resource);
// If not already done via multiple resources being validated simultaneously,
// determine all XSD's involved, and unload & reload them.
final boolean reload = (this.xsds == null);
if (reload) {
final HashSet xsds = new HashSet(1);
xsds.add(resource);
validationStarted(xsds, context);
}
// If the validation preferences is IGNORE then return
if (!canValidate(context)) {
return;
}
try {
if (!(resource instanceof XSDResourceImpl)) {
final String msg = XsdPlugin.Util.getString("XsdResourceValidator.XsdResource_validator_may_only_be_used_to_validate_instances_of_XsdResourceImpl_1"); //$NON-NLS-1$
throw new ModelerCoreException(msg);
}
final XSDResourceImpl xsdResource = (XSDResourceImpl)resource;
// Get the list of all XSD resources in the workspace ...
final ResourceSet resourceSet = xsdResource.getResourceSet();
List xsdResources = getXsdResources(resourceSet);
// Store the current modified state of those XSD resources so that their state
// can be reset to the correct value after unloading and then reloading the
// resource being validated. This is necessary since reloading an XSD
// that contains an XSDImport the imported resource will get marked as modified
// even if it was originally unmodified.
final Map xsdResourceModifiedState = new HashMap();
for (Iterator iter = xsdResources.iterator(); iter.hasNext();) {
final Resource r = (Resource)iter.next();
final String rUri = r.getURI().toString();
if (r.isModified()) {
xsdResourceModifiedState.put(rUri, Boolean.TRUE);
} else {
xsdResourceModifiedState.put(rUri, Boolean.FALSE);
}
}
final XSDSchema schema = xsdResource.getSchema();
if (schema == null) {
final String msg = XsdPlugin.Util.getString("XsdResourceValidator.Error_processing_XSD_file_during_validation_1"); //$NON-NLS-1$
throw new ModelerCoreException(msg);
}
// Validate the schema, generating all new diagnostics
if (monitor != null && monitor.isCanceled()) {
return;
}
schema.validate();
final List diagnostics = schema.getAllDiagnostics();
for (final Iterator iter = diagnostics.iterator(); iter.hasNext();) {
final XSDDiagnostic next = (XSDDiagnostic)iter.next();
this.addProblem(next, context);
}
xsdResource.setModified(false);
// Get the list of all XSD resources in the workspace ...
xsdResources = getXsdResources(resourceSet);
// Reset the modified state of the XSD resource to what it was prior
// to unloading and then reloading the resource being validated
for (Iterator iter = xsdResources.iterator(); iter.hasNext();) {
final Resource r = (Resource)iter.next();
final String rUri = r.getURI().toString();
r.setModified(false);
if (xsdResourceModifiedState.containsKey(rUri)) {
Boolean modifiedState = (Boolean)xsdResourceModifiedState.get(rUri);
if (modifiedState == Boolean.TRUE) {
r.setModified(true);
}
}
}
} finally {
if (reload) {
validationEnded(context);
}
}
}
private boolean canValidate( final ValidationContext context ) {
// See what the preference is ...
this.prefStatus = context.getPreferenceStatus(ValidationPreferences.XSD_MODEL_VALIDATION, IStatus.WARNING);
// If the validation preference is IGNORE an IStatus.OK is returned
if (this.prefStatus == IStatus.OK) {
return false;
}
return true;
}
private static List getXsdResources( final ResourceSet resourceSet ) {
final List result = new ArrayList();
if (resourceSet != null) {
for (Iterator iter = resourceSet.getResources().iterator(); iter.hasNext();) {
final Resource resource = (Resource)iter.next();
if (resource instanceof XSDResourceImpl) {
result.add(resource);
}
}
}
return result;
}
private void addProblem( final Object object,
final int code,
final int severity,
final String msg,
final ValidationContext context ) {
ValidationProblem problem = new ValidationProblemImpl(code, severity, msg);
ValidationResult result = new ValidationResultImpl(object);
result.addProblem(problem);
context.addResult(result);
}
private void addProblem( final XSDDiagnostic diagnostic,
final ValidationContext context ) {
final EObject target = diagnostic.getPrimaryComponent();
if (target != null) {
int severity = Math.min(getStatusSeverity(diagnostic), this.prefStatus);
this.addProblem(target, 0, severity, diagnostic.getMessage(), context);
}
}
/**
* Create a marker given a validationProblem
*/
private void createProblemMarker( final String locationPath,
final String locationUri,
final String targetUri,
final ValidationProblem problem,
final IResource resource ) throws CoreException {
IMarker marker = resource.createMarker(IMarker.PROBLEM);
marker.setAttribute(IMarker.LOCATION, locationPath);
marker.setAttribute(ModelerCore.MARKER_URI_PROPERTY, locationUri);
marker.setAttribute(ModelerCore.TARGET_MARKER_URI_PROPERTY, targetUri);
marker.setAttribute(IMarker.MESSAGE, problem.getMessage());
setMarkerSeverity(marker, problem.getSeverity());
}
/**
* Get the severity given the XSDDiagnostic.
*/
private int getStatusSeverity( final XSDDiagnostic diagnostic ) {
final int severity = diagnostic.getSeverity().getValue();
switch (severity) {
case XSDDiagnosticSeverity.ERROR:
return IStatus.ERROR;
case XSDDiagnosticSeverity.WARNING:
return IStatus.WARNING;
case XSDDiagnosticSeverity.INFORMATION:
return IStatus.INFO;
default:
return IStatus.OK;
}
}
/**
* Sets the severity on the specified marker using the specified validation problem severity and the current user preference
* setting.
*
* @param marker the marker
* @param theSeverity the {@link ValidationProblem} severity
*/
private void setMarkerSeverity( final IMarker marker,
int theSeverity ) throws CoreException {
// adjust severity if necessary based on what the validate schema user preference is set to
if (theSeverity > this.prefStatus) {
do {
--theSeverity;
} while (theSeverity > this.prefStatus);
}
switch (theSeverity) {
case IStatus.ERROR:
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
break;
case IStatus.WARNING:
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING);
break;
case IStatus.INFO:
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
break;
default:
return;
}
}
}