blob: a5b1341242f501797776fa8f89ec7d8c8afa09ca [file] [log] [blame]
/**
*
* Copyright (c) 2011, 2016 - Loetz GmbH&Co.KG (69115 Heidelberg, Germany)
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christophe Loetz (Loetz GmbH&Co.KG) - initial implementation
*/
package org.eclipse.osbp.ide.core.ui.softwarefactory.builder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
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.IStorage;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.ProgressMonitorWrapper;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.osbp.dsl.semantic.common.types.LType;
import org.eclipse.osbp.dsl.semantic.common.types.LTypedPackage;
import org.eclipse.osbp.dsl.semantic.entity.LBean;
import org.eclipse.osbp.dsl.semantic.entity.LEntity;
import org.eclipse.osbp.dsl.semantic.entity.LEntityAttribute;
import org.eclipse.osbp.dsl.semantic.entity.LEntityModel;
import org.eclipse.osbp.ide.core.ui.softwarefactory.constants.Constants;
import org.eclipse.osbp.ide.core.ui.softwarefactory.extender.IModelExtenderProvider;
import org.eclipse.osbp.ide.core.ui.softwarefactory.extender.IModelExtenderProvider.Event.Listener;
import org.eclipse.osbp.ide.core.ui.softwarefactory.extender.ModelExtendedEvent;
import org.eclipse.osbp.ide.core.ui.softwarefactory.extender.ModelExtenderUtils;
import org.eclipse.osbp.ide.core.ui.softwarefactory.extender.ModelInstanceDescription;
import org.eclipse.osbp.xtext.action.ActionModel;
import org.eclipse.osbp.xtext.action.ActionPackage;
import org.eclipse.osbp.xtext.action.ActionToolbar;
import org.eclipse.osbp.xtext.addons.EObjectHelper;
import org.eclipse.osbp.xtext.authorizationdsl.AuthorizationModel;
import org.eclipse.osbp.xtext.authorizationdsl.Role;
import org.eclipse.osbp.xtext.datainterchange.DataInterchange;
import org.eclipse.osbp.xtext.datainterchange.DataInterchangeGroup;
import org.eclipse.osbp.xtext.datainterchange.DataInterchangeModel;
import org.eclipse.osbp.xtext.datainterchange.DataInterchangePackage;
import org.eclipse.osbp.xtext.datamartdsl.AxisEnum;
import org.eclipse.osbp.xtext.datamartdsl.DatamartAttribute;
import org.eclipse.osbp.xtext.datamartdsl.DatamartAxis;
import org.eclipse.osbp.xtext.datamartdsl.DatamartDSLFactory;
import org.eclipse.osbp.xtext.datamartdsl.DatamartDSLPackage;
import org.eclipse.osbp.xtext.datamartdsl.DatamartDefinition;
import org.eclipse.osbp.xtext.datamartdsl.DatamartEntity;
import org.eclipse.osbp.xtext.datamartdsl.DatamartModel;
import org.eclipse.osbp.xtext.datamartdsl.DatamartPackage;
import org.eclipse.xtext.resource.IReferenceDescription;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.eclipse.xtext.ui.editor.findrefs.IReferenceFinder;
import org.eclipse.xtext.ui.resource.IStorage2UriMapper;
import org.eclipse.xtext.ui.resource.XtextResourceSetProvider;
import org.eclipse.xtext.util.IAcceptor;
import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
class DatamartMetadata {
IProject project;
Resource resource;
DatamartModel datamartModel;
List<DatamartEntity> trackingEntities;
}
public class OSBP2Builder extends IncrementalProjectBuilder {
public static final String BUILDER_ID = Constants.BUILDER_ID;
private static final Logger LOGGER = LoggerFactory.getLogger(OSBP2Builder.class);
public static final String MODELS_BASE_SRC_DIRECTORY = "models";
public static final String SOURCE_BASE_SRC_DIRECTORY = "src";
public static final String ENTITY_MODEL_EXTENSION = "entity";
public static final String AUTHORIZATION_MODEL_EXTENSION = "authorization";
public static final String DATAMART_MODEL_EXTENSION = "datamart";
public static final String DATAINTERCHANGE_MODEL_EXTENSION = "data";
public static final String DTO_MODEL_EXTENSION = "dto";
public static final String SERVICE_MODEL_EXTENSION = "service";
public static final String ACTION_MODEL_EXTENSION = "action";
public static final String TABLE_MODEL_EXTENSION = "table";
public static final String DIALOG_MODEL_EXTENSION = "dialog";
public static final String UIMODEL_MODEL_EXTENSION = "ui";
public static final String PERSPECTIVE_MODEL_EXTENSION = "perspective";
public static final String MENU_MODEL_EXTENSION = "menu";
Set<String> doNotTouchExtensions = new HashSet<>();
{
doNotTouchExtensions.add(ENTITY_MODEL_EXTENSION);
doNotTouchExtensions.add(DTO_MODEL_EXTENSION);
doNotTouchExtensions.add(SERVICE_MODEL_EXTENSION);
}
@Inject
private XtextResourceSetProvider rsProvider;
@Inject
private IReferenceFinder referenceFinder;
@Inject
private IStorage2UriMapper uriStorageMapper;
@Inject
private IModelExtenderProvider.Event.Source modelExtenderBuilder;
private boolean firstBuild = true;
@Override
protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException {
LOGGER.info("build started for {}", getProject());
long startTime = System.currentTimeMillis();
SubMonitor progress = null;
boolean isModelExtending = false;
List<String> extensionsRebuilt = new CopyOnWriteArrayList<>();
for (String fileExtension : modelExtenderBuilder.getPending()) {
for (Listener listener : modelExtenderBuilder.getListenerList()) {
Map<String, ModelInstanceDescription> modelInstanceDecriptionMap = modelExtenderBuilder
.getModelInstanceDescriptionMap(listener);
isModelExtending = true;
for (Entry<String, ModelInstanceDescription> extension : modelInstanceDecriptionMap.entrySet()) {
if (extension.getKey().equals(fileExtension)
&& extension.getValue().getProject().equals(getProject())
&& extension.getValue().isDone()
&& !extension.getValue().isBuilt()) {
LOGGER.debug("rebuild for {}", extension.getKey());
extension.getValue().setBuilt(true);
needRebuild();
extensionsRebuilt.add(extension.getKey());
modelExtenderBuilder.getPending().remove(fileExtension);
}
}
}
}
if (!isModelExtending) { // this disturbs the model extender workflow
try {
final String taskName = "Building" + getProject().getName() + ": "; //$NON-NLS-2$
progress = SubMonitor.convert(new ProgressMonitorWrapper(monitor) { // NOSONAR
// -
// lambda
@Override
public void subTask(String name) { // NOSONAR
super.subTask(taskName + name);
}
}, 8);
if (kind == FULL_BUILD || firstBuild) {
firstBuild = false;
fullBuild(progress.newChild(1));
} else {
IResourceDelta delta = getDelta(getProject());
if (delta == null || isOpened(delta)) {
fullBuild(progress.newChild(1));
} else {
incrementalBuild(delta, progress.newChild(1));
}
}
progress.worked(8);
} catch (CoreException e) {
LOGGER.error(e.getMessage(), e);
throw e;
} catch (OperationCanceledException e) {
forgetLastBuiltState();
throw e;
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
if (progress != null) {
progress.done();
}
LOGGER.info("Build {} in {} ms", getProject().getName(), System.currentTimeMillis() - startTime);
}
}
if (!extensionsRebuilt.isEmpty()) {
for (String extension : extensionsRebuilt) {
LOGGER.debug("extending finished for {}", extension);
modelExtenderBuilder.notifyListeners(new ModelExtendedEvent(extension));
}
}
return getProject().getReferencedProjects();
}
private boolean checkFolder(IFolder folder) {
return (MODELS_BASE_SRC_DIRECTORY.equals(folder.getName()) || SOURCE_BASE_SRC_DIRECTORY.equals(folder.getName()));
}
private boolean checkFile(IFile file) {
if (file.getFileExtension() != null) {
try {
if (file.getFileExtension().equals(ENTITY_MODEL_EXTENSION)) {
modifyModel(file);
touchReferenced(file);
}
if (file.getFileExtension().equals(AUTHORIZATION_MODEL_EXTENSION)) {
touchReferenced(file);
}
if (file.getFileExtension().equals(DATAMART_MODEL_EXTENSION)) {
touchReferenced(file);
}
if (file.getFileExtension().equals(ACTION_MODEL_EXTENSION)) {
touchReferenced(file);
}
if (file.getFileExtension().equals(DATAINTERCHANGE_MODEL_EXTENSION)) {
touchReferenced(file);
}
} catch (IOException e) { // NOSONAR - is logged
LOGGER.error(e.getLocalizedMessage());
}
}
return false;
}
/**
* @param monitor
* the progress monitor to use for reporting progress to the
* user. It is the caller's responsibility to call done() on the
* given monitor. Accepts null, indicating that no progress
* should be reported and that the operation cannot be cancelled.
*/
protected void incrementalBuild(IResourceDelta delta, final IProgressMonitor monitor) throws CoreException {
final SubMonitor progress = SubMonitor.convert(monitor, "Updating references", 4);
progress.subTask("Updating references");
if (progress.isCanceled())
throw new OperationCanceledException();
progress.worked(2);
IProject project = getProject();
if (!project.getDescription().hasNature(Constants.NATURE_ID)) {
return;
}
delta.accept(new IResourceDeltaVisitor() { // NOSONAR - anonymous to
// lambda
@Override
public boolean visit(IResourceDelta delta) throws CoreException {
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
if (delta.getResource() instanceof IProject) {
return delta.getResource() == getProject();
} else if (delta.getResource() instanceof IFolder) {
return checkFolder((IFolder) delta.getResource());
} else if (delta.getResource() instanceof IFile) {
return checkFile((IFile) delta.getResource());
}
return false;
}
});
if (progress.isCanceled())
throw new OperationCanceledException();
progress.worked(4);
}
/**
* @param monitor
* the progress monitor to use for reporting progress to the
* user. It is the caller's responsibility to call done() on the
* given monitor. Accepts null, indicating that no progress
* should be reported and that the operation cannot be cancelled.
*/
protected void fullBuild(final IProgressMonitor monitor) throws CoreException {
IProject project = getProject();
if (!project.getDescription().hasNature(Constants.NATURE_ID)) {
return;
}
monitor.worked(2);
project.accept(new IResourceVisitor() {
@Override
// NOSONAR - anonymous to lambda
public boolean visit(IResource resource) throws CoreException {
if (resource == getProject()) {
return true;
} else if (resource instanceof IFolder) {
return checkFolder((IFolder) resource);
} else if (resource instanceof IFile) {
return checkFile((IFile) resource);
}
return false;
}
});
monitor.worked(6);
}
/**
* touches all model resources referencing this root model resource file
*
* @param modifiedFile
*/
protected void touchReferenced(IFile modifiedFile) throws IOException {
if (modifiedFile == null) {
return;
}
EObject model = loadSemanticModel(modifiedFile);
if (model == null) {
LOGGER.error("Skipping build since SemanticModel could not be loaded from {}", modifiedFile.getName());
return;
}
if (model instanceof LEntityModel) {
LEntityModel entityModel = (LEntityModel) model;
Set<URI> lTypes = new HashSet<>();
for (LTypedPackage entityPkg : entityModel.getPackages()) {
for (LType lType : entityPkg.getTypes()) {
lTypes.add(EcoreUtil.getURI(lType));
}
}
touchReferencingModel(modifiedFile.getName(), lTypes, model.eResource().getResourceSet());
}
if (model instanceof AuthorizationModel) {
AuthorizationModel authorizationModel = (AuthorizationModel) model;
Set<URI> roles = new HashSet<>();
for (Role role : authorizationModel.getPckg().getRoles()) {
roles.add(EcoreUtil.getURI(role));
}
touchReferencingModel(modifiedFile.getName(), roles, model.eResource().getResourceSet());
}
if (model instanceof DatamartModel) {
DatamartModel datamartModel = (DatamartModel) model;
Set<URI> datamarts = new HashSet<>();
for (DatamartPackage datamartPkg : datamartModel.getPackages()) {
for (DatamartDefinition datamart : datamartPkg.getDefinitions()) {
datamarts.add(EcoreUtil.getURI(datamart));
}
}
touchReferencingModel(modifiedFile.getName(), datamarts, model.eResource().getResourceSet());
}
if (model instanceof ActionModel) {
ActionModel actionModel = (ActionModel) model;
Set<URI> actionToolbars = new HashSet<>();
for (ActionPackage actionPkg : actionModel.getPackages()) {
for (ActionToolbar actionToolbar : actionPkg.getToolbars()) {
actionToolbars.add(EcoreUtil.getURI(actionToolbar)); // NOSONAR
}
}
touchReferencingModel(modifiedFile.getName(), actionToolbars, model.eResource().getResourceSet());
}
if (model instanceof DataInterchangeModel) {
DataInterchangeModel dataInterchangeModel = (DataInterchangeModel) model;
Set<URI> dataInterchanges = new HashSet<>();
for (DataInterchangePackage actionPkg : dataInterchangeModel.getPackages()) {
for (DataInterchangeGroup group : actionPkg.getGroups()) {
for (DataInterchange dataInterchange : group.getDatInts()) {
dataInterchanges.add(EcoreUtil.getURI(dataInterchange)); // NOSONAR
}
}
}
touchReferencingModel(modifiedFile.getName(), dataInterchanges, model.eResource().getResourceSet());
}
}
protected boolean shouldTouch(IReferenceDescription t) {
URI sourceResourceURI = t.getSourceEObjectUri().trimFragment();
URI targetResourceURI = t.getTargetEObjectUri().trimFragment();
if (sourceResourceURI.equals(targetResourceURI)) {
// reference points to an EObject in same resource
return false;
}
// if defined in set to not touch
return !doNotTouchExtensions.contains(sourceResourceURI.fileExtension());
}
protected void touchReferencingModel(String triggerModel, Set<URI> items, ResourceSet sourceResourceSet) {
final List<IReferenceDescription> referenceTargets = new ArrayList<>();
referenceFinder.findAllReferences(items, new LocalResourceAccess(sourceResourceSet),
new IAcceptor<IReferenceDescription>() {
@Override
public void accept(IReferenceDescription t) {
if (shouldTouch(t) && !referenceTargets.contains(t)) {
referenceTargets.add(t);
}
}
}, null);
List<Resource> affectedResources = new LinkedList<>();
Map<IProject, XtextResourceSet> targetResourceSets = new HashMap<>();
for (IReferenceDescription desc : referenceTargets) {
IProject project = null;
Iterable<Pair<IStorage, IProject>> pairs = uriStorageMapper.getStorages(desc.getSourceEObjectUri());
if (pairs.iterator().hasNext()) {
project = pairs.iterator().next().getSecond();
}
if (project == null) {
LOGGER.error("No project could be found for {}", desc.getSourceEObjectUri());
continue;
}
XtextResourceSet resourceSet;
if (targetResourceSets.containsKey(project)) {
resourceSet = targetResourceSets.get(project);
} else {
resourceSet = (XtextResourceSet) rsProvider.get(getProject());
targetResourceSets.put(project, resourceSet);
}
// load the resource by the resource set
Resource modelResource = resourceSet.getResource(desc.getSourceEObjectUri().trimFragment(), true);
if (!affectedResources.contains(modelResource)) {
affectedResources.add(modelResource);
}
}
for (Resource resource : affectedResources) {
ModelExtenderUtils.writeResource(resource, triggerModel);
}
}
/**
* Modifies the datamart model for which the changed entity model has a
* tracking reference match.
*
* @param modifiedFile
*/
protected void modifyModel(IFile modifiedFile) {
if (modifiedFile == null) {
return;
}
LEntityModel lEntityModel = loadSemanticModel(modifiedFile);
if (lEntityModel == null) {
LOGGER.error("Skipping datamart build since SemanticModel can not be loaded from file {}", modifiedFile.getName());
return;
}
Set<URI> entities = new HashSet<>();
for (LTypedPackage lPkg : lEntityModel.getPackages()) {
for (LType lType : lPkg.getTypes()) {
if (lType instanceof LEntity) {
entities.add(EcoreUtil.getURI(lType));
}
}
}
final List<IReferenceDescription> referenceTargets = new ArrayList<>();
referenceFinder.findAllReferences(entities, new LocalResourceAccess(lEntityModel.eResource().getResourceSet()),
new IAcceptor<IReferenceDescription>() {
@Override
public void accept(IReferenceDescription t) {
if (t.getEReference() == DatamartDSLPackage.Literals.DATAMART_ENTITY__ENTITY_REF) {
referenceTargets.add(t);
}
}
}, null);
List<Resource> affectedResources = new LinkedList<>();
Map<IProject, XtextResourceSet> datamartResourceSets = new HashMap<>();
for (IReferenceDescription desc : referenceTargets) {
IProject datamartProject = null;
Iterable<Pair<IStorage, IProject>> pairs = uriStorageMapper.getStorages(desc.getSourceEObjectUri());
if (pairs.iterator().hasNext()) {
datamartProject = pairs.iterator().next().getSecond();
}
if (datamartProject == null) {
LOGGER.error("No project could be found for {}", desc.getSourceEObjectUri());
continue;
}
XtextResourceSet resourceSet;
if (datamartResourceSets.containsKey(datamartProject)) {
resourceSet = datamartResourceSets.get(datamartProject);
} else {
resourceSet = (XtextResourceSet) rsProvider.get(getProject());
datamartResourceSets.put(datamartProject, resourceSet);
}
// load the resource by the resource set
Resource datamartModelResource = resourceSet.getResource(desc.getSourceEObjectUri().trimFragment(), true);
DatamartEntity dmEntity = (DatamartEntity) EObjectHelper.getSemanticElement(datamartModelResource,
desc.getSourceEObjectUri().fragment());
if (!dmEntity.isTracking()) {
continue;
}
if (!affectedResources.contains(datamartModelResource)) {
affectedResources.add(datamartModelResource);
}
// clear properties section
DatamartDSLFactory dmFactory = DatamartDSLFactory.eINSTANCE;
EList<DatamartAttribute> properties = dmEntity.getAttributes();
if (properties != null) {
properties.clear();
// rebuild properties section
for (LEntityAttribute eAttr : dmEntity.getEntityRef().getAllAttributes()) {
if (!eAttr.isId() && !eAttr.isUuid() && !eAttr.isVersion() && !(eAttr.getType() instanceof LBean)) {
DatamartAxis axis = dmFactory.createDatamartAxis();
axis.setName(AxisEnum.COLUMNS);
DatamartAttribute attribute = dmFactory.createDatamartAttribute();
attribute.setAggregated(false);
attribute.setScaled(false);
attribute.setAxis(axis);
attribute.setAttributeRef(eAttr);
properties.add(attribute);
}
}
}
}
for (Resource resource : affectedResources) {
ModelExtenderUtils.writeResource(resource, DATAMART_MODEL_EXTENSION);
}
}
/**
* Returns the entityModel which is to be build, identified by file.
*
* @param file
* @return
*/
protected <A extends EObject> A loadSemanticModel(IFile file) {
LOGGER.info("loading {}", file.getName());
org.eclipse.emf.common.util.URI entityDSLURI = org.eclipse.emf.common.util.URI
.createPlatformResourceURI(file.getFullPath().toString(), false);
XtextResourceSet rs = (XtextResourceSet) rsProvider.get(getProject());
Resource entityResource = rs.getResource(entityDSLURI, true);
try {
entityResource.load(null);
} catch (IOException e) { // NOSONAR - is logged
LOGGER.error(e.getLocalizedMessage());
return null;
}
@SuppressWarnings("unchecked")
A entityModel = (A) EObjectHelper.getSemanticElement(entityResource);
LOGGER.info("finished loading {}", file.getName());
return entityModel;
}
/**
* Returns true, if delta is from our projectentity is contained in
* entityModel.
*
* @param entity
* @param entityModel
* @return
*/
protected boolean isOpened(IResourceDelta delta) {
return delta.getResource() instanceof IProject && (delta.getFlags() & IResourceDelta.OPEN) != 0
&& ((IProject) delta.getResource()).isOpen();
}
@Override
protected void clean(IProgressMonitor monitor) throws CoreException {
if (monitor != null) {
monitor.done();
}
}
public static class LocalResourceAccess implements IReferenceFinder.ILocalResourceAccess {
private ResourceSet resourceSet;
public LocalResourceAccess(ResourceSet resourceSet) {
this.resourceSet = resourceSet;
}
public <R> R readOnly(URI targetURI, IUnitOfWork<R, ResourceSet> work) {
try {
return work.exec(resourceSet);
} catch (OperationCanceledException e) {
throw e;
} catch (Exception exc) {
throw new WrappedException(exc);
}
}
}
}