/*
* 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.extension.ui;
import static org.teiid.designer.extension.ExtensionConstants.MED_EXTENSION;
import static org.teiid.designer.extension.ExtensionConstants.PLUGIN_ID;
import static org.teiid.designer.extension.ui.UiConstants.UTIL;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
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.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.texteditor.MarkerUtilities;
import org.teiid.designer.core.builder.AbstractTeiidProjectBuilder;
import org.teiid.designer.core.workspace.ModelUtil;
import org.teiid.designer.extension.ExtensionPlugin;
import org.teiid.designer.extension.ModelExtensionAssistantAggregator;
import org.teiid.designer.extension.definition.ModelExtensionDefinition;
import org.teiid.designer.extension.definition.ModelExtensionDefinitionParser;
import org.teiid.designer.extension.definition.ModelObjectExtensionAssistant;
import org.teiid.designer.extension.properties.ModelExtensionPropertyDefinition;
import org.teiid.designer.extension.registry.ModelExtensionRegistry;
import org.teiid.designer.extension.ui.UiConstants.ExtensionIds;
/**
* The <code>ModelExtensionDefinitionBuilder</code> is a project builder that creates resource problem markers for Model Extension
* Definition (MED) files (*.mxd).
*/
public final class ModelExtensionDefinitionBuilder extends AbstractTeiidProjectBuilder {
private static final boolean VISIT_MODELS = true; // turns visiting off for model files
private static final String SAX_ERR_PREFIX = "cvc-"; //$NON-NLS-1$
private static final String MED_VALIDATION_MSG = "MED Validation: "; //$NON-NLS-1$
private static final String SEE_DETAILS_MSG = " (See log for details)"; //$NON-NLS-1$
private static final String LEGACY_CLASSNAME_PREFIX = "com.metamatrix"; //$NON-NLS-1$
public static final String HAS_LEGACY_NAMES = "hasLegacyNames"; //$NON-NLS-1$
public static final String HAS_OLD_REST_PREFIX = "hasOldRestPrefix"; //$NON-NLS-1$
public static final String IS_OLD_RELATIONAL_MED = "isOldRelationalMed"; //$NON-NLS-1$
public static final String IS_OLD_REST_VERSION_MED = "isOldRestVersionMed"; //$NON-NLS-1$
private ModelExtensionAssistantAggregator aggregator = ExtensionPlugin.getInstance().getModelExtensionAssistantAggregator();
private ModelExtensionRegistry registry = ExtensionPlugin.getInstance().getRegistry();
/**
* {@inheritDoc}
*
* @see org.eclipse.core.resources.IncrementalProjectBuilder#build(int, java.util.Map,
* org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected IProject[] buildInternal( int kind,
Map<String, String> args,
IProgressMonitor monitor ) throws CoreException {
IProject project = getProject();
// don't do anything if project is closed or doesn't exist
if ((project == null) || !project.isAccessible()) {
return null;
}
MedVisitor visitor = new MedVisitor();
if ((IncrementalProjectBuilder.FULL_BUILD == kind) || (getDelta(project) == null)) {
getProject().accept(visitor); // gather all MEDs in project
} else {
IResourceDelta delta = getDelta(project);
delta.accept(visitor); // gather MEDs that have changed since last build
}
// collect the MED files and model files we need to build
Collection<IFile> medFilesToBuild = visitor.getMedFiles();
Collection<IFile> modelFilesToBuild = visitor.getModelFiles();
monitor.beginTask(Messages.medBuildTaskName, (medFilesToBuild.size() + modelFilesToBuild.size()));
if (!medFilesToBuild.isEmpty()) {
File medSchema = null; // schema used to validate MED
try {
medSchema = ExtensionPlugin.getInstance().getMedSchema();
} catch (Exception e) {
IStatus status = new Status(IStatus.ERROR, PLUGIN_ID, Messages.medSchemaNotFoundMsg, e);
throw new CoreException(status);
}
ModelExtensionDefinitionParser parser = new ModelExtensionDefinitionParser(medSchema);
MultiStatus status = new MultiStatus(PLUGIN_ID, IStatus.ERROR, Messages.medFileParseProblemMsg, null);
for (IFile medFile : medFilesToBuild) {
monitor.subTask(NLS.bind(Messages.medBuildSubTaskName, medFile.getName()));
try {
// clear existing markers if not already done by the clean build
medFile.deleteMarkers(null, true, IResource.DEPTH_INFINITE);
// parse to get parse problems
ModelExtensionDefinition med = parser.parse(medFile.getContents(), ExtensionPlugin.getInstance().createDefaultModelObjectExtensionAssistant());
// create new problem markers
createMarkers(medFile, med, parser.getErrors(), parser.getWarnings(), parser.getInfos());
} catch (Exception e) {
IStatus parseStatus = new Status(IStatus.ERROR, PLUGIN_ID, NLS.bind(Messages.medFileParseErrorMsg,
medFile.getName()), e);
status.add(parseStatus);
} finally {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
monitor.worked(1);
}
}
if (!status.isOK()) {
UTIL.log(status);
}
}
if (!modelFilesToBuild.isEmpty()) {
MultiStatus status = new MultiStatus(PLUGIN_ID, IStatus.ERROR, Messages.modelFilesBuildProblemMsg, null);
for (IFile modelFile : modelFilesToBuild) {
monitor.subTask(NLS.bind(Messages.modelBuildSubTaskName, modelFile.getName()));
try {
if (modelFile.exists()) {
modelFile.deleteMarkers(ExtensionIds.PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); // clear markers
refreshModelFileMarkers(modelFile); // create MED-related problem markers
}
} catch (Exception e) {
IStatus modelStatus = new Status(IStatus.ERROR, PLUGIN_ID, NLS.bind(Messages.modelFileBuildErrorMsg,
modelFile.getName()), e);
status.add(modelStatus);
} finally {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
monitor.worked(1);
}
}
if (!status.isOK()) {
UTIL.log(status);
}
}
// no other projects need also be rebuilt because this project was built
return null;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.core.resources.IncrementalProjectBuilder#clean(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected void clean( IProgressMonitor monitor ) throws CoreException {
MedVisitor visitor = new MedVisitor();
getProject().accept(visitor); // gather all MEDs in project
try {
Collection<IFile> medFilesToClean = visitor.getMedFiles();
Collection<IFile> modelFilesToClean = visitor.getModelFiles();
monitor.beginTask(Messages.medCleanTaskName, (medFilesToClean.size() + modelFilesToClean.size()));
// clean all MED problem markers
for (IFile medFile : medFilesToClean) {
try {
monitor.subTask(NLS.bind(Messages.medCleanSubTaskName, medFile.getName()));
medFile.deleteMarkers(null, true, IResource.DEPTH_INFINITE);
} finally {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
monitor.worked(1);
}
}
// clean MED-related problem markers in Teiid Designer models
for (IFile modelFile : modelFilesToClean) {
try {
monitor.subTask(NLS.bind(Messages.medCleanSubTaskName, modelFile.getName()));
modelFile.deleteMarkers(ExtensionIds.PROBLEM_MARKER, true, IResource.DEPTH_INFINITE);
} finally {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
monitor.worked(1);
}
}
} finally {
monitor.done();
}
}
/**
* @param file the MED or model file who will create the problem marker (precondition: not <code>null</code>)
* @param severity the marker severity
* @param message the marker message (precondition: not <code>null</code> or empty)
* @param markerId the Id for the marker
* @param hasLegacyNames when 'true' adds "hasLegacyNames" attribute to the marker
*/
private void createMarker( IFile file,
int severity,
String message,
String markerId,
boolean hasLegacyNames) {
// parameters
assert (file != null) : "file is null"; //$NON-NLS-1$
assert ((message != null) && !message.isEmpty()) : "message is empty"; //$NON-NLS-1$
// For severity=ERROR, Re-write the Raw SaxParser exception to something more readable
if (severity == IMarker.SEVERITY_ERROR) {
String originalMessage = message;
if (message.trim().startsWith(SAX_ERR_PREFIX)) {
int index1 = message.indexOf(':'); // colon following cvc code
int index2 = message.indexOf('.', index1); // end of first sentence of parser msg
StringBuffer sb = new StringBuffer(MED_VALIDATION_MSG);
sb.append(message.substring(index1 + 1, index2 + 1));
sb.append(SEE_DETAILS_MSG);
message = sb.toString();
}
// log the original message
UTIL.log(IStatus.ERROR, MED_VALIDATION_MSG + originalMessage);
}
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put(IMarker.SEVERITY, severity);
attributes.put(IMarker.MESSAGE, message);
if(hasLegacyNames) attributes.put(HAS_LEGACY_NAMES, true);
if( message.indexOf("\"rest\"") > -1 && message.indexOf("is not registered in workspace") > -1 ) {
attributes.put(HAS_OLD_REST_PREFIX, true);
}
if( message.indexOf("model extension definition \"relational\" found in model is a different version") > -1 ) {
attributes.put(IS_OLD_RELATIONAL_MED, true);
} else if( message.indexOf("model extension definition \"REST\" found in model is a different version") > -1 ) {
attributes.put(IS_OLD_REST_VERSION_MED, true);
}
try {
MarkerUtilities.createMarker(file, attributes, markerId);
} catch (CoreException e) {
UTIL.log(e);
}
}
/**
* @param medFile the MED file whose problem markers are being refreshed (precondition: not <code>null</code>)
* @param errors the parsing error messages (precondition: not <code>null</code>)
* @param warnings the parsing warning messages (precondition: not <code>null</code>)
* @param infos the parsing info messages (precondition: not <code>null</code>)
* @throws Exception if there is a problem writing the markers to the resource
*/
private void createMarkers( IFile medFile,
ModelExtensionDefinition med,
Collection<String> errors,
Collection<String> warnings,
Collection<String> infos ) throws Exception {
assert (medFile != null) : "medFile is null"; //$NON-NLS-1$
assert (errors != null) : "errors is null"; //$NON-NLS-1$
assert (warnings != null) : "warnings is null"; //$NON-NLS-1$
assert (infos != null) : "infos is null"; //$NON-NLS-1$
// create errors
for (String message : errors) {
createMarker(medFile, IMarker.SEVERITY_ERROR, message, ExtensionIds.PROBLEM_MARKER, false);
}
// create warnings
for (String message : warnings) {
createMarker(medFile, IMarker.SEVERITY_WARNING, message ,ExtensionIds.PROBLEM_MARKER, false);
}
// create infos
for (String message : infos) {
createMarker(medFile, IMarker.SEVERITY_INFO, message, ExtensionIds.PROBLEM_MARKER, false);
}
// Check for Legacy 'com.metamatrix' classnames
if(medHasLegacyClassnames(med)) {
createMarker(medFile, IMarker.SEVERITY_ERROR, Messages.medFileHasLegacyClassnames, ExtensionIds.PROBLEM_MARKER, true);
}
}
private boolean medsAreEqual( ModelExtensionDefinition thisMed,
ModelExtensionDefinition thatMed ) {
return thisMed.equals(thatMed);
}
/*
* Determine if the supplied MED contains property references to the legacy 'com.metamatrix' class names
* @param theMed the supplied MED
* @return 'true' if the MED contains properties that reference 'com.metamatrix' class names, 'false' if not.
*/
private boolean medHasLegacyClassnames( ModelExtensionDefinition theMed ) {
if (theMed == null)
return false;
boolean hasLegacyClassnames = false;
Map<String, Collection<ModelExtensionPropertyDefinition>> propMap = theMed.getPropertyDefinitions();
Set<String> mapKeys = propMap.keySet();
for(String mapKey : mapKeys) {
if(mapKey.startsWith(LEGACY_CLASSNAME_PREFIX)) {
hasLegacyClassnames = true;
break;
}
}
return hasLegacyClassnames;
}
/**
* @param modelFile the model file whose MED-related problem markers are being refreshed (cannot be <code>null</code>)
* @throws Exception if there is a problem obtaining MED information from the model file
*/
void refreshModelFileMarkers( IFile modelFile ) throws Exception {
for (String namespacePrefix : this.aggregator.getSupportedNamespacePrefixes(modelFile)) {
Object temp = this.registry.getModelExtensionAssistant(namespacePrefix);
// if there is no assistant than the MED is not registered
if ((temp == null) || (!(temp instanceof ModelObjectExtensionAssistant))) {
createMarker(modelFile, IMarker.SEVERITY_WARNING, NLS.bind(Messages.modelMedNotFoundInRegistry, namespacePrefix), ExtensionIds.PROBLEM_MARKER, false);
} else {
ModelObjectExtensionAssistant registryAssistant = (ModelObjectExtensionAssistant)temp;
ModelExtensionDefinition registryMed = registryAssistant.getModelExtensionDefinition();
ModelObjectExtensionAssistant modelAssistant = ExtensionPlugin.getInstance()
.createDefaultModelObjectExtensionAssistant(namespacePrefix);
ModelExtensionDefinition modelMed = modelAssistant.getModelExtensionDefinition(modelFile);
// Add marker if Model has properties with the legacy Class names ("com.metamatrix")
// No further analysis is done until legacy names are corrected.
if (medHasLegacyClassnames(modelMed)) {
createMarker(modelFile, IMarker.SEVERITY_ERROR, Messages.modelMedHasLegacyClassnames, ExtensionIds.PROBLEM_MARKER, true);
return;
}
if (!medsAreEqual(registryMed, modelMed)) {
// make sure MED is same version as the registered one
createMarker(modelFile, IMarker.SEVERITY_WARNING,
NLS.bind(Messages.modelMedDifferentVersionThanOneFoundInRegistry, namespacePrefix), ExtensionIds.PROBLEM_MARKER, false);
}
}
}
}
/**
* The <code>MedVisitor</code> gathers MED files that need their problem markers refreshed. A new visitor must be constructed
* for each build.
*/
class MedVisitor implements IResourceVisitor, IResourceDeltaVisitor {
private Collection<IFile> medFiles = new ArrayList<IFile>();
private Collection<IFile> modelFiles = new ArrayList<IFile>();
/**
* @return the MED files whose problem markers need to be refreshed (never <code>null</code>)
*/
public Collection<IFile> getMedFiles() {
return this.medFiles;
}
/**
* @return the Teiid Designer model files whose MED-related problem markers need to be refreshed (never <code>null</code>)
*/
public Collection<IFile> getModelFiles() {
return this.modelFiles;
}
/**
* @param resource the resource being checked (never <code>null</code>)
* @return <code>true</code> if resource is a MED file
*/
private boolean isMedFile( IResource resource ) {
return ((resource.getType() == IResource.FILE) && MED_EXTENSION.equals(resource.getFileExtension()) && resource.exists());
}
/**
* @param resource the resource being checked (never <code>null</code>)
* @return <code>true</code> if resource is a Teiid Designer model file
*/
private boolean isModelFile( IResource resource ) {
return ModelUtil.isModelFile(resource);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.core.resources.IResourceVisitor#visit(org.eclipse.core.resources.IResource)
*/
@Override
public boolean visit( IResource resource ) {
if (isMedFile(resource)) {
this.medFiles.add((IFile)resource);
} else if (VISIT_MODELS && isModelFile(resource)) {
this.modelFiles.add((IFile)resource);
}
return true; // visit resource members
}
/**
* {@inheritDoc}
*
* @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
*/
@Override
public boolean visit( IResourceDelta delta ) {
IResource resource = delta.getResource();
if (isMedFile(resource)) {
this.medFiles.add((IFile)resource);
} else if (VISIT_MODELS && isModelFile(resource)) {
this.modelFiles.add((IFile)resource);
}
return true; // visit children
}
}
}