To achieve this behavior, we need a so-called project app in our module. If this is installed in a project, the functionalities of the activated module are made available for this project and thus act as a feature toggle. In addition, the project app provides us with a storage location that we can access from all other components.
For the complete implementation we only need a "ProjectApp", a few helper methods and various plugins, which we can use as test objects.
First we need an empty class of type "ProjectApp", which has to be installed in the project to activate the extension.
package de.aboutcontent.blogpost.projectapp;
import de.espirit.firstspirit.module.ProjectApp;
import de.espirit.firstspirit.module.ProjectEnvironment;
import de.espirit.firstspirit.module.descriptor.ProjectAppDescriptor;
public class MyProjectApp implements ProjectApp {
@Override
public void init(ProjectAppDescriptor projectAppDescriptor, ProjectEnvironment projectEnvironment) {}
@Override
public void installed() {}
@Override
public void uninstalling() {}
@Override
public void updated(String oldVersion) {}
}
To make the created classes visible we need a new entry in our module.xml
file.
<project-app>
<name>myProjectApp</name>
<class>de.aboutcontent.blogpost.projectapp.MyProjectApp</class>
</project-app>
To check whether our ProjectApp is installed in the current project, we extend our class with a method that uses the ModuleAdminAgent to figure out in which project the specified module component is installed.
private static boolean isInstalled(ModuleAdminAgent moduleAdminAgent, String moduleName, String componentName, long projectId) {
Collection<Project> projects = moduleAdminAgent.getProjectAppUsages(moduleName, componentName);
for (Project project : projects) {
if (projectId != project.getId()) {
continue;
}
return true;
}
return false;
}
This approach would already be sufficient for our application. In order to be able to use the whole however somewhat more comfortable, we add another method, which determines the module and component name based on the class that has been provided.
public static boolean isInstalled(SpecialistsBroker broker, Class<? extends ProjectApp> projectAppClass) {
ModuleAdminAgent moduleAdminAgent = broker.requireSpecialist(ModuleAdminAgent.TYPE);
Collection<ModuleDescriptor> modules = moduleAdminAgent.getModules();
for (ModuleDescriptor moduleDescriptor : modules) {
ComponentDescriptor[] components = moduleDescriptor.getComponents();
for (ComponentDescriptor componentDescriptor : components) {
if (!projectAppClass.getCanonicalName().equals(componentDescriptor.getComponentClass())) {
continue;
}
if (componentDescriptor.getType() != ComponentDescriptor.Type.PROJECTAPP) {
throw new RuntimeException(String.format("found wrong component type [expected: %s, actual: %s]", ComponentDescriptor.Type.PROJECTAPP.name(), componentDescriptor.getType().name()));
}
long projectId = broker.requireSpecialist(ProjectAgent.TYPE).getId();
return isInstalled(moduleAdminAgent, moduleDescriptor.getName(), componentDescriptor.getName(), projectId);
}
}
throw new RuntimeException(String.format("component or class not found in any module [class: %s]", projectAppClass.getCanonicalName()));
}
With the help of these two methods, we have laid the foundation to be able to check whether the ProjectApp is installed in the current project. Now we can look at its use in a plugin where its visibility can be controlled within FirstSpirit.
The first thing we'll look at is the usage in a ToolbarPlugin for the ContentCreator. For this we first need a WebApp, which provides our implementations in the ContentCreator.
<web-app scopes="Global,Project">
<name>myProjectAppWebApp</name>
<description>WebApp zum Bereitstellen der Klassen im ContentCreator</description>
<web-resources>
${module.resources.global.legacy.web}
</web-resources>
<web-xml>web/web.xml</web-xml>
</web-app>
In the actual plugin, the method isVisible controls whether the plugin is displayed or not. So that the ProjectApp is not checked each time the plugin is called, this is only done once during the instantiation of the plugin. In our example, the instantiation of the plugin corresponds to the start of the client.
package de.aboutcontent.blogpost.projectapp;
import java.util.Collection;
import java.util.Collections;
import de.espirit.firstspirit.access.BaseContext;
import de.espirit.firstspirit.agency.OperationAgent;
import de.espirit.firstspirit.client.plugin.toolbar.ToolbarContext;
import de.espirit.firstspirit.ui.operations.RequestOperation;
import de.espirit.firstspirit.webedit.plugin.WebeditToolbarActionsItemsPlugin;
import de.espirit.firstspirit.webedit.plugin.toolbar.ExecutableToolbarActionsItem;
import de.espirit.firstspirit.webedit.plugin.toolbar.WebeditToolbarItem;
public class MyWebeditToolbarActionsItemsPlugin implements WebeditToolbarActionsItemsPlugin {
public boolean isInstalled = false;
@Override
public void setUp(BaseContext context) {
isInstalled = MyProjectApp.isInstalled(context, MyProjectApp.class);
}
@Override
public void tearDown() {}
@Override
public Collection<? extends WebeditToolbarItem> getItems() {
return Collections.singleton(new WebeditToolbarItemImplementation());
}
public final class WebeditToolbarItemImplementation implements ExecutableToolbarActionsItem {
@Override
public boolean isVisible(ToolbarContext toolbarContext) {
return isInstalled;
}
@Override
public boolean isEnabled(ToolbarContext toolbarContext) {
return true;
}
@Override
public void execute(ToolbarContext toolbarContext) {
RequestOperation requestOperation = toolbarContext.requireSpecialist(OperationAgent.TYPE).getOperation(RequestOperation.TYPE);
requestOperation.perform("Die ProjectApp wurde in diesem Projekt installiert");
}
@Override
public String getIconPath(ToolbarContext toolbarContext) {
return null;
}
@Override
public String getLabel(ToolbarContext toolbarContext) {
return "MyWebeditToolbarActionsItemsPlugin";
}
}
}
The infrastructure created this way can of course not only be used in the ContentCreator but are also available to us in the SiteArchitect.
Similarly, we can use the functionality in plugins that have no visibility control and are always executed as soon as they are installed on the server. In the next example, let's look at the use in an UploadHook. Here we terminate the execution before our actual business logic.
package de.aboutcontent.blogpost.projectapp;
import java.io.IOException;
import java.io.InputStream;
import de.espirit.firstspirit.access.BaseContext;
import de.espirit.firstspirit.access.project.Resolution;
import de.espirit.firstspirit.access.store.mediastore.File;
import de.espirit.firstspirit.access.store.mediastore.Media;
import de.espirit.firstspirit.access.store.mediastore.MediaElement;
import de.espirit.firstspirit.access.store.mediastore.Picture;
import de.espirit.firstspirit.access.store.mediastore.UploadRejectedException;
import de.espirit.firstspirit.service.mediamanagement.UploadHook;
public class MyUploadHook implements UploadHook {
@Override
public void postProcess(BaseContext context, Media media, File file, long length) {
if (!MyProjectApp.isInstalled(context, MyProjectApp.class)) {
return;
}
// do something
}
@Override
public void postProcess(BaseContext context, Media media, Picture picture, long length) {
if (!MyProjectApp.isInstalled(context, MyProjectApp.class)) {
return;
}
// do something
}
@Override
public void preProcess(BaseContext context, Media media, File file, InputStream inputStream, long length)
throws UploadRejectedException, IOException {
if (!MyProjectApp.isInstalled(context, MyProjectApp.class)) {
return;
}
// do something
}
@Override
public void preProcess(BaseContext context, Media media, Picture picture, Resolution reolution, InputStream inputStream, long length)
throws UploadRejectedException, IOException {
if (!MyProjectApp.isInstalled(context, MyProjectApp.class)) {
return;
}
// do something
}
@Override
public void uploadAborted(BaseContext context, Media media, MediaElement mediaElement) {
if (!MyProjectApp.isInstalled(context, MyProjectApp.class)) {
return;
}
// do something
}
}
Since a ProjectApp already exists in the module, it can be used without additional effort, for example to provide a central configuration of the project, without it being visible to every user. In addition, this would be transported with every export and import of the project.
The sources of this module are freely available and can be downloaded via Maven. To build the module, however, the tools provided by e-Spirit are required, which were described in our blog entry
Getting Started.
If you have further questions about the development of extensions or if you need our support for your project, do not hesitate to
contact us!