Commit b635d796 authored by Romain Bioteau's avatar Romain Bioteau Committed by GitHub

feat(bdm) split deploy and jar generation (#996)

* deploy is now asynchronous
* generation reuse engine module
* Workspace API only use the generation part

Closes BS-18030
parent 994b0545
......@@ -5,5 +5,6 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="src-test/java"/>
<classpathentry kind="src" path="src-test/resources"/>
<classpathentry kind="lib" path="lib/bonita-business-data-generator.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
......@@ -3,7 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Name: Business Object Plugin
Bundle-SymbolicName: org.bonitasoft.studio.businessobject;singleton:=true
Bundle-Version: 7.7.0.qualifier
Bundle-ClassPath: .
Bundle-ClassPath: .,
lib/bonita-business-data-generator.jar
Bundle-Activator: org.bonitasoft.studio.businessobject.BusinessObjectPlugin
Bundle-Vendor: BonitaSoft S.A.
Bundle-Localization: plugin
......@@ -63,7 +64,8 @@ Require-Bundle: org.bonitasoft.studio.engine;bundle-version="1.0.0",
org.eclipse.core.runtime,
org.eclipse.e4.core.commands;bundle-version="0.12.100",
org.eclipse.e4.core.contexts;bundle-version="1.6.0",
org.eclipse.e4.ui.workbench;bundle-version="1.5.0"
org.eclipse.e4.ui.workbench;bundle-version="1.5.0",
org.apache.commons.io;bundle-version="2.2.0"
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Import-Package: org.bonitasoft.engine.api,
......@@ -73,6 +75,7 @@ Import-Package: org.bonitasoft.engine.api,
org.bonitasoft.engine.bdm.model.field,
org.bonitasoft.engine.bdm.validator,
org.bonitasoft.engine.business.data,
org.bonitasoft.engine.command,
org.bonitasoft.engine.exception,
org.bonitasoft.engine.platform,
org.bonitasoft.engine.session
......@@ -7,5 +7,6 @@ bin.includes = META-INF/,\
icons/,\
plugin*.properties,\
messages*.properties,\
fragment.e4xmi
fragment.e4xmi,\
lib/
source.. = src/
......@@ -165,4 +165,6 @@ dateDetails=DATE stores a date and a time in the database using a LONG (number o
dateOnlyDetails=%s is used to hold a date with no time of the day. Use it for birth dates for example.\nIt is stored in the database as a String, using ISO-8601 "yyyy-mm-dd". It uses a java.time.LocalDate as Java type.
dateTimeDetails=%s is used to hold a date-time that displays the same whatever the user's time zone (NO Time Zone). Use it for stores opening hours for example.\nIt is stored in the database as a String using ISO 8601 "yyyyy-mm-ddThh:mm:ss:sss". It uses java.time.LocalDateTime as Java type.
dateTimeInTimezoneDetails=%s is used to hold a date-time that will take the user's computer time zone (TZ) into account when displayed. Use it for meetings date and time for example.\nIt is stored in the database as a String using ISO 8601 "yyyy-mm-ddThh:mm:ss.sssZ". It uses java.time.OffsetDateTime as Java type.
fieldNameShouldStartsWithLowercase=Attribute name should starts with a lower case, else it might leads to some errors.
\ No newline at end of file
fieldNameShouldStartsWithLowercase=Attribute name should starts with a lower case, else it might leads to some errors.
generatingJarFromBDMModel=Generating jar from Business Data Model...
deployBDMJobName=Deploy Business Data Model
\ No newline at end of file
......@@ -10,4 +10,53 @@
<groupId>org.bonitasoft.studio.bundles.plugins</groupId>
<artifactId>org.bonitasoft.studio.businessobject</artifactId>
<packaging>eclipse-plugin</packaging>
<properties>
<lib.dir>lib</lib.dir>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>Retrieve bonita-business-data-generator dependencies</id>
<phase>process-sources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.bonitasoft.engine.data</groupId>
<artifactId>bonita-business-data-generator</artifactId>
<version>${engine.version}</version>
<classifier>studio</classifier>
</artifactItem>
</artifactItems>
<stripVersion>true</stripVersion>
<stripClassifier>true</stripClassifier>
<outputDirectory>${lib.dir}</outputDirectory>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<configuration>
<filesets>
<fileset>
<directory>${lib.dir}</directory>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>
</build>
</project>
......@@ -16,8 +16,6 @@ package org.bonitasoft.studio.businessobject.core.operation;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.notNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
......@@ -40,7 +38,6 @@ import org.bonitasoft.studio.businessobject.core.repository.BusinessObjectModelF
import org.bonitasoft.studio.common.repository.Repository;
import org.bonitasoft.studio.engine.BOSEngineManager;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
......@@ -70,9 +67,6 @@ public class DeployBDMOperationTest {
private BusinessObjectModel bom;
@Mock
private IEventBroker eventBroker;
/**
* @throws java.lang.Exception
*/
......@@ -87,16 +81,12 @@ public class DeployBDMOperationTest {
bo.getFields().add(firstName);
bom.getBusinessObjects().add(bo);
operationUnderTest = spy(new DeployBDMOperation(bdmFileStore));
doReturn(eventBroker).when(operationUnderTest).eventBroker();
doReturn(bom).when(bdmFileStore).getContent();
final Map<String, byte[]> result = new HashMap<>();
result.put("bdm-client", new byte[512]);
doReturn(result).when(operationUnderTest).retrieveContent(any(byte[].class));
doReturn(false).when(operationUnderTest).dropDBOnInstall();
when(manager.getTenantAdministrationAPI((APISession) anyObject())).thenReturn(tenantAdminAPI);
doReturn(manager).when(operationUnderTest).getEngineManager();
doNothing().when(operationUnderTest).updateDependency(any(byte[].class));
doNothing().when(operationUnderTest).removeDependency();
doNothing().when(operationUnderTest).uninstallBDMAccessControl(any(IProgressMonitor.class));
parentFolder = new File("test");
parentFolder.mkdirs();
......@@ -128,7 +118,6 @@ public class DeployBDMOperationTest {
inOrder.verify(tenantAdminAPI).uninstallBusinessDataModel();
inOrder.verify(tenantAdminAPI).installBusinessDataModel(any(byte[].class));
inOrder.verify(tenantAdminAPI).resume();
verify(tenantAdminAPI).getClientBDMZip();
}
@Test
......@@ -138,13 +127,11 @@ public class DeployBDMOperationTest {
operationUnderTest.run(Repository.NULL_PROGRESS_MONITOR);
verify(manager).loginDefaultTenant(Repository.NULL_PROGRESS_MONITOR);
verify(bdmFileStore).getContent();
final InOrder inOrder = inOrder(tenantAdminAPI, eventBroker);
final InOrder inOrder = inOrder(tenantAdminAPI);
inOrder.verify(tenantAdminAPI).pause();
inOrder.verify(tenantAdminAPI).cleanAndUninstallBusinessDataModel();
inOrder.verify(tenantAdminAPI).installBusinessDataModel(any(byte[].class));
inOrder.verify(tenantAdminAPI).resume();
inOrder.verify(eventBroker).send(eq("bdm/deployed"), notNull(Map.class));
verify(tenantAdminAPI).getClientBDMZip();
}
@Test
......@@ -159,7 +146,6 @@ public class DeployBDMOperationTest {
inOrder.verify(tenantAdminAPI).pause();
inOrder.verify(tenantAdminAPI).uninstallBusinessDataModel();
inOrder.verify(tenantAdminAPI).resume();
verify(operationUnderTest).removeDependency();
verify(tenantAdminAPI, never()).installBusinessDataModel(any(byte[].class));
}
......
......@@ -85,9 +85,11 @@ public class BusinessObjectModelRepositoryStoreTest {
doReturn(businessObjectFileStore).when(storeUnderTest).superDoImportInputStream("test", inputStream);
doReturn(true).when(storeUnderTest).isDeployable();
doNothing().when(storeUnderTest).deploy(businessObjectFileStore);
doNothing().when(storeUnderTest).generateJar(businessObjectFileStore);
storeUnderTest.doImportInputStream("test", inputStream);
verify(storeUnderTest).generateJar(businessObjectFileStore);
verify(storeUnderTest).deploy(businessObjectFileStore);
}
......@@ -96,9 +98,11 @@ public class BusinessObjectModelRepositoryStoreTest {
doReturn(businessObjectFileStore).when(storeUnderTest).superDoImportInputStream("test", inputStream);
doReturn(false).when(storeUnderTest).isDeployable();
doNothing().when(storeUnderTest).deploy(businessObjectFileStore);
doNothing().when(storeUnderTest).generateJar(businessObjectFileStore);
storeUnderTest.doImportInputStream("test", inputStream);
verify(storeUnderTest).generateJar(businessObjectFileStore);
verify(storeUnderTest, never()).deploy(businessObjectFileStore);
}
}
/**
* Copyright (C) 2018 Bonitasoft S.A.
* Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2.0 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.bonitasoft.studio.businessobject.core.operation;
import java.lang.reflect.InvocationTargetException;
import org.bonitasoft.studio.businessobject.BusinessObjectPlugin;
import org.bonitasoft.studio.businessobject.core.repository.BusinessObjectModelFileStore;
import org.bonitasoft.studio.businessobject.i18n.Messages;
import org.bonitasoft.studio.common.repository.Repository;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
public class DeployBDMJob extends Job {
private BusinessObjectModelFileStore fileStore;
public DeployBDMJob(BusinessObjectModelFileStore fileStore) {
super(Messages.deployBDMJobName);
this.fileStore = fileStore;
}
/*
* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
new DeployBDMOperation(fileStore).run(Repository.NULL_PROGRESS_MONITOR);
} catch (final InvocationTargetException e) {
return new Status(IStatus.ERROR, BusinessObjectPlugin.PLUGIN_ID,
"Failed to deploy BDM. Check Studio logs for more information.",
new DeployBDMStackTraceResolver().reduceHibernateException(e));
} catch (InterruptedException e) {
return new Status(IStatus.ERROR, BusinessObjectPlugin.PLUGIN_ID, "Failed to deploy BDM", e);
}
return Status.OK_STATUS;
}
/*
* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
*/
@Override
public boolean belongsTo(Object family) {
return DeployBDMJob.class.equals(family);
}
}
......@@ -38,6 +38,7 @@ public class DeployBDMOnStartup implements IEngineAction {
.getRepositoryStore(BusinessObjectModelRepositoryStore.class);
final BusinessObjectModelFileStore fileStore = store.getChild(BusinessObjectModelFileStore.BOM_FILENAME);
if (fileStore != null) {
new GenerateBDMOperation(fileStore);
new DeployBDMOperation(fileStore)
.reuseSession(session)
.run(Repository.NULL_PROGRESS_MONITOR);
......
......@@ -14,18 +14,11 @@
*/
package org.bonitasoft.studio.businessobject.core.operation;
import static com.google.common.io.ByteStreams.toByteArray;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.bonitasoft.engine.api.TenantAdministrationAPI;
import org.bonitasoft.engine.bdm.model.BusinessObjectModel;
......@@ -45,7 +38,6 @@ import org.bonitasoft.studio.engine.BOSEngineManager;
import org.bonitasoft.studio.engine.EnginePlugin;
import org.bonitasoft.studio.engine.preferences.EnginePreferenceConstants;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.e4.core.commands.ECommandService;
import org.eclipse.e4.core.commands.EHandlerService;
......@@ -59,10 +51,6 @@ import org.eclipse.ui.internal.Workbench;
public class DeployBDMOperation implements IRunnableWithProgress {
private static final String UNINSTALL_BDM_AC_CMD = "org.bonitasoft.studio.bdm.access.control.command.uninstall.headless";
private static final String BDM_DEPLOYED_TOPIC = "bdm/deployed";
private static final String BDM_CLIENT = "bdm-client";
private static final String BDM_DAO = "bdm-dao";
private static final String MODEL = "model";
private final BusinessObjectModelFileStore fileStore;
private APISession session;
......@@ -163,22 +151,8 @@ public class DeployBDMOperation implements IRunnableWithProgress {
if (containsBusinessObjects(bom)) {
tenantManagementAPI.installBusinessDataModel(fileStore.toByteArray());
}
tenantManagementAPI.resume();
if (containsBusinessObjects(bom)) {
final byte[] zipContent = tenantManagementAPI.getClientBDMZip();
final Map<String, byte[]> jarContent = retrieveContent(zipContent);
updateDependency(jarContent.get(BDM_CLIENT));
final Map<String, Object> data = new HashMap<>();
data.put(MODEL, bom);
data.put(BDM_DAO, jarContent.get(BDM_DAO));
eventBroker().send(BDM_DEPLOYED_TOPIC, data);
} else {
removeDependency();
}
} catch (final Exception e) {
BonitaStudioLog.error(e);
try {
tenantManagementAPI.uninstallBusinessDataModel();
} catch (final BusinessDataRepositoryDeploymentException e1) {
......@@ -224,40 +198,6 @@ public class DeployBDMOperation implements IRunnableWithProgress {
return preferenceStore.getBoolean(EnginePreferenceConstants.DROP_BUSINESS_DATA_DB_ON_INSTALL);
}
protected Map<String, byte[]> retrieveContent(final byte[] zipContent) throws IOException {
Assert.isNotNull(zipContent);
ByteArrayInputStream is = null;
ZipInputStream zis = null;
final ByteArrayOutputStream out = null;
final Map<String, byte[]> result = new HashMap<>();
try {
is = new ByteArrayInputStream(zipContent);
zis = new ZipInputStream(is);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
final String entryName = entry.getName();
if (entryName.contains(MODEL) && entryName.endsWith(".jar")) {
result.put(BDM_CLIENT, toByteArray(zis));
}
if (entryName.contains("dao") && entryName.endsWith(".jar")) {
result.put(BDM_DAO, toByteArray(zis));
}
}
} finally {
if (is != null) {
is.close();
}
if (zis != null) {
zis.close();
}
if (out != null) {
out.close();
}
}
return result;
}
protected void updateDependency(final byte[] jarContent) throws InvocationTargetException {
ByteArrayInputStream is = null;
try {
......
/**
* Copyright (C) 2018 Bonitasoft S.A.
* Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2.0 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.bonitasoft.studio.businessobject.core.operation;
import static com.google.common.base.Strings.isNullOrEmpty;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
public class DeployBDMStackTraceResolver {
public Throwable reduceHibernateException(final InvocationTargetException e) {
final Throwable targetException = e.getTargetException();
int index = -1;
for (int i = 0; i < targetException.getStackTrace().length; i++) {
final StackTraceElement element = targetException.getStackTrace()[i];
final String className = element.getClassName();
if (!isNullOrEmpty(className) && className.contains("org.hibernate.HibernateException")) {
index = i;
break;
}
}
if (index > -1) {
targetException.setStackTrace(
Arrays.copyOfRange(targetException.getStackTrace(), index, targetException.getStackTrace().length));
}
return targetException;
}
}
/**
* Copyright (C) 2013-2015 Bonitasoft S.A.
* Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2.0 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.bonitasoft.studio.businessobject.core.operation;
import static com.google.common.io.ByteStreams.toByteArray;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.bonitasoft.engine.bdm.model.BusinessObjectModel;
import org.bonitasoft.engine.business.data.generator.AbstractBDMJarBuilder;
import org.bonitasoft.engine.business.data.generator.client.ClientBDMJarBuilder;
import org.bonitasoft.engine.business.data.generator.client.ResourcesLoader;
import org.bonitasoft.engine.business.data.generator.compiler.JDTCompiler;
import org.bonitasoft.engine.business.data.generator.filter.OnlyDAOImplementationFileFilter;
import org.bonitasoft.engine.business.data.generator.filter.WithoutDAOImplementationFileFilter;
import org.bonitasoft.studio.businessobject.BusinessObjectPlugin;
import org.bonitasoft.studio.businessobject.core.repository.BusinessObjectModelFileStore;
import org.bonitasoft.studio.businessobject.i18n.Messages;
import org.bonitasoft.studio.common.log.BonitaStudioLog;
import org.bonitasoft.studio.common.repository.Repository;
import org.bonitasoft.studio.dependencies.repository.DependencyFileStore;
import org.bonitasoft.studio.dependencies.repository.DependencyRepositoryStore;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.ui.PlatformUI;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.wiring.BundleWiring;
public class GenerateBDMOperation implements IRunnableWithProgress {
private static final String BDM_DEPLOYED_TOPIC = "bdm/deployed";
private static final String BDM_CLIENT = "bdm-client";
private static final String BDM_DAO = "bdm-dao";
private static final String MODEL = "model";
private final BusinessObjectModelFileStore fileStore;
private static Object generationLock = new Object();
public GenerateBDMOperation(final BusinessObjectModelFileStore fileStore) {
this.fileStore = fileStore;
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.operation.IRunnableWithProgress#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
synchronized (generationLock) {
doGenerateBDM(monitor);
}
}
protected void doGenerateBDM(IProgressMonitor monitor) throws InvocationTargetException {
if (monitor == null) {
monitor = Repository.NULL_PROGRESS_MONITOR;
}
final BusinessObjectModel model = fileStore.getContent();
if (containsBusinessObjects(model)) {
monitor.beginTask(Messages.generatingJarFromBDMModel, IProgressMonitor.UNKNOWN);
BonitaStudioLog.debug(Messages.generatingJarFromBDMModel, BusinessObjectPlugin.PLUGIN_ID);
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
final Map<String, byte[]> resources = new HashMap<>();
Bundle bundle = FrameworkUtil.getBundle(GenerateBDMOperation.class);
ClassLoader bundleClassloader = bundle.adapt(BundleWiring.class).getClassLoader();
//Due to some issue with tycho-surefire-plugin we need to set the proper context classloader
//before invoking the code generation
Thread.currentThread().setContextClassLoader(bundleClassloader);
JDTCompiler compiler = new JDTCompiler();
// Build jar with Model
ResourcesLoader bundleResourcesLoader = new ResourcesLoader();
AbstractBDMJarBuilder builder = new ClientBDMJarBuilder(compiler, bundleResourcesLoader);
final byte[] modelJarContent = builder.build(model, new WithoutDAOImplementationFileFilter());
resources.put(BDM_CLIENT, modelJarContent);
// Build jar with DAO
builder = new ClientBDMJarBuilder(compiler, bundleResourcesLoader);
final byte[] daoJarContent = builder.build(model, new OnlyDAOImplementationFileFilter());
resources.put(BDM_DAO, daoJarContent);
updateDependency(resources.get(BDM_CLIENT));
final Map<String, Object> data = new HashMap<>();
data.put(MODEL, model);
data.put(BDM_DAO, resources.get(BDM_DAO));
eventBroker().send(BDM_DEPLOYED_TOPIC, data);
} catch (final Exception e) {
throw new InvocationTargetException(e);
}finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
} else {
removeDependency();
}
}
protected IEventBroker eventBroker() {
return (IEventBroker) PlatformUI.getWorkbench().getService(IEventBroker.class);