Commit 489f4ddf authored by Romain Bioteau's avatar Romain Bioteau Committed by GitHub
Browse files

feat(app) split community/sp (#352)


Closes BS-16073
parent 4485552d
......@@ -43,10 +43,9 @@ Import-Package: org.bonitasoft.engine.api,
org.bonitasoft.engine.platform,
org.bonitasoft.engine.search,
org.bonitasoft.engine.search.impl,
org.bonitasoft.engine.session,
org.bonitasoft.studio.engine,
org.bonitasoft.studio.engine.operation
org.bonitasoft.engine.session
Bundle-ActivationPolicy: lazy
Export-Package: org.bonitasoft.studio.la.core,
org.bonitasoft.studio.la.i18n,
org.bonitasoft.studio.la.repository
org.bonitasoft.studio.la.repository,
org.bonitasoft.studio.la.ui.validator
......@@ -16,7 +16,6 @@ deletingApplication=Deleting application descriptor...
open=Open
openExistingApplication=Open an existing application
openExistingApplicationDescription=Open an existing application in your current repository
appNameUniqueness=An application descriptor with the same token already exists
deleteExistingApplication=Delete existing applications
deleteExistingApplicationDescription=Delete existing applications
deleteConfirmation=Delete confirmation
......@@ -29,7 +28,6 @@ deployDoneTitle=Deploy completed
deployDoneMessage=All application descriptors below has been deployed successfully
deployFailedTitle=Deploy failed
deploy=Deploy
fetchFromDatabase=Fetch from database
delete=Delete
overview=Overview
description=Description
......@@ -39,16 +37,11 @@ saveBeforeDeploy='%s' has been modified. Save changes ?
layout=Layout
layoutMessage=Your custom layouts have to be deployed manually to be used in applications
theme=Theme
versionMessage=Version of the application
descriptionMessage=Description of the application
themeMessage=You can use a custom theme installed in the Portal, use its theme id
customProfile=You can manually enter a custom profile from the Portal
url=URL
export=Export
selectAppDescriptorToExport=Select an application descriptor to export
exportApplicationDescriptor= Export an application descriptor
selectDestination= Select destination
destination=Destination
exportDoneTitle=Export done
exportDoneMessage=Export operation finished successfully
lookNFeel=Look'n'Feel
......@@ -63,8 +56,6 @@ browse=Browse...
filePathNotEmpty=You must select an application descriptor to import
fileDoesntExist=This file doesn't exist
importConflictWarning=An application descriptor with the same filename already exists
overwriteConfirmation=Overwrite ?
overwriteConfirmationMessage=An application descriptor with the same filename already exists.\nDo you want to overwrite it?
importLabel=Import
notAnApplicationError=Invalid application descriptor xml file
importApplicationDescriptorDesc=Select a valid application descriptor xml file to import.\nYou can export application descriptors from the Portal or from the Studio.
......@@ -73,8 +64,6 @@ invalidCharFileName='%s' is not allowed in filename
invalidFileName='%s' is not a valid filename
unknownCustomLayout=This layout does not exist in the UI Designer
unknownCustomTheme=This theme is not provided by default, make sure this theme is installed in the Portal
exportSingleDoneMessage=%s has been exported
exportFailedMessage=%s has not been exported, an error occurred
exportFailedTitle=Export failed
exportOperation=Export operation
exporting=Exporting %s...
#X-Generator: crowdin.com
applicationStoreName=Aplicaciones
createNewApplication=Create a new Application
create=Crear
newApplicationTitle=Create a new Application
newApplicationDescription=Fill the basic informations to create a new Application
applicationTokenMessage=A unique identifier used as url endpoint for the application (../bonita/apps/<appToken>)
applicationToken=Application Token
version=Version
displayName=T\u00edtulo din\u00e1mico
displayNameMessage=A display name used in the admin portal and in the default application layout.
required=Requerido
tokenValidatorMessage=Alphanumerics only
source=Origen
#X-Generator: crowdin.com
applicationStoreName=Applications
createNewApplication=Cr\u00e9er une application
create=Cr\u00e9er
newApplicationTitle=Cr\u00e9er une application
newApplicationDescription=Remplissez les informations de cr\u00e9ation d'une nouvelle application
applicationTokenMessage=Un identifiant unique utilis\u00e9 comme point de terminaison d'url pour l\u2019application (.. / bonita/apps/<appToken>)
applicationToken=Token URL de l'application
version=Version
displayName=Nom affich\u00e9
displayNameMessage=Un nom d\u2019affichage, utilis\u00e9 dans le portail administrateur et dans le layout d\u2019application par d\u00e9faut.
required=Obligatoire
tokenValidatorMessage=Caract\u00e8res alphanum\u00e9riques seulement
source=Source
#X-Generator: crowdin.com
applicationStoreName=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3
createNewApplication=\u65b0\u3057\u3044\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u4f5c\u6210
create=\u65b0\u898f\u4f5c\u6210
newApplicationTitle=\u65b0\u3057\u3044\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u4f5c\u6210
newApplicationDescription=\u65b0\u3057\u3044\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u4f5c\u6210\u3059\u308b\u57fa\u672c\u7684\u306a\u60c5\u5831\u3092\u8a18\u5165\u3057\u307e\u3059
applicationTokenMessage=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e URL \u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u3068\u3057\u3066\u4f7f\u7528\u3055\u308c\u308b\u30e6\u30cb\u30fc\u30af\u306a\u8b58\u5225\u5b50 (../bonita/apps/<appToken>)
applicationToken=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 \u30c8\u30fc\u30af\u30f3
version=\u30d0\u30fc\u30b8\u30e7\u30f3
displayName=\u8868\u793a\u540d
displayNameMessage=\u7ba1\u7406\u30dd\u30fc\u30bf\u30eb\u3067\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 \u30ec\u30a4\u30a2\u30a6\u30c8\u306b\u4f7f\u7528\u3055\u308c\u308b\u8868\u793a\u540d\u3002
required=\u5fc5\u9808
tokenValidatorMessage=\u82f1\u6570\u5b57\u306e\u307f
source=\u30bd\u30fc\u30b9
newApplication=New...
openApplication=Open...
applicationMenuLabel=Application
ApplicationEditor=Application Editor
applicationMenuLabel=Application Descriptor
ApplicationPerspective=Application Development Perspective
deployApplication=Deploy
deleteApplication=Delete...
exportApplication=Export...
importApplication=Import...
......@@ -49,44 +49,6 @@
</menu>
</menuContribution>
</extension>
<extension
point="org.eclipse.ui.editors">
<editor
class="org.bonitasoft.studio.la.ui.editor.ApplicationEditor"
contributorClass="org.bonitasoft.studio.la.ui.editor.ApplicationEditorActionBarContributor"
default="false"
extensions="xml"
icon="icons/applicationStore.gif"
id="org.bonitasoft.studio.la.editor"
name="%ApplicationEditor">
<contentTypeBinding
contentTypeId="org.bonitasoft.studio.la.applicationDescriptor">
</contentTypeBinding>
</editor>
</extension>
<extension
id=""
point="org.eclipse.core.contenttype.contentTypes">
<content-type
base-type="org.eclipse.core.runtime.xml"
default-charset="UTF-8"
file-extensions="xml"
id="org.bonitasoft.studio.la.applicationDescriptor"
name="Application Descriptor"
priority="normal">
<describer
class="org.bonitasoft.studio.la.ui.editor.ApplicationContentDescriber">
</describer>
</content-type>
</extension>
<extension
point="org.eclipse.ui.perspectives">
<perspective
class="org.bonitasoft.studio.la.ui.ApplicationPerspectiveFactory"
id="org.bonitasoft.studio.la.perspective"
name="%ApplicationPerspective">
</perspective>
</extension>
<extension
point="org.eclipse.wst.xml.core.catalogContributions">
<catalogContribution
......@@ -97,5 +59,13 @@
</public>
</catalogContribution>
</extension>
<extension
point="org.eclipse.ui.perspectives">
<perspective
class="org.bonitasoft.studio.la.ui.ApplicationPerspectiveFactory"
id="org.bonitasoft.studio.la.perspective"
name="%ApplicationPerspective">
</perspective>
</extension>
</plugin>
#X-Generator: crowdin.com
newApplication=Nueva...
applicationMenuLabel=Aplicaci\u00f3n
ApplicationEditor=Application Editor
ApplicationPerspective=Application Development Perspective
#X-Generator: crowdin.com
newApplication=Nouveau...
applicationMenuLabel=Application
ApplicationEditor=Editeur d'application
ApplicationPerspective=Perspective de d\u00e9veloppement d\u2019application
#X-Generator: crowdin.com
newApplication=\u65b0\u898f...
applicationMenuLabel=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3
ApplicationEditor=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 \u30a8\u30c7\u30a3\u30bf
ApplicationPerspective=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u958b\u767a\u306e\u5168\u4f53\u50cf
/**
* Copyright (C) 2017 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.la.core;
import static org.bonitasoft.engine.business.application.xml.ApplicationNodeBuilder.newApplication;
import static org.bonitasoft.engine.business.application.xml.ApplicationNodeBuilder.newApplicationContainer;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.notNull;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.bonitasoft.engine.api.ApplicationAPI;
import org.bonitasoft.engine.business.application.Application;
import org.bonitasoft.engine.business.application.ApplicationSearchDescriptor;
import org.bonitasoft.engine.business.application.xml.ApplicationNodeBuilder.ApplicationBuilder;
import org.bonitasoft.engine.exception.DeletionException;
import org.bonitasoft.engine.search.SearchOptions;
import org.bonitasoft.engine.search.SearchOptionsBuilder;
import org.bonitasoft.engine.search.SearchResult;
import org.bonitasoft.engine.search.impl.SearchResultImpl;
import org.bonitasoft.studio.assertions.StatusAssert;
import org.bonitasoft.studio.common.repository.model.ReadFileStoreException;
import org.bonitasoft.studio.la.repository.ApplicationFileStore;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class DeleteApplicationRunnableTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
public void should_delete_application_by_token() throws Exception {
final ApplicationAPI applicationAPI = mock(ApplicationAPI.class);
final Application application1 = applicationWithId(1);
final Application application2 = applicationWithId(2);
final ApplicationFileStore appFileSotre = applicationXMLWithApps(applicationAPI,
newApplication("myApp1", "My App", "1.0"),
newApplication("myApp2", "My App 2", "1.0"));
when(applicationAPI.searchApplications(notNull(SearchOptions.class)))
.thenReturn(searchResult(Collections.emptyList()));
when(applicationAPI.searchApplications(eq(withToken("myApp1"))))
.thenReturn(searchResult(Arrays.asList(application1)));
when(applicationAPI.searchApplications(eq(withToken("myApp2"))))
.thenReturn(searchResult(Arrays.asList(application2)));
final DeleteApplicationRunnable operation = new DeleteApplicationRunnable(applicationAPI, appFileSotre);
operation.run(new NullProgressMonitor());
verify(applicationAPI).deleteApplication(1);
verify(applicationAPI).deleteApplication(2);
StatusAssert.assertThat(operation.getStatus()).isOK();
}
@Test
public void should_fail_if_application_not_found() throws Exception {
final ApplicationAPI applicationAPI = mock(ApplicationAPI.class);
final Application application1 = applicationWithId(1);
final Application application2 = applicationWithId(2);
final ApplicationFileStore appFileSotre = applicationXMLWithApps(applicationAPI,
newApplication("myApp1", "My App", "1.0"),
newApplication("unknownToken", "My App 2", "1.0"));
when(applicationAPI.searchApplications(notNull(SearchOptions.class)))
.thenReturn(searchResult(Collections.emptyList()));
when(applicationAPI.searchApplications(eq(withToken("myApp1"))))
.thenReturn(searchResult(Arrays.asList(application1)));
when(applicationAPI.searchApplications(eq(withToken("myApp2"))))
.thenReturn(searchResult(Arrays.asList(application2)));
final DeleteApplicationRunnable operation = new DeleteApplicationRunnable(applicationAPI, appFileSotre);
operation.run(new NullProgressMonitor());
verify(applicationAPI).deleteApplication(1);
verify(applicationAPI, never()).deleteApplication(2);
StatusAssert.assertThat(operation.getStatus()).isNotOK();
}
@Test
public void should_not_fail_if_application_not_found_with_ignore_option() throws Exception {
final ApplicationAPI applicationAPI = mock(ApplicationAPI.class);
final Application application1 = applicationWithId(1);
final Application application2 = applicationWithId(2);
final ApplicationFileStore appFileSotre = applicationXMLWithApps(applicationAPI,
newApplication("myApp1", "My App", "1.0"),
newApplication("unknownToken", "My App 2", "1.0"));
when(applicationAPI.searchApplications(notNull(SearchOptions.class)))
.thenReturn(searchResult(Collections.emptyList()));
when(applicationAPI.searchApplications(eq(withToken("myApp1"))))
.thenReturn(searchResult(Arrays.asList(application1)));
when(applicationAPI.searchApplications(eq(withToken("myApp2"))))
.thenReturn(searchResult(Arrays.asList(application2)));
final DeleteApplicationRunnable operation = new DeleteApplicationRunnable(applicationAPI, appFileSotre)
.ignoreErrors();
operation.run(new NullProgressMonitor());
verify(applicationAPI).deleteApplication(1);
verify(applicationAPI, never()).deleteApplication(2);
StatusAssert.assertThat(operation.getStatus()).isOK();
}
@Test
public void should_fail_if_application_deletion_failed() throws Exception {
final ApplicationAPI applicationAPI = mock(ApplicationAPI.class);
doThrow(DeletionException.class).when(applicationAPI).deleteApplication(1);
final Application application1 = applicationWithId(1);
final ApplicationFileStore appFileSotre = applicationXMLWithApps(applicationAPI,
newApplication("myApp1", "My App", "1.0"));
when(applicationAPI.searchApplications(notNull(SearchOptions.class)))
.thenReturn(searchResult(Collections.emptyList()));
when(applicationAPI.searchApplications(eq(withToken("myApp1"))))
.thenReturn(searchResult(Arrays.asList(application1)));
final DeleteApplicationRunnable operation = new DeleteApplicationRunnable(applicationAPI, appFileSotre);
operation.run(new NullProgressMonitor());
verify(applicationAPI).deleteApplication(1);
StatusAssert.assertThat(operation.getStatus()).isNotOK();
}
private SearchResult<Application> searchResult(List<Application> resultAsList) {
return new SearchResultImpl<>(resultAsList.size(), resultAsList);
}
protected SearchOptions withToken(String token) {
return new SearchOptionsBuilder(0, 1).filter(ApplicationSearchDescriptor.TOKEN, token).done();
}
protected Application applicationWithId(long id) {
final Application app = mock(Application.class);
when(app.getId()).thenReturn(Long.valueOf(id));
return app;
}
private ApplicationFileStore applicationXMLWithApps(ApplicationAPI applicationAPI, ApplicationBuilder... apps)
throws ReadFileStoreException {
final ApplicationFileStore fileStore = mock(ApplicationFileStore.class);
when(fileStore.getContent()).thenReturn(newApplicationContainer()
.havingApplications(apps)
.create());
return fileStore;
}
}
/**
* Copyright (C) 2017 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.la.core;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.bonitasoft.engine.api.ApplicationAPI;
import org.bonitasoft.engine.business.application.ApplicationImportPolicy;
import org.bonitasoft.engine.exception.AlreadyExistsException;
import org.bonitasoft.studio.assertions.StatusAssert;
import org.bonitasoft.studio.la.repository.ApplicationFileStore;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.junit.Test;
public class DeployApplicationRunnableTest {
@Test
public void should_import_application_xml() throws Exception {
final ApplicationAPI applicationAPI = mock(ApplicationAPI.class);
final ApplicationFileStore appFileSotre = mock(ApplicationFileStore.class);
final DeployApplicationRunnable operation = spy(new DeployApplicationRunnable(applicationAPI, appFileSotre));
doReturn(Status.OK_STATUS).when(operation).deleteBeforeDeploy(any(IProgressMonitor.class));
operation.run(new NullProgressMonitor());
verify(applicationAPI).importApplications(appFileSotre.toByteArray(), ApplicationImportPolicy.FAIL_ON_DUPLICATES);
}
@Test
public void should_fail_import_application_xml_when_app_already_exists() throws Exception {
final ApplicationAPI applicationAPI = mock(ApplicationAPI.class);
when(applicationAPI.importApplications(any(byte[].class), eq(ApplicationImportPolicy.FAIL_ON_DUPLICATES)))
.thenThrow(AlreadyExistsException.class);
final ApplicationFileStore appFileSotre = mock(ApplicationFileStore.class);
final DeployApplicationRunnable operation = spy(new DeployApplicationRunnable(applicationAPI, appFileSotre));
doReturn(Status.OK_STATUS).when(operation).deleteBeforeDeploy(any(IProgressMonitor.class));
operation.run(new NullProgressMonitor());
StatusAssert.assertThat(operation.getStatus()).isNotOK();
}
}
/**
* Copyright (C) 2016 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.la.core;
public class ApplicationDescriptorNotFoundException extends Exception {
public ApplicationDescriptorNotFoundException() {
super();
}
}
/**
* Copyright (C) 2017 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.la.core;
import static java.util.Objects.requireNonNull;
import static org.bonitasoft.studio.ui.util.StatusCollectors.toMultiStatus;
import java.lang.reflect.InvocationTargetException;
import org.bonitasoft.engine.api.ApplicationAPI;
import org.bonitasoft.engine.business.application.Application;
import org.bonitasoft.engine.business.application.ApplicationSearchDescriptor;
import org.bonitasoft.engine.business.application.xml.ApplicationNode;
import org.bonitasoft.engine.exception.DeletionException;
import org.bonitasoft.engine.exception.SearchException;
import org.bonitasoft.engine.search.SearchOptions;
import org.bonitasoft.engine.search.SearchOptionsBuilder;
import org.bonitasoft.studio.common.repository.model.ReadFileStoreException;
import org.bonitasoft.studio.la.LivingApplicationPlugin;
import org.bonitasoft.studio.la.i18n.Messages;
import org.bonitasoft.studio.la.repository.ApplicationFileStore;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.operation.IRunnableWithProgress;
public class DeleteApplicationRunnable implements IRunnableWithProgress {
private final ApplicationAPI applicationAPI;
private final ApplicationFileStore appFileStore;
private IStatus status;
private boolean ignoreErrors = false;
public DeleteApplicationRunnable(ApplicationAPI applicationAPI, ApplicationFileStore appFileStore) {
this.applicationAPI = requireNonNull(applicationAPI);
this.appFileStore = requireNonNull(appFileStore);
}
public DeleteApplicationRunnable ignoreErrors() {
this.ignoreErrors = true;
return this;
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.operation.IRunnableWithProgress#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
monitor.beginTask(Messages.deletingApplication, IProgressMonitor.UNKNOWN);
try {
status = appFileStore
.getContent()
.getApplications()
.stream()
.map(this::delete)
.collect(toMultiStatus());
} catch (final ReadFileStoreException e) {
throw new InvocationTargetException(e, "Cannot retrieve application xml content for %s");
}
monitor.done();
}
public IStatus getStatus() {
return status;
}
private SearchOptions withToken(String token) {
return new SearchOptionsBuilder(0, 1).filter(ApplicationSearchDescriptor.TOKEN, token).done();
}
private IStatus delete(ApplicationNode applicationNode) {
try {
final long applicationId = applicationAPI.searchApplications(withToken(applicationNode.getToken()))
.getResult().stream()
.mapToLong(Application::getId)
.findFirst()
.orElseThrow(ApplicationDescriptorNotFoundException::new);