package com.sap.tc.moin.incubation.mm.internal.resource;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
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.IWorkspaceRunnable;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.ExportPackageDescription;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.plugin.IPluginBase;
import org.eclipse.pde.core.plugin.PluginRegistry;
import com.sap.mi.fwk.ConnectionManager;
import com.sap.mi.fwk.ModelAdapter;
import com.sap.tc.logging.Location;
import com.sap.tc.moin.incubation.mm.internal.Activator;
import com.sap.tc.moin.repository.Connection;
import com.sap.tc.moin.repository.ModelPartition;
import com.sap.tc.moin.repository.PRI;
import com.sap.tc.moin.repository.ide.MoinFactory;
import com.sap.tc.moin.repository.ide.metamodels.MmGenerationBaseInfo;
import com.sap.tc.moin.repository.ide.metamodels.MmGenerationInfo;
import com.sap.tc.moin.repository.ide.metamodels.MmGenerator;
import com.sap.tc.moin.repository.jmigenerator.controller.MoinMMGeneration;
import com.sap.tc.moin.repository.metamodels.MetaModelCatalog;
public final class MmBuilder extends IncrementalProjectBuilder {
public static final String BUILDER_ID = "com.sap.tc.moin.metamodel.Builder"; //$NON-NLS-1$
public static final String NATURE_ID = "com.sap.tc.moin.metamodel.BuildNature"; //$NON-NLS-1$
public static final String MARKER_TYPE_PROBLEM = "com.sap.tc.moin.metamodel.problemMarker";
public static final String MARKER_ATT_MISSING_PACKAGES = "missingPackageExports";
public static final String JMI_SRC_PATH = "jmisrc"; //$NON-NLS-1$
public static final String MOIN_FOLDER = "moin"; //$NON-NLS-1$
public static final String MOIN_SRC_FOLDER = "moinsrc"; //$NON-NLS-1$
public static final String JAVA_SRC_FOLDER = "java"; //$NON-NLS-1$
public static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
public static final String VENDOR_DEFAULT = "sap.com";
static final String PACKAGELIST_SEPARATOR = "; ";
@Override
@SuppressWarnings("unchecked")
protected IProject[] build(final int kind, final Map args, IProgressMonitor monitor) throws CoreException {
if (kind == IncrementalProjectBuilder.FULL_BUILD) {
fullBuild(monitor);
} else {
final IResourceDelta delta = getDelta(getProject());
if (delta == null) {
fullBuild(monitor);
} else {
incrementalBuild(delta, monitor);
}
}
return null;
}
@Override
protected void clean(final IProgressMonitor monitor) throws CoreException {
ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
public void run(final IProgressMonitor monitor) throws CoreException {
final String msg = NLS.bind("Cleaning {0}", getProject().getName());
monitor.subTask(msg);
final IFolder srcFolder = getJmiFolder();
if (srcFolder.exists()) {
srcFolder.delete(true, false, monitor);
}
srcFolder.create(true, true, monitor);
IResource manifest = getManifest(getProject());
manifest.deleteMarkers(MARKER_TYPE_PROBLEM, false, IResource.DEPTH_INFINITE);
}
}, monitor);
super.clean(monitor);
}
protected void fullBuild(IProgressMonitor monitor) throws CoreException {
clean(monitor);
final String msg = NLS.bind("Building {0}", getProject().getName());
monitor.subTask(msg);
List<IFile> partitionFiles = getMetamodelPartitionIFiles(getProject());
createJmiJavaFiles(partitionFiles, monitor);
}
protected void incrementalBuild(final IResourceDelta delta, final IProgressMonitor monitor) throws CoreException {
final String msg = NLS.bind("Building {0}", getProject().getName());
monitor.subTask(msg);
final XMIFileFinder visitor = new XMIFileFinder(getOutputLocation(getProject()));
delta.accept(visitor);
if (visitor.hasRelevantChanges()) {
fullBuild(monitor);
}
}
private static IPath getOutputLocation(final IProject project) throws CoreException {
if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
final IJavaProject javaProject = JavaCore.create(project);
return javaProject.getOutputLocation();
}
return null;
}
private IFolder getJmiFolder() {
final IFolder srcFolder = getProject().getFolder(MmBuilder.JMI_SRC_PATH);
return srcFolder;
}
@SuppressWarnings("deprecation")
private void createJmiJavaFiles(final List<IFile> partitionFiles, final IProgressMonitor monitor) throws CoreException {
IProject project = getProject();
IPath jmiFolderPath = getJmiFolder().getLocation();
Collection<MmGenerationBaseInfo> additionalMetamodelsToConsider = null;
final Properties buildFileProperties = getMetamodelProperties(project);
if (buildFileProperties == null) {
throw new IllegalStateException("Metamodel information could not be found"); //$NON-NLS-1$
}
if (additionalMetamodelsToConsider == null) {
additionalMetamodelsToConsider = new ArrayList<MmGenerationBaseInfo>();
}
IFolder moinFolder = getTargetMoinFolder(project);
if (moinFolder == null) {
return;
}
String containerName = buildFileProperties.getProperty(MmGenerator.CONTAINER);
final MmGenerationBaseInfo baseInfo = new MmGenerationBaseInfo(moinFolder.getLocation().toOSString(), containerName);
additionalMetamodelsToConsider.add(baseInfo);
IPath outputPath = project.getLocation().append(getOutputLocation(project).removeFirstSegments(1));
final File outputLocation = new File(outputPath.toOSString());
try {
String metamodelPartitionFilesSourcePath = getMetamodelPartitionFilesSourcePath(project);
File[] files = new File[partitionFiles.size()];
for (int i = 0; i < files.length; i++) {
files[i] = new File(partitionFiles.get(i).getLocation().toOSString());
}
final File jmiFolder = new File(jmiFolderPath.toOSString());
MoinFactory.getMmGenerator().generate(
new MmGenerationInfo(metamodelPartitionFilesSourcePath, files, jmiFolder, outputLocation, buildFileProperties, false,
false), additionalMetamodelsToConsider);
} catch (final IllegalStateException e) { // $JL-EXC$
// This is a hack since the generation above crashes due to its own
// misconfigured connection. However, we need the partial results
// (moinarch.properties...). Fix is to call the underlying code
// generator directly.
MoinMMGeneration generation = completeBuild(project, jmiFolderPath, containerName, partitionFiles);
String msg = NLS.bind("Refreshing {0}", project.getName());
monitor.subTask(msg);
getJmiFolder().refreshLocal(IResource.DEPTH_INFINITE, monitor);
// For some reasons the Java model is not uptodate after the
// previous refresh on clean builds, which leads to PDE's manifest
// validator (a successor builder) to yield some "exported
// packages are not existing" markers. Fix is to checkpoint here
// leading to resource change events being broadcasted. This
// apparently makes the Java model consistent.
project.getWorkspace().checkpoint(false);
markMissingPackageExports(project, generation.getGeneratedPathFragments());
} catch (final Exception e) {
Activator.error("Exception during jmi java file generation", e, Location.getLocation(this)); //$NON-NLS-1$
}
}
private static Properties getMetamodelProperties(final IProject project) throws CoreException {
IPluginBase plugin = PluginRegistry.findModel(project).getPluginBase();
String name = plugin.getId();
String vendor = plugin.getProviderName();
String desc = plugin.getName();
Properties buildFileProperties = new Properties();
buildFileProperties.put(MmGenerator.CONTAINER, getContainerName(name, vendor));
buildFileProperties.put(MmGenerator.DC_DESCRIPTION, desc);
buildFileProperties.put(MmGenerator.DC_NAME, name);
buildFileProperties.put(MmGenerator.DC_VENDOR, vendor != null ? vendor : VENDOR_DEFAULT);
buildFileProperties.put(MmGenerator.DC_VERSION, plugin.getVersion());
buildFileProperties.put(MmGenerator.FACILITY_NAME, "PF"); //$NON-NLS-1$
return buildFileProperties;
}
private static IFolder getTargetMoinFolder(final IProject project) {
IFolder folder = project.getFolder(MOIN_FOLDER + "/meta");
if (folder.exists()) {
return folder;
}
return null;
}
private static String getMetamodelPartitionFilesSourcePath(final IProject project) throws CoreException {
return getTargetMoinFolder(project).getLocation().toString();
}
static List<IFile> getMetamodelPartitionIFiles(final IProject project) throws CoreException {
final XMIFileFinder visitor = new XMIFileFinder(getOutputLocation(project));
project.accept(visitor);
List<IFile> xmiFiles = visitor.getPartitionFiles();
return xmiFiles;
}
public static String getContainerName(String name, String vendor) {
if (vendor == null || vendor.length() == 0) {
vendor = VENDOR_DEFAULT;
}
return vendor.trim() + "/" + name.trim(); //$NON-NLS-1$
}
private MoinMMGeneration completeBuild(IProject project, IPath outputLocation, String containerName, List<IFile> xmiFiles) {
Connection connection = ConnectionManager.getInstance().createConnection(project);
connection.setLabel(NLS.bind("JMI generation for {0}", project.getName()));
try {
MoinMMGeneration generation = new MoinMMGeneration(null, null);
generation.setDestPathJmi(outputLocation.toOSString());
generation.setGenerateMof(false);
// ignoreChecks=true since we do not want to abort the build due to some
// (minor) constraint violations
generation.setIgnoreMetamodelCheck(true);
Set<ModelPartition> mps = new HashSet<ModelPartition>();
for (IFile file : xmiFiles) {
PRI pri = ModelAdapter.getInstance().getPri(file, connection);
ModelPartition partition = connection.getPartition(pri);
if (partition != null) {
mps.add(partition);
}
}
generation.execute(connection, mps, containerName);
return generation;
} finally {
connection.close();
}
}
private void markMissingPackageExports(IProject project, Set<List<String>> mmPackages) throws CoreException {
BundleDescription bundleDescription = PluginRegistry.findModel(project).getBundleDescription();
if (bundleDescription == null) {
// may happen for corrupt manifest files
return;
}
ExportPackageDescription[] exportPackages = bundleDescription.getExportPackages();
Set<String> exportedPackageNames = new HashSet<String>(exportPackages.length);
for (ExportPackageDescription pack : exportPackages) {
exportedPackageNames.add(pack.getName());
}
IJavaProject javaProject = JavaCore.create(project);
StringBuilder missingPackages = new StringBuilder();
for (List<String> mmPackageList : mmPackages) {
StringBuilder packBuilder = new StringBuilder();
for (Iterator<String> iterator = mmPackageList.iterator(); iterator.hasNext();) {
String pack = iterator.next();
packBuilder.append(pack);
if (iterator.hasNext()) {
packBuilder.append('.');
}
}
String packString = packBuilder.toString();
if (!exportedPackageNames.contains(packString) && isJavaPackage(packString, project, javaProject)) {
missingPackages.append(packString).append(PACKAGELIST_SEPARATOR);
}
}
if (missingPackages.length() == 0) {
return;
}
IFile manifest = getManifest(project);
IMarker marker = manifest.createMarker(MARKER_TYPE_PROBLEM);
String[] attNames = new String[] { IMarker.SEVERITY, IMarker.MESSAGE, MARKER_ATT_MISSING_PACKAGES };
String msg = NLS.bind("The following metamodel packages are not exported in plugin's manifest: {0}", missingPackages.substring(0,
missingPackages.length() - PACKAGELIST_SEPARATOR.length()));
Object[] attValues = new Object[] { IMarker.SEVERITY_ERROR, msg, missingPackages.toString()};
marker.setAttributes(attNames, attValues);
}
private boolean isJavaPackage(String packageName, IProject project, IJavaProject javaProject) throws CoreException {
IPath packPath = project.getFolder(JMI_SRC_PATH).getFullPath().append(packageName.replace('.', '/'));
IPackageFragment packageFragment = javaProject.findPackageFragment(packPath);
if (packageFragment != null && packageFragment.containsJavaResources()) {
return true;
}
return false;
}
private IFile getManifest(IProject project) {
IFile manifest = (IFile) project.findMember(MANIFEST_PATH);
return manifest;
}
private static final class XMIFileFinder implements IResourceVisitor, IResourceDeltaVisitor {
private final IPath outputLocation;
private final List<IFile> xmiIFiles = new ArrayList<IFile>();
private final List<IFile> relevantFiles = new ArrayList<IFile>();
/**
* @return the xmiIFiles
*/
public List<IFile> getPartitionFiles() {
return xmiIFiles;
}
public XMIFileFinder(final IPath outputLocation) {
this.outputLocation = outputLocation;
}
public boolean visit(final IResource resource) {
if (this.outputLocation != null && this.outputLocation.isPrefixOf(resource.getFullPath())) {
return true;
}
if (resource.exists() && resource instanceof IFile) {
if (MetaModelCatalog.META_MODEL_EXTENSION.equals("." + resource.getFileExtension())) {
this.xmiIFiles.add((IFile) resource);
this.relevantFiles.add((IFile) resource);
}
IPath filePath = resource.getProjectRelativePath();
if (filePath.equals(new Path(MANIFEST_PATH))) {
this.relevantFiles.add((IFile) resource);
return false;
}
if (filePath.equals(new Path("plugin.xml"))) {
this.relevantFiles.add((IFile) resource);
return false;
}
}
// return true to continue visiting children.
return true;
}
public boolean visit(final IResourceDelta delta) throws CoreException {
if (delta.getKind() == IResourceDelta.ADDED || delta.getKind() == IResourceDelta.CHANGED) {
final IResource resource = delta.getResource();
if (this.outputLocation != null && this.outputLocation.isPrefixOf(resource.getFullPath())) {
return true;
}
if (resource.exists() && resource instanceof IFile) {
if (MetaModelCatalog.META_MODEL_EXTENSION.equals("." + resource.getFileExtension())) {
this.xmiIFiles.add((IFile) resource);
this.relevantFiles.add((IFile) resource);
// for now we can return here since a full build will be
// triggered anyway
// TODO remove when increment build is available
return false;
}
IPath filePath = resource.getProjectRelativePath();
if (filePath.equals(new Path(MANIFEST_PATH))) {
this.relevantFiles.add((IFile) resource);
return false;
}
if (filePath.equals(new Path("plugin.xml"))) {
this.relevantFiles.add((IFile) resource);
return false;
}
}
}
// return true to continue visiting children.
return true;
}
public boolean hasRelevantChanges() {
return relevantFiles.size() > 0;
}
}
}