/** * */ package org.activiti.designer.util.extension; import java.io.InputStream; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.jar.Manifest; import org.activiti.bpmn.model.CustomProperty; import org.activiti.bpmn.model.ServiceTask; import org.activiti.designer.integration.palette.AbstractDefaultPaletteCustomizer; import org.activiti.designer.integration.palette.DefaultPaletteCustomizer; import org.activiti.designer.integration.palette.PaletteEntry; import org.activiti.designer.integration.servicetask.AbstractCustomServiceTask; import org.activiti.designer.integration.servicetask.CustomServiceTask; import org.activiti.designer.integration.servicetask.CustomServiceTaskDescriptor; import org.activiti.designer.util.eclipse.ActivitiUiUtil; import org.activiti.designer.util.eclipse.ExtensionConstants; import org.apache.commons.lang.StringUtils; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Path; import org.eclipse.graphiti.ui.editor.DiagramEditor; import org.eclipse.jdt.core.IClassFile; import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJarEntryResource; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.core.JarEntryDirectory; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage; /** * Provides utilities for Extensions to the Designer. * * @author Tiese Barrell * @since 0.5.1 * @version 1 * */ public final class ExtensionUtil { public static final String USER_LIBRARY_NAME_EXTENSIONS = "Activiti Designer Extensions"; public static final String DESIGNER_EXTENSIONS_USER_LIB_PATH = "org.eclipse.jdt.USER_LIBRARY/" + USER_LIBRARY_NAME_EXTENSIONS; public static List<CustomServiceTaskDescriptor> providedCustomServiceTaskDescriptors; private static final List<String> FLAGS = new ArrayList<String>(); private ExtensionUtil() { } public static void addProvidedCustomServiceTaskDescriptors(List<CustomServiceTaskDescriptor> descriptors) { if (providedCustomServiceTaskDescriptors == null) providedCustomServiceTaskDescriptors = new ArrayList<CustomServiceTaskDescriptor>(); providedCustomServiceTaskDescriptors.addAll(descriptors); } public static final Set<PaletteEntry> getDisabledPaletteEntries(IProject project) { Set<PaletteEntry> result = new HashSet<PaletteEntry>(); // Determine the project IJavaProject javaProject = null; try { javaProject = (IJavaProject) project.getNature(JavaCore.NATURE_ID); } catch (CoreException e) { // skip, not a Java project } if (javaProject != null) { try { // Get the container for the designer extensions. This is the // predefined user library linking to the extension libraries final IClasspathContainer userLibraryContainer = JavaCore.getClasspathContainer(new Path(DESIGNER_EXTENSIONS_USER_LIB_PATH), javaProject); // Get a list of the classpath entries in the container. Each of // these represents one jar containing zero or more designer // extensions final IClasspathEntry[] extensionJars = userLibraryContainer.getClasspathEntries(); // If there are jars, inspect them; otherwise return because // there are no extensions if (extensionJars.length > 0) { for (final IClasspathEntry classpathEntry : extensionJars) { // Only check entries of the correct kind if (classpathEntry.getEntryKind() == 1 && classpathEntry.getContentKind() == 2) { final IPackageFragmentRoot packageFragmentRoot = javaProject.getPackageFragmentRoot(classpathEntry.getPath().toString()); // Create a JarClassLoader to load any classes we // find for this extension final JarClassLoader cl = new JarClassLoader(packageFragmentRoot.getPath().toPortableString()); // Inspect the jar by scanning its classpath and looking for // classes that implement // CustomServiceTask final IJavaElement[] javaElements = packageFragmentRoot.getChildren(); for (final IJavaElement javaElement : javaElements) { if (javaElement.getElementType() == IJavaElement.PACKAGE_FRAGMENT) { IPackageFragment fragment = (IPackageFragment) javaElement; if (fragment.containsJavaResources()) { final IClassFile[] classFiles = fragment.getClassFiles(); for (final IClassFile classFile : classFiles) { if (classFile.isClass()) { final IType type = classFile.getType(); if (!isConcretePaletteCustomizer(type)) { continue; } try { Class<DefaultPaletteCustomizer> clazz = (Class<DefaultPaletteCustomizer>) cl.loadClass(type.getFullyQualifiedName()); if (DefaultPaletteCustomizer.class.isAssignableFrom(clazz)) { try { DefaultPaletteCustomizer DefaultPaletteCustomizer = (DefaultPaletteCustomizer) clazz.newInstance(); // Add this DefaultPaletteCustomizer to the result result.addAll(DefaultPaletteCustomizer.disablePaletteEntries()); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } } } } } } catch (JavaModelException e) { // TODO: test when this exception occurs: if there is no user // lib for example? e.printStackTrace(); } } return result; } private static boolean isConcreteCustomService(IType type) { boolean customserviceFound = containsAbstractClassOrInterface(type, AbstractCustomServiceTask.class, CustomServiceTask.class); try { if (customserviceFound && !Modifier.isAbstract(type.getFlags())) { customserviceFound = true; } else { customserviceFound = false; } } catch (JavaModelException e) { customserviceFound = false; e.printStackTrace(); } return customserviceFound; } private static boolean isConcretePaletteCustomizer(IType type) { boolean paletteCustomizerFound = containsAbstractClassOrInterface(type, AbstractDefaultPaletteCustomizer.class, DefaultPaletteCustomizer.class); try { if (paletteCustomizerFound && !Modifier.isAbstract(type.getFlags())) { paletteCustomizerFound = true; } else { paletteCustomizerFound = false; } } catch (JavaModelException e) { paletteCustomizerFound = false; e.printStackTrace(); } return paletteCustomizerFound; } // TODO: utilize type hierarchy in efficient manner private static boolean containsAbstractClassOrInterface(IType type, Class targetAbstractClass, Class targetInterface) { boolean result = false; try { // 1. Check whether the super classname of the type matches the abstract // superclass we favor // 2. Check whether the type implements the interface itself // 3. If the type has *other* superclasses than the one we favor, // recursively inspect the hierarchy // using // the same method if (targetAbstractClass.getCanonicalName().equalsIgnoreCase(type.getSuperclassName())) { result = true; } else if (type.getSuperInterfaceNames() != null && type.getSuperInterfaceNames().length > 0) { for (String interfaceName : type.getSuperInterfaceNames()) { if (targetInterface.getCanonicalName().equalsIgnoreCase(interfaceName)) { result = true; } } } // } else if (type.getSuperclassName() != null) { // result = // containsAbstractClassOrInterface(hierarchy.getSuperclass(type), // targetAbstractClass, targetInterface); // } } catch (Exception e) { result = false; } return result; } /** * Wraps the property id for the provided service tasks. * * @param serviceTask * the parent task the property belongs to * @param propertyId * the id of the property to wrap * @return the wrapped id */ public static final String wrapCustomPropertyId(final ServiceTask serviceTask, final String propertyId) { return serviceTask.getId() + ExtensionConstants.CUSTOM_PROPERTY_ID_SEPARATOR + propertyId; } /** * Unwraps the provided property id string. * * @param wrappedCustomPropertyId * the id string to unwrap * @return the unwrapped id */ public static final String upWrapCustomPropertyId(final String wrappedCustomPropertyId) { return StringUtils.substringAfter(wrappedCustomPropertyId, ExtensionConstants.CUSTOM_PROPERTY_ID_SEPARATOR); } /** * Determines whether the provided business object is a custom service task. * * @param bo * the business object * @return true if the business object is a custom service task, false * otherwise */ public static final boolean isCustomServiceTask(final Object bo) { boolean result = false; if (bo instanceof ServiceTask) { final ServiceTask serviceTask = (ServiceTask) bo; return StringUtils.isNotEmpty(serviceTask.getExtensionId()); } return result; } /** * Gets the {@link CustomProperty} identified by the provided name from the * serviceTask. * * @param serviceTask * the servicetask that holds the custom property * @param propertyName * the name of the property * @return the {@link CustomProperty} found or null if no property was found */ public static final CustomProperty getCustomProperty(final ServiceTask serviceTask, final String propertyName) { CustomProperty result = null; for (final CustomProperty customProperty : serviceTask.getCustomProperties()) { if (propertyName.equals(customProperty.getName())) { result = customProperty; break; } } return result; } /** * Determines whether the {@link CustomProperty} identified by the provided * name is held by the serviceTask. * * @param serviceTask * the servicetask that holds the custom property * @param propertyName * the name of the property * @return true if the serviceTask has the property, false otherwise */ public static final boolean hasCustomProperty(final ServiceTask serviceTask, final String propertyName) { boolean result = false; for (final CustomProperty customProperty : serviceTask.getCustomProperties()) { if (propertyName.equals(customProperty.getName())) { result = true; break; } } return result; } /** * Gets a list of {@link CustomServiceTask} objects based on the * {@link TabbedPropertySheetPage} provided. * * @param tabbedPropertySheetPage * the property sheet page linked to a diagram in a project that has * {@link CustomServiceTask}s defined * @return a list of all {@link CustomServiceTask}s or an empty list if none * were found */ public static List<CustomServiceTask> getCustomServiceTasks(final TabbedPropertySheetPage tabbedPropertySheetPage) { // Determine the part the property sheet page is in final IWorkbenchPart part = tabbedPropertySheetPage.getSite().getWorkbenchWindow().getPartService().getActivePart(); // If the part is a diagram editor, get the project from the diagram if (part instanceof DiagramEditor) { final DiagramEditor editor = (DiagramEditor) part; final IProject project = ActivitiUiUtil.getProjectFromDiagram(editor.getDiagramTypeProvider().getDiagram()); // Determine the custom service tasks using the project found return getCustomServiceTasks(project); } return null; } /** * Gets a list of {@link CustomServiceTask} objects based on the * {@link IProject} provided. * * @param project * the project that has {@link CustomServiceTask}s defined * @return a list of all {@link CustomServiceTask}s or an empty list if none * were found */ public static List<CustomServiceTask> getCustomServiceTasks(final IProject project) { List<CustomServiceTask> result = new ArrayList<CustomServiceTask>(); // Determine the project IJavaProject javaProject = null; try { javaProject = (IJavaProject) project.getNature(JavaCore.NATURE_ID); } catch (CoreException e) { // skip, not a Java project } if (javaProject != null) { // get the contexts first final List<CustomServiceTaskContext> cstContexts = getCustomServiceTaskContexts(project); // extract custom service tasks from the contexts for (final CustomServiceTaskContext customServiceTaskContext : cstContexts) { result.add(customServiceTaskContext.getServiceTask()); } } return result; } /** * Gets a list of {@link CustomServiceTaskContext} objects based on the * {@link IProject} provided. * * @param project * the project that has {@link CustomServiceTask}s defined * @return a list containing the context of each {@link CustomServiceTask} * object found or an empty list if {@link CustomServiceTask}s were * found were found */ public static List<CustomServiceTaskContext> getCustomServiceTaskContexts(final IProject project) { List<CustomServiceTaskContext> result = new ArrayList<CustomServiceTaskContext>(); addToCustomServiceTasks(result); IJavaProject javaProject = null; try { javaProject = (IJavaProject) project.getNature(JavaCore.NATURE_ID); } catch (CoreException e) { // skip, not a Java project } if (javaProject != null) { try { // Get the container for the designer extensions. This is the // predefined user library linking to the extension libraries final IClasspathContainer userLibraryContainer = JavaCore.getClasspathContainer(new Path(DESIGNER_EXTENSIONS_USER_LIB_PATH), javaProject); // Get a list of the classpath entries in the container. Each of // these represents one jar containing zero or more designer // extensions final IClasspathEntry[] extensionJars = userLibraryContainer.getClasspathEntries(); // If there are jars, inspect them; otherwise return because // there are no extensions if (extensionJars.length > 0) { for (final IClasspathEntry classpathEntry : extensionJars) { // Only check entries of the correct kind if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY && classpathEntry.getContentKind() == IPackageFragmentRoot.K_BINARY) { final IPackageFragmentRoot packageFragmentRoot = javaProject.getPackageFragmentRoot(classpathEntry.getPath().toString()); // Guard against JARs that are not open, // http://jira.codehaus.org/browse/ACT-1445 if (!packageFragmentRoot.isOpen()) { final String exceptionMessage = String .format("There was an error when processing an extension to Activiti Designer. The extension at location '%s' is not open. Please make sure it is not installed inside the Eclipse workspace.", classpathEntry.getPath().toString()); showExtensionExceptionMessageIfNotFlagged(exceptionMessage, packageFragmentRoot); continue; } // Determine the name of the extension String extensionName = null; // Extract the manifest and look for the // CustomServiceTask.MANIFEST_EXTENSION_NAME // property final Manifest manifest = extractManifest(packageFragmentRoot); if (manifest != null) { extensionName = manifest.getMainAttributes().getValue(CustomServiceTask.MANIFEST_EXTENSION_NAME); } // If there is no manifest or the property wasn't // defined, use the jar's name as extension name // instead if (extensionName == null) { extensionName = classpathEntry.getPath().lastSegment(); } // Create a JarClassLoader to load any classes we // find for this extension final JarClassLoader cl = new JarClassLoader(packageFragmentRoot.getPath().toPortableString()); // Inspect the jar by scanning its classpath and // looking for classes that implement // CustomServiceTask final IJavaElement[] javaElements = packageFragmentRoot.getChildren(); for (final IJavaElement javaElement : javaElements) { if (javaElement.getElementType() == IJavaElement.PACKAGE_FRAGMENT) { IPackageFragment fragment = (IPackageFragment) javaElement; if (fragment.containsJavaResources()) { final IClassFile[] classFiles = fragment.getClassFiles(); for (final IClassFile classFile : classFiles) { if (classFile.isClass()) { final IType type = classFile.getType(); if (!isConcreteCustomService(type)) { continue; } // if (type.getFullyQualifiedName() != // "com.atosorigin.esuite.editors.servicetasks.ESuiteEndNode") // { // continue; // } try { Class<CustomServiceTask> clazz = (Class<CustomServiceTask>) cl.loadClass(type.getFullyQualifiedName()); // Filter if the class is // abstract: this probably // means it is extended by // concrete classes in the // extension and will have // any properties applied in // that way; we can't // instantiate the class // anyway if (!Modifier.isAbstract(clazz.getModifiers()) && CustomServiceTask.class.isAssignableFrom(clazz)) { try { CustomServiceTask customServiceTask = (CustomServiceTask) clazz.newInstance(); // Add this // CustomServiceTask // to // the result, // wrapped // in its context result.add(new CustomServiceTaskContextImpl(customServiceTask, extensionName, classpathEntry.getPath().toPortableString())); } catch (InstantiationException e) { // TODO // Auto-generated // catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO // Auto-generated // catch block e.printStackTrace(); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } } } } } } catch (JavaModelException e) { showExtensionExceptionMessage(String.format("There was a technical error when processing an extension to Activiti Designer: %s", e.getMessage())); e.printStackTrace(); } } return result; } private static void showExtensionExceptionMessageIfNotFlagged(final String detailMessage, final IPackageFragmentRoot packageFragmentRoot) { final String flagKey = packageFragmentRoot.getPath().toPortableString(); if (!FLAGS.contains(flagKey)) { FLAGS.add(flagKey); showExtensionExceptionMessage(detailMessage); } } private static void showExtensionExceptionMessage(final String detailMessage) { MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Error in extension", detailMessage); } private static void addToCustomServiceTasks(List<CustomServiceTaskContext> result) { if (providedCustomServiceTaskDescriptors != null) { for (CustomServiceTaskDescriptor dscr : providedCustomServiceTaskDescriptors) { Class< ? extends CustomServiceTask> clazz = dscr.getClazz(); if (clazz != null && !Modifier.isAbstract(clazz.getModifiers()) && CustomServiceTask.class.isAssignableFrom(clazz)) { try { CustomServiceTask customServiceTask = (CustomServiceTask) clazz.newInstance(); result.add(new CustomServiceTaskContextImpl(customServiceTask, dscr.getExtensionName(), dscr.getExtensionJarPath())); } catch (Exception e) { e.printStackTrace(); } } } } } /** * @param packageFragmentRoot * @throws JavaModelException * @throws CoreException */ @SuppressWarnings("restriction") private static Manifest extractManifest(IPackageFragmentRoot packageFragmentRoot) throws JavaModelException { Manifest result = null; final Object[] nonJavaResources = packageFragmentRoot.getNonJavaResources(); for (Object obj : nonJavaResources) { if (obj instanceof JarEntryDirectory) { final JarEntryDirectory jarEntryDirectory = (JarEntryDirectory) obj; final IJarEntryResource[] jarEntryResources = jarEntryDirectory.getChildren(); for (IJarEntryResource jarEntryResource : jarEntryResources) { if ("MANIFEST.MF".equals(jarEntryResource.getName())) { try { final InputStream stream = jarEntryResource.getContents(); result = new Manifest(stream); } catch (Exception e) { // no manifest as result } } } } } return result; } }