FirstSpirit Module - Sichtbarkeit von Funktionen steuern

Veröffentlicht am 21.08.2019

Wenn in einem Firstspirit-Projekt eigene Erweiterungen für den ContentCreator entwickelt werden, wird damit meistens auch gleich für jedes Projekt eine eigene Webapplikation ausgeliefert. Diese Lösung wird mit steigender Projektanzahl immer ineffizienter und verlangt deutlich mehr Ressourcen vom Application-Server. Besser ist es, alle Projekte mit derselben, zentralen WebApp zu bedienen und die jeweils benötigten Funktionen nur bei Bedarf für einzelne Projekte aktiv zu schalten. Wir zeigen wie es geht!

Peter Zeidler

Research and Development

Lösung

Um dieses Verhalten zu erreichen, benötigen wir in unserem Modul eine sogenannte Projekt-App. Ist diese in einem Projekt installiert, werden dadurch die Funktionalitäten des aktivierten Moduls für dieses Projekt zur Verfügung gestellt und wirken somit als Feature Toggle. Zusätzlich bekommen wir durch die Projekt-App einen Speicherort zur Verfügung gestellt, auf welchen wir von allen sonstigen Komponenten aus zugreifen können.

Umsetzung

Für die Umsetzung brauchen wir lediglich eine “ProjectApp”, ein paar Hilfsmethoden und diverse Plugins, die wir als Testobjekte verwenden können.

Als erstes benötigen wir eine leere Klasse vom Typ “ProjectApp”, die zum Aktivieren der Erweiterung im Projekt installiert werden muss.

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) {}
}

Für die erzeugte Klassen brauchen wir auch einen passenden Eintrag in unserer module.xml.

<project-app>
	<name>myProjectApp</name>
	<class>de.aboutcontent.blogpost.projectapp.MyProjectApp</class>
</project-app>

Prüfung im Plugin

Zur Prüfung, ob unsere ProjectApp im aktuellen Projekt installiert ist, erweitern wir unsere Klasse um eine Methode, die sich den ModuleAdminAgent zur Hilfe nimmt, um herauszufinden in welchem Projekt die Komponente aus dem angegebenen Modul installiert ist.

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;
}

Diese Methode würde für unseren Anwendungsfall bereits ausreichen. Um das Ganze aber etwas komfortabler nutzen zu können, fügen wir noch eine weitere Methode hinzu, die den Modul- und Komponenten-Name anhand einer übergebenen Klasse ermittelt.

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()));
}

Mithilfe dieser beiden Methoden haben wir die Grundlagen geschaffen, um prüfen zu können, ob die ProjektApp im aktuellen Projekt installiert ist. Nun können wir uns deren Verwendung in einem Plugin ansehen, dessen Sichtbarkeit innerhalb von Firstspirit gesteuert werden kann.

Verwendung in der Toolbar

Als erstes schauen wir uns die Verwendung beispielhaft in einem ToolbarPlugin für den ContentCreator an. Hierzu benötigen wir eine WebApp, die unsere Implementierungen im ContentCreator zur Verfügung stellt.

<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 dem eigentlichen Plugin wird dann über die Methode isVisible gesteuert, ob das Plugin angezeigt wird oder nicht. Damit nicht bei jedem Aufruf des Plugins erneut geprüft wird, ob die ProjectApp installiert ist, wird dies nur einmalig bei der Instanziierung des Plugins getan. Die Instanziierung des Plugins entspricht in unserem Beispiel dem Start des Clients.

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";
		}
	}
}

Nutzung in weiteren Plugins

Genauso können wir die Funktionalität in Plugins nutzen, die über keine Sichtbarkeits-Steuerung verfügen und immer ausgeführt werden, sobald sie auf der Server installiert wurden. Im nächsten Beispiel schauen wir uns die Verwendung in einem UploadHook an. Hierbei beenden wir die Ausführung vor unserer eigentlichen Business Logik.

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
	}
}

Fazit

Da jetzt bereits eine ProjectApp im Modul vorhanden ist, kann diese ohne zusätzlichen Aufwand genutzt werden, um beispielsweise eine zentrale Konfigurationen des Projekts bereitzustellen, ohne dass diese von jedem Nutzer eingesehen werden kann.

Die Sourcen dieses Moduls stehen frei zur Verfügung und können über Maven heruntergeladen werden. Zum Bauen des Moduls werden jedoch die von e-Spirit bereitgestellten Tools benötigt, die in unserem Blog-Eintrag Einsteiger Guide beschrieben wurden.

Wenn Sie weitere Fragen zur Entwicklung von Erweiterungen haben oder Sie unsere Unterstützung für Ihr Projekt benötigen, zögern Sie nicht und treten Sie mit uns in Kontakt

facebook icon twitter icon xing icon linkedin icon
© 2019 aboutcontent GmbH