diff --git a/source/java/org/alfresco/traitextender/AJDanglingExtensionError.java b/source/java/org/alfresco/traitextender/AJDanglingExtensionError.java new file mode 100644 index 0000000000..05540b65f0 --- /dev/null +++ b/source/java/org/alfresco/traitextender/AJDanglingExtensionError.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +import java.lang.reflect.Method; + +public class AJDanglingExtensionError implements AJExtensibleCompilingError +{ + private Method danglingMethod; + private Extend extendDeclaration; + + public AJDanglingExtensionError(Method danglingMethod,Extend extendDeclaration) + { + super(); + this.danglingMethod = danglingMethod; + this.extendDeclaration=extendDeclaration; + } + + + + @Override + public String getShortMessage() + { + return "Dangling extension method "+danglingMethod+" "+extendDeclaration; + } + +} diff --git a/source/java/org/alfresco/traitextender/AJExtender.java b/source/java/org/alfresco/traitextender/AJExtender.java new file mode 100644 index 0000000000..87ce87a2ea --- /dev/null +++ b/source/java/org/alfresco/traitextender/AJExtender.java @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; + +import com.hazelcast.util.ConcurrentHashSet; + +public class AJExtender +{ + private static final Object[] SAFE_NULL_ARGS = new Object[0]; + + private static Log logger = LogFactory.getLog(AJExtender.class); + + private static ConcurrentHashSet oneTimeLogSet = null; + + private static final ThreadLocal> ajPointsLocalEnabled = new ThreadLocal>() + { + protected Stack initialValue() + { + Stack enablementStack = new Stack(); + enablementStack.push(true); + return enablementStack; + }; + }; + + static class ProceedingContext + { + final Extend extend; + + final ProceedingJoinPoint proceedingJoinPoint; + + ProceedingContext(Extend extend, ProceedingJoinPoint proceedingJoinPoint) + { + super(); + this.extend = extend; + this.proceedingJoinPoint = proceedingJoinPoint; + } + + } + + private static final ThreadLocal> ajLocalProceedingJoinPoints = new ThreadLocal>() + { + protected java.util.Stack initialValue() + { + return new Stack<>(); + }; + }; + + public static interface ExtensionBypass + { + R run() throws Throwable; + } + + public static class CompiledExtensible + { + private Class extensible; + + private Map routedMethods = new HashMap<>(); + + private Map notRoutedMethods = new HashMap<>(); + + private List errors = new LinkedList<>(); + + public CompiledExtensible(Class extensible) + { + super(); + this.extensible = extensible; + } + + public Class getExtensible() + { + return this.extensible; + } + + public void add(AJExtensibleCompilingError error) + { + this.errors.add(error); + } + + public boolean hasErrors() + { + return !errors.isEmpty(); + } + + public String getErrorsString() + { + StringBuilder builder = new StringBuilder(); + + for (AJExtensibleCompilingError error : errors) + { + builder.append(error.getShortMessage()); + builder.append("\n"); + } + + return builder.toString(); + } + + public List getErrors() + { + return this.errors; + } + + public void add(ExtensionRoute route) + { + if (route.extensionMethod == null) + { + notRoutedMethods.remove(route.extendedMethod); + routedMethods.put(route.extendedMethod, + route); + } + else if (!routedMethods.containsKey(route.extendedMethod)) + { + routedMethods.put(route.extendedMethod, + route); + } + } + + public Collection getAllNotRouted() + { + return notRoutedMethods.values(); + } + + public int getExtendedMethodCount() + { + return routedMethods.size() + notRoutedMethods.size(); + } + + public String getInfo() + { + return extensible.getName() + "{ " + routedMethods.size() + " routed methods; " + notRoutedMethods.size() + + " not routed methods;" + errors.size() + " errors}"; + } + } + + public static class ExtensionRoute + { + final Extend extendAnnotation; + + final Method extendedMethod; + + final Method extensionMethod; + + ExtensionRoute(Extend extendAnnotation, Method traitMethod) + { + this(extendAnnotation, + traitMethod, + null); + } + + ExtensionRoute(Extend extendAnnotation, Method extendedMethod, Method extensionMethod) + { + super(); + ParameterCheck.mandatory("extendAnnotation", + extendAnnotation); + ParameterCheck.mandatory("traitMethod", + extendedMethod); + + this.extendAnnotation = extendAnnotation; + this.extendedMethod = extendedMethod; + this.extensionMethod = extensionMethod; + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof ExtensionRoute) + { + ExtensionRoute route = (ExtensionRoute) obj; + return extendAnnotation.traitAPI().equals(route.extendAnnotation.traitAPI()) + && extendAnnotation.extensionAPI().equals(route.extendAnnotation.extensionAPI()) + && extendedMethod.equals(route.extendedMethod) + && ((extensionMethod == null && route.extensionMethod == null) || (extensionMethod != null && extensionMethod + .equals(route.extensionMethod))); + } + else + { + return false; + } + } + + @Override + public String toString() + { + String extensionString = "NOT ROUTED"; + + if (extensionMethod != null) + { + Class exDeclClass = extendedMethod.getDeclaringClass(); + extensionString = extensionMethod.toGenericString() + "#" + exDeclClass; + } + + return extendAnnotation.toString() + "\t\n[" + extendedMethod.toGenericString() + " -> " + extensionString + + "]"; + } + + @Override + public int hashCode() + { + return extendAnnotation.hashCode(); + } + } + + public static boolean areAJPointsEnabled() + { + return ajPointsLocalEnabled.get().peek(); + } + + static void enableAJPoints() + { + ajPointsLocalEnabled.get().push(true); + } + + static void revertAJPoints() + { + ajPointsLocalEnabled.get().pop(); + } + + public static R throwableRun(AJExtender.ExtensionBypass section) throws Throwable + { + try + { + AJExtender.ajPointsLocalEnabled.get().push(false); + return section.run(); + + } + finally + { + AJExtender.ajPointsLocalEnabled.get().pop(); + } + } + + public static R run(AJExtender.ExtensionBypass section, Class[] exTypes) throws Throwable + { + try + { + return throwableRun(section); + } + catch (Error | RuntimeException error) + { + throw error; + } + catch (Throwable error) + { + throw asCheckThrowable(error, + exTypes); + } + } + + public static Throwable asCheckThrowable(Throwable error, Class... checkedThrowableTypes) + { + Class errorClass = error.getClass(); + for (int i = 0; i < checkedThrowableTypes.length; i++) + { + if (errorClass.equals(checkedThrowableTypes[i])) + { + return error; + } + } + return new UndeclaredThrowableException(error); + } + + public static R run(AJExtender.ExtensionBypass section) + { + try + { + return throwableRun(section); + } + catch (Error | RuntimeException error) + { + throw error; + } + catch (Throwable error) + { + throw new UndeclaredThrowableException(error); + } + } + + public static void oneTimeLiveLog(Log logger, ExtensionRoute route) + { + synchronized (AJExtender.class) + { + if (oneTimeLogSet == null) + { + oneTimeLogSet = new ConcurrentHashSet<>(); + } + } + + synchronized (oneTimeLogSet) + { + if (oneTimeLogSet.contains(route)) + { + return; + } + else + { + logger.debug(route.toString()); + oneTimeLogSet.add(route); + } + } + } + + public static CompiledExtensible compile(Class extensible) + throws AJExtensibleCompilingException + { + logger.info("Compiling extensible " + extensible); + + CompiledExtensible compiledExtensible = new CompiledExtensible(extensible); + + List methods = new ArrayList<>(); + Class extendDeclaring = extensible; + while (extendDeclaring != null) + { + Method[] declaredMethods = extendDeclaring.getDeclaredMethods(); + methods.addAll(Arrays.asList(declaredMethods)); + extendDeclaring = extendDeclaring.getSuperclass(); + } + Set extendDeclarations = new HashSet<>(); + Set routedExtensionMethods = new HashSet<>(); + for (Method method : methods) + { + + Extend extend = method.getAnnotation(Extend.class); + if (extend != null) + { + try + { + extendDeclarations.add(extend); + Class extensionAPI = extend.extensionAPI(); + Method extensionMethod = extensionAPI.getMethod(method.getName(), + method.getParameterTypes()); + compiledExtensible.add(new ExtensionRoute(extend, + method, + extensionMethod)); + routedExtensionMethods.add(extensionMethod); + } + catch (NoSuchMethodException error) + { + AJExtensibleCompilingException ajCompilingError = new AJExtensibleCompilingException("No route for " + + method.toGenericString() + + " @" + + extend, + error); + compiledExtensible.add(ajCompilingError); + } + catch (SecurityException error) + { + AJExtensibleCompilingException ajCompilingError = new AJExtensibleCompilingException("Access denined to route for " + + method.toGenericString() + + " @" + + extend, + error); + compiledExtensible.add(ajCompilingError); + } + } + + } + + final Set allObjectMethods = new HashSet<>(Arrays.asList(Object.class.getMethods())); + + for (Extend extend : extendDeclarations) + { + Class extension = extend.extensionAPI(); + + Set allExtensionMethods = new HashSet<>(Arrays.asList(extension.getMethods())); + allExtensionMethods.removeAll(allObjectMethods); + allExtensionMethods.removeAll(routedExtensionMethods); + if (!allExtensionMethods.isEmpty()) + { + for (Method method : allExtensionMethods) + { + compiledExtensible.add(new AJDanglingExtensionError(method, + extend)); + } + } + } + + logger.info(compiledExtensible.getInfo()); + + return compiledExtensible; + } + + public static Object extendAroundAdvice(JoinPoint thisJoinPoint, Extensible extensible, Extend extendAnnotation, + Object extension) + { + + MethodSignature ms = (MethodSignature) thisJoinPoint.getSignature(); + Method method = ms.getMethod(); + try + { + ajLocalProceedingJoinPoints.get().push(new ProceedingContext(extendAnnotation, + (ProceedingJoinPoint) thisJoinPoint)); + + Method extensionMethod = extension.getClass().getMethod(method.getName(), + method.getParameterTypes()); + if (logger.isDebugEnabled()) + { + oneTimeLiveLog(AJExtender.logger, + new ExtensionRoute(extendAnnotation, + method, + extensionMethod)); + } + + return extensionMethod.invoke(extension, + thisJoinPoint.getArgs()); + } + catch (IllegalAccessException error) + { + throw new InvalidExtension("Ivalid extension : " + error.getMessage(), + error); + } + catch (IllegalArgumentException error) + { + throw new InvalidExtension("Ivalid extension : " + error.getMessage(), + error); + } + catch (InvocationTargetException error) + { + Throwable targetException = error.getTargetException(); + if (targetException instanceof RuntimeException) + { + throw (RuntimeException) targetException; + } + else + { + throw new ExtensionTargetException(targetException); + } + } + catch (NoSuchMethodException error) + { + throw new InvalidExtension("Ivalid extension : " + error.getMessage(), + error); + } + catch (SecurityException error) + { + throw new InvalidExtension("Ivalid extension : " + error.getMessage(), + error); + } + finally + { + ajLocalProceedingJoinPoints.get().pop(); + } + + } + + public static boolean isLocalProceeder(Method method) + { + if (!ajLocalProceedingJoinPoints.get().isEmpty()) + { + ProceedingContext proceedingCotext = ajLocalProceedingJoinPoints.get().peek(); + MethodSignature ms = (MethodSignature) proceedingCotext.proceedingJoinPoint.getSignature(); + Method jpMethod = ms.getMethod(); + return jpMethod.getName().endsWith(method.getName()) && Arrays.equals(jpMethod.getParameterTypes(), + method.getParameterTypes()); + } + else + { + return false; + } + } + + public static Object localProceed(Object[] args) throws Throwable + { + ProceedingContext proceedingCotext = ajLocalProceedingJoinPoints.get().peek(); + Object[] safeArgs = args == null ? SAFE_NULL_ARGS : args; + return proceedingCotext.proceedingJoinPoint.proceed(safeArgs); + } +} diff --git a/source/java/org/alfresco/traitextender/AJExtensibleCompilingError.java b/source/java/org/alfresco/traitextender/AJExtensibleCompilingError.java new file mode 100644 index 0000000000..65b7ea29dc --- /dev/null +++ b/source/java/org/alfresco/traitextender/AJExtensibleCompilingError.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +public interface AJExtensibleCompilingError +{ + String getShortMessage(); +} diff --git a/source/java/org/alfresco/traitextender/AJExtensibleCompilingException.java b/source/java/org/alfresco/traitextender/AJExtensibleCompilingException.java new file mode 100644 index 0000000000..b450758bd8 --- /dev/null +++ b/source/java/org/alfresco/traitextender/AJExtensibleCompilingException.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +public class AJExtensibleCompilingException extends Exception implements AJExtensibleCompilingError +{ + + /** + * + */ + private static final long serialVersionUID = 1L; + + public AJExtensibleCompilingException() + { + super(); + } + + public AJExtensibleCompilingException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) + { + super(message, + cause, + enableSuppression, + writableStackTrace); + } + + public AJExtensibleCompilingException(String message, Throwable cause) + { + super(message, + cause); + } + + public AJExtensibleCompilingException(String message) + { + super(message); + } + + public AJExtensibleCompilingException(Throwable cause) + { + super(cause); + } + + @Override + public String getShortMessage() + { + return getMessage(); + } + +} diff --git a/source/java/org/alfresco/traitextender/AJProxyTrait.java b/source/java/org/alfresco/traitextender/AJProxyTrait.java new file mode 100644 index 0000000000..06291bdea0 --- /dev/null +++ b/source/java/org/alfresco/traitextender/AJProxyTrait.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.alfresco.traitextender.AJExtender.ExtensionBypass; +import org.alfresco.util.ParameterCheck; + +/** + * Java {@link Proxy} {@link InvocationHandler} to be used in conjuction with + * asprctJ extended traits.
+ * Method calls will be delegated to a given {@link Extensible} object method + * having the same signature within an {@link ExtensionBypass} context. + * + * @author Bogdan Horje + */ +public class AJProxyTrait implements InvocationHandler +{ + + /** + * {@link Trait} {@link Proxy} factory method. + * + * @param extensible the {@link Extensible} object that defines the given + * {@link Trait} + * @param traitAPI the trait interface part that the given + * {@link Extensible} object defines + * @return a {@link Proxy} object for the given trait API interface with an + * {@link AJProxyTrait} attached. All method calls performed on the + * returned proxy will be delegated to a given {@link Extensible} + * object method having the same signature within an + * {@link ExtensionBypass} context. + */ + @SuppressWarnings("unchecked") + public static M create(Object extensibleInterface, Class traitAPI) + { + return (M) Proxy.newProxyInstance(AJProxyTrait.class.getClassLoader(), + new Class[] { traitAPI }, + new AJProxyTrait(extensibleInterface)); + } + + private Object extensibleInterface; + + private AJProxyTrait(Object extensibleInterface) + { + ParameterCheck.mandatory("extensible", + extensibleInterface); + this.extensibleInterface = extensibleInterface; + } + + @Override + public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable + { + final Method traitMethod = extensibleInterface.getClass().getMethod(method.getName(), + method.getParameterTypes()); + + if (AJExtender.isLocalProceeder(method)) + { + return AJExtender.localProceed(args); + } + else + { + Class[] exTypes = traitMethod.getExceptionTypes(); + + return AJExtender.run(new AJExtender.ExtensionBypass() + { + @Override + public Object run() throws Throwable + { + try + { + return traitMethod.invoke(extensibleInterface, + args); + } + catch (IllegalAccessException error) + { + throw new InvalidExtension(error); + } + catch (IllegalArgumentException error) + { + throw new InvalidExtension(error); + } + catch (InvocationTargetException error) + { + Throwable targetException = error.getTargetException(); + throw targetException; + } + } + + }, + exTypes); + } + } + + @Override + public String toString() + { + return "AJAutoTrait@" + System.identityHashCode(this) + " of " + extensibleInterface.getClass().getSimpleName() + "@" + + System.identityHashCode(extensibleInterface); + } + +} diff --git a/source/java/org/alfresco/traitextender/Extend.java b/source/java/org/alfresco/traitextender/Extend.java new file mode 100644 index 0000000000..64f425536c --- /dev/null +++ b/source/java/org/alfresco/traitextender/Extend.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Extend +{ + Class extensionAPI(); + Class traitAPI(); +} diff --git a/source/java/org/alfresco/traitextender/ExtendedTrait.java b/source/java/org/alfresco/traitextender/ExtendedTrait.java new file mode 100644 index 0000000000..c701cffeac --- /dev/null +++ b/source/java/org/alfresco/traitextender/ExtendedTrait.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +import java.util.concurrent.ConcurrentHashMap; + +public class ExtendedTrait +{ + private ConcurrentHashMap, Object> extensions = new ConcurrentHashMap, Object>(); + + private T trait; + + public ExtendedTrait(T trait) + { + super(); + this.trait = trait; + } + + public T getTrait() + { + return trait; + } + + public E getExtension(Class extensionAPI) + { + @SuppressWarnings("unchecked") + E extension=(E) extensions.get(extensionAPI); + + return extension; + } + + public synchronized E extend(Class extensionAPI, ExtensionFactory factory) + { + @SuppressWarnings("unchecked") + E extension=(E) extensions.get(extensionAPI); + + if (extension==null) + { + extension = factory.createExtension(trait); + extensions.put(extensionAPI, extension); + + } + + return extension; + } +} diff --git a/source/java/org/alfresco/traitextender/Extender.java b/source/java/org/alfresco/traitextender/Extender.java new file mode 100644 index 0000000000..c9998533df --- /dev/null +++ b/source/java/org/alfresco/traitextender/Extender.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +import java.util.List; + + +public abstract class Extender +{ + private static Extender instance; + + public static class Configuration { + private boolean enabled; + private List disbaledBundles; + + + } + + public static synchronized Extender getInstance() + { + if (instance == null) + { + instance = new ExtenderImpl(); + } + return instance; + } + + public abstract void start(ExtensionBundle bundle); + + public abstract void stop(ExtensionBundle bundle); + + public abstract void stopAll(); + + public abstract E getExtension(Extensible anExtensible, ExtensionPoint point); + + public abstract void register(ExtensionPoint point, ExtensionFactory factory); + + public abstract void unregister(ExtensionPoint point); + +} diff --git a/source/java/org/alfresco/traitextender/ExtenderImpl.java b/source/java/org/alfresco/traitextender/ExtenderImpl.java new file mode 100644 index 0000000000..c0bed57a40 --- /dev/null +++ b/source/java/org/alfresco/traitextender/ExtenderImpl.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class ExtenderImpl extends Extender +{ + private static Log logger = LogFactory.getLog(Extender.class); + + + + private List bundles = new LinkedList(); + + private Map, ExtensionFactory> pointFactories = new ConcurrentHashMap, ExtensionFactory>(); + + public synchronized void start(ExtensionBundle bundle) + { + bundles.add(bundle); + bundle.start(this); + } + + public void stop(ExtensionBundle bundle) + { + bundle.stop(this); + bundles.remove(bundle); + } + + public synchronized void stopAll() + { + List bundlesCopy = new LinkedList<>(bundles); + for (ExtensionBundle bundle : bundlesCopy) + { + stop(bundle); + } + } + + public synchronized E getExtension(Extensible anExtensible, ExtensionPoint point) + { + E extension = null; + + //consistency is checked at registration time + @SuppressWarnings("unchecked") + ExtensionFactory factory = (ExtensionFactory) pointFactories.get(point); + + if (factory != null) + { + ExtendedTrait exTrait = anExtensible.getTrait(point.getTraitAPI()); + + extension = exTrait.getExtension(point.getExtensionAPI()); + + if (extension == null) + { + extension = exTrait.extend(point.getExtensionAPI(), + factory); + if (logger.isDebugEnabled()) + { + logger.debug("trait extension leak trace : " + System.identityHashCode(extension) + " : " + + System.identityHashCode(exTrait) + " : " + System.identityHashCode(extension)); + } + } + } + return extension; + } + + public synchronized void register(ExtensionPoint point, ExtensionFactory factory) + { + // point->factory type consistency checks + if (!factory.canCreateExtensionFor(point)) + { + throw new InvalidExtension("Invalid extension factory registry entry : " + point + "->" + factory); + } + pointFactories.put(point, + factory); + } + + public synchronized void unregister(ExtensionPoint point) + { + pointFactories.remove(point); + } + +} diff --git a/source/java/org/alfresco/traitextender/Extensible.java b/source/java/org/alfresco/traitextender/Extensible.java new file mode 100644 index 0000000000..d634cd6b7c --- /dev/null +++ b/source/java/org/alfresco/traitextender/Extensible.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + + +public interface Extensible +{ + ExtendedTrait getTrait(Class traitAPI); +} diff --git a/source/java/org/alfresco/traitextender/ExtensionBundle.java b/source/java/org/alfresco/traitextender/ExtensionBundle.java new file mode 100644 index 0000000000..c1e9622bb3 --- /dev/null +++ b/source/java/org/alfresco/traitextender/ExtensionBundle.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + + +public interface ExtensionBundle +{ + void start(Extender extender); + + void stop(Extender extender); + + String getId(); +} diff --git a/source/java/org/alfresco/traitextender/ExtensionFactory.java b/source/java/org/alfresco/traitextender/ExtensionFactory.java new file mode 100644 index 0000000000..9726b562e4 --- /dev/null +++ b/source/java/org/alfresco/traitextender/ExtensionFactory.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +public interface ExtensionFactory +{ + E createExtension(T trait); + + boolean canCreateExtensionFor(ExtensionPoint point); +} diff --git a/source/java/org/alfresco/traitextender/ExtensionPoint.java b/source/java/org/alfresco/traitextender/ExtensionPoint.java new file mode 100644 index 0000000000..18ab2ccd67 --- /dev/null +++ b/source/java/org/alfresco/traitextender/ExtensionPoint.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +public class ExtensionPoint +{ + private Class extensionAPI; + + private Class traitAPI; + + public ExtensionPoint(Class extensionAPI, Class traitAPI) + { + super(); + this.extensionAPI = extensionAPI; + this.traitAPI = traitAPI; + } + + public Class getExtensionAPI() + { + return this.extensionAPI; + } + + public Class getTraitAPI() + { + return this.traitAPI; + } + + @Override + public int hashCode() + { + return extensionAPI.hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof ExtensionPoint) + { + ExtensionPoint pointObj = (ExtensionPoint) obj; + return extensionAPI.equals(pointObj.extensionAPI) + && traitAPI.equals(pointObj.traitAPI); + } + else + { + return false; + } + } + + @Override + public String toString() + { + return "{" + extensionAPI.toString() + "," + traitAPI.toString() + "}"; + } +} diff --git a/source/java/org/alfresco/traitextender/ExtensionPointActivator.java b/source/java/org/alfresco/traitextender/ExtensionPointActivator.java new file mode 100644 index 0000000000..f4d445fc67 --- /dev/null +++ b/source/java/org/alfresco/traitextender/ExtensionPointActivator.java @@ -0,0 +1,21 @@ + +package org.alfresco.traitextender; + +public class ExtensionPointActivator +{ + private ExtensionBundle bundle; + + private ExtensionPoint extensionPoint; + + private ExtensionFactory extensionFactory; + + public ExtensionPointActivator(ExtensionBundle bundle, ExtensionPoint extensionPoint, + ExtensionFactory extensionFactory) + { + super(); + this.bundle = bundle; + this.extensionPoint = extensionPoint; + this.extensionFactory = extensionFactory; + } + +} diff --git a/source/java/org/alfresco/traitextender/ExtensionTargetException.java b/source/java/org/alfresco/traitextender/ExtensionTargetException.java new file mode 100644 index 0000000000..b6ead38069 --- /dev/null +++ b/source/java/org/alfresco/traitextender/ExtensionTargetException.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +public class ExtensionTargetException extends RuntimeException +{ + private static final long serialVersionUID = -502697833178766952L; + + public ExtensionTargetException() + { + super(); + } + + public ExtensionTargetException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) + { + super(message, cause, enableSuppression, writableStackTrace); + } + + public ExtensionTargetException(String message, Throwable cause) + { + super(message, cause); + } + + public ExtensionTargetException(String message) + { + super(message); + } + + public ExtensionTargetException(Throwable cause) + { + super(cause); + } + +} diff --git a/source/java/org/alfresco/traitextender/InstanceExtension.java b/source/java/org/alfresco/traitextender/InstanceExtension.java new file mode 100644 index 0000000000..2569a6eef6 --- /dev/null +++ b/source/java/org/alfresco/traitextender/InstanceExtension.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +public abstract class InstanceExtension +{ + protected T trait; + + public InstanceExtension(T trait) + { + super(); + this.trait = trait; + } + +} diff --git a/source/java/org/alfresco/traitextender/InstanceExtensionFactory.java b/source/java/org/alfresco/traitextender/InstanceExtensionFactory.java new file mode 100644 index 0000000000..b155976603 --- /dev/null +++ b/source/java/org/alfresco/traitextender/InstanceExtensionFactory.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +import java.lang.reflect.Constructor; + +public class InstanceExtensionFactory, T extends Trait, E> implements + ExtensionFactory +{ + private Class extensionClass; + + private Class traitAPI; + + public InstanceExtensionFactory(Class extensionClass, Class traitAPI, + Class extensionAPI) + { + super(); + this.extensionClass = extensionClass; + this.traitAPI = traitAPI; + + if (!extensionAPI.isAssignableFrom(extensionClass)) + { + throw new InvalidExtension("Extension class " + extensionClass + " is incompatible with extensio API " + + extensionAPI); + } + } + + @SuppressWarnings("unchecked") + @Override + public E createExtension(TO traitObject) + { + try + { + // Trait RTTI will be performed anyway at Constructor#newInstance invocation time + T tTrait = (T) traitObject; + + Constructor c = extensionClass.getConstructor(traitAPI); + return (E) c.newInstance(tTrait); + } + catch (Exception error) + { + throw new RuntimeException(error); + } + } + + @Override + public boolean canCreateExtensionFor(ExtensionPoint point) + { + return point.getExtensionAPI().isAssignableFrom(extensionClass) + && traitAPI.isAssignableFrom(point.getTraitAPI()); + } + +} diff --git a/source/java/org/alfresco/traitextender/InvalidExtension.java b/source/java/org/alfresco/traitextender/InvalidExtension.java new file mode 100644 index 0000000000..7d3f885d48 --- /dev/null +++ b/source/java/org/alfresco/traitextender/InvalidExtension.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +public class InvalidExtension extends RuntimeException +{ + private static final long serialVersionUID = -7146808120353555462L; + + public InvalidExtension() + { + super(); + } + + public InvalidExtension(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) + { + super(message, cause, enableSuppression, writableStackTrace); + } + + public InvalidExtension(String message, Throwable cause) + { + super(message, cause); + } + + public InvalidExtension(String message) + { + super(message); + } + + public InvalidExtension(Throwable cause) + { + super(cause); + } + +} diff --git a/source/java/org/alfresco/traitextender/RegistryExtensionBundle.java b/source/java/org/alfresco/traitextender/RegistryExtensionBundle.java new file mode 100644 index 0000000000..10df6c19e0 --- /dev/null +++ b/source/java/org/alfresco/traitextender/RegistryExtensionBundle.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.alfresco.util.ParameterCheck; + +/** + * {@link ExtensionBundle} that supports simple {@link ExtensionPoint} to + * {@link ExtensionFactory} association registration. + * + * @author Bogdan Horje + */ +public class RegistryExtensionBundle implements ExtensionBundle +{ + + private Map, ExtensionFactory> factories = new HashMap<>(); + + private String id; + + public RegistryExtensionBundle(String id) + { + ParameterCheck.mandatory("id", + id); + this.id = id; + } + + /** + * Registers an association between the given {@link ExtensionPoint} and + * {@link ExtensionFactory}.
+ * At {@link #start(Extender)} time all registered {@link ExtensionPoint}s + * will be registered with the given {@link Extender}.
+ * At {@link #stop(Extender)} time all registered {@link ExtensionPoint}s + * will be unregistered with the given {@link Extender}.
+ * + * @param point + * @param factory + */ + public void register(ExtensionPoint point, ExtensionFactory factory) + { + factories.put(point, + factory); + } + + @Override + public void start(Extender extender) + { + Set, ExtensionFactory>> factoryEntries = factories.entrySet(); + for (Entry, ExtensionFactory> entry : factoryEntries) + { + extender.register(entry.getKey(), + entry.getValue()); + } + } + + @Override + public void stop(Extender extender) + { + Set, ExtensionFactory>> factoryEntries = factories.entrySet(); + for (Entry, ExtensionFactory> entry : factoryEntries) + { + extender.unregister(entry.getKey()); + } + } + + @Override + public String getId() + { + return id; + } + +} diff --git a/source/java/org/alfresco/traitextender/RouteExtensions.java b/source/java/org/alfresco/traitextender/RouteExtensions.java new file mode 100644 index 0000000000..5570f24c94 --- /dev/null +++ b/source/java/org/alfresco/traitextender/RouteExtensions.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +import java.lang.reflect.Method; + +import org.alfresco.traitextender.AJExtender.ExtensionRoute; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; + +@Aspect +public class RouteExtensions +{ + private static Log logger = LogFactory.getLog(RouteExtensions.class); + + @Around("execution(@org.alfresco.traitextender.Extend * *(..)) && (@annotation(extendAnnotation))") + public Object intercept(ProceedingJoinPoint pjp, Extend extendAnnotation) throws Throwable { + boolean ajPointsEnabled = AJExtender.areAJPointsEnabled(); + try + { + AJExtender.enableAJPoints(); + if (ajPointsEnabled) + { + Object extensibleObject = pjp.getThis(); + if (!(extensibleObject instanceof Extensible)) + { + throw new InvalidExtension("Invalid extension point for non extensible class : " + + extensibleObject.getClass()); + } + Extensible extensible = (Extensible) extensibleObject; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + ExtensionPoint point = new ExtensionPoint(extendAnnotation.extensionAPI(), + extendAnnotation.traitAPI()); + @SuppressWarnings("unchecked") + Object extension = Extender.getInstance().getExtension(extensible, + point); + if (extension != null) + { + + return AJExtender.extendAroundAdvice(pjp, + extensible, + extendAnnotation, + extension); + } + else if (logger.isDebugEnabled()) + { + MethodSignature ms = (MethodSignature) pjp.getSignature(); + Method traitMethod = ms.getMethod(); + + AJExtender.oneTimeLiveLog(logger, + new ExtensionRoute(extendAnnotation, + traitMethod)); + } + } + return pjp.proceed(); + } + finally + { + AJExtender.revertAJPoints(); + } + } + +} diff --git a/source/java/org/alfresco/traitextender/SingletonExtension.java b/source/java/org/alfresco/traitextender/SingletonExtension.java new file mode 100644 index 0000000000..b15f618893 --- /dev/null +++ b/source/java/org/alfresco/traitextender/SingletonExtension.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +public abstract class SingletonExtension +{ + private ThreadLocal localTrait = new ThreadLocal<>(); + + private Class traitClass; + + public SingletonExtension(Class traitClass) + { + super(); + this.traitClass = traitClass; + } + + public boolean acceptsTrait(Object trait) + { + return trait != null && acceptsTraitClass(trait.getClass()); + } + + public boolean acceptsTraitClass(Class aTraitClass) + { + return traitClass.isAssignableFrom(aTraitClass); + } + + void setTrait(T trait) + { + localTrait.set(trait); + } + + protected T getTrait() + { + return localTrait.get(); + } + +} diff --git a/source/java/org/alfresco/traitextender/SingletonExtensionFactory.java b/source/java/org/alfresco/traitextender/SingletonExtensionFactory.java new file mode 100644 index 0000000000..903d75f99d --- /dev/null +++ b/source/java/org/alfresco/traitextender/SingletonExtensionFactory.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class SingletonExtensionFactory, T extends Trait> implements + ExtensionFactory +{ + private Log logger = LogFactory.getLog(SingletonExtensionFactory.class); + + private class TraiSingletontHandler implements InvocationHandler + { + private S singleton; + + private T trait; + + public TraiSingletontHandler(S singleton, T trait) + { + super(); + this.singleton = singleton; + this.trait = trait; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + // Subsequent trait calls might change the trait on this thread. + // We save the current trait on the current call-stack in order + // to restore it on the finally block. + + T stackedTrait = this.singleton.getTrait(); + + this.singleton.setTrait(trait); + try + { + return method.invoke(this.singleton, + args); + } + catch (IllegalAccessException error) + { + throw new InvalidExtension(error); + } + catch (IllegalArgumentException error) + { + throw new InvalidExtension(error); + } + catch (InvocationTargetException error) + { + Throwable targetException = error.getTargetException(); + throw targetException; + } + finally + { + this.singleton.setTrait(stackedTrait); + } + } + + } + + private S singleton; + + private Class extensionAPI; + + public SingletonExtensionFactory(S singleton, Class extensionAPI) throws InvalidExtension + { + super(); + if (!extensionAPI.isAssignableFrom(singleton.getClass())) + { + throw new InvalidExtension(singleton.getClass() + " is not an " + extensionAPI + " extension."); + } + this.singleton = singleton; + this.extensionAPI = extensionAPI; + + } + + @SuppressWarnings("unchecked") + @Override + public E createExtension(TO traitObject) + { + T tTrait = (T) traitObject; + // perform programmatic RTTI + if (!singleton.acceptsTrait(traitObject)) + { + throw new InvalidExtension("Extension factory error : " + singleton.getClass() + " does not support trait " + + traitObject); + } + + if (logger.isDebugEnabled()) + { + String traitTrace = traitObject != null ? traitObject.getClass().toString() : " trait"; + logger.info("Creating singleton extension " + singleton.getClass() + "<-" + traitTrace); + } + else + { + logger.info("Creating singleton extension " + singleton.getClass()); + } + + return (E) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] { extensionAPI }, + new TraiSingletontHandler(singleton, + tTrait)); + } + + @Override + public boolean canCreateExtensionFor(ExtensionPoint point) + { + return point.getExtensionAPI().isAssignableFrom(singleton.getClass()) + && singleton.acceptsTraitClass(point.getTraitAPI()); + } + +} diff --git a/source/java/org/alfresco/traitextender/SpringBeanExtension.java b/source/java/org/alfresco/traitextender/SpringBeanExtension.java new file mode 100644 index 0000000000..32c5695708 --- /dev/null +++ b/source/java/org/alfresco/traitextender/SpringBeanExtension.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +public abstract class SpringBeanExtension extends SingletonExtension +{ + private SpringExtensionPoint extensionPoint; + + public SpringBeanExtension(Class traitClass) + { + super(traitClass); + } + + public void setExtensionPoint(SpringExtensionPoint extensionPoint) + { + this.extensionPoint = extensionPoint; + } + + public void register(RegistryExtensionBundle bundle) throws InvalidExtension + { + extensionPoint.register(bundle,this); + } +} diff --git a/source/java/org/alfresco/traitextender/SpringExtensionBundle.java b/source/java/org/alfresco/traitextender/SpringExtensionBundle.java new file mode 100644 index 0000000000..278bff7fbf --- /dev/null +++ b/source/java/org/alfresco/traitextender/SpringExtensionBundle.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +public class SpringExtensionBundle implements InitializingBean +{ + private static Log logger = LogFactory.getLog(SpringExtensionBundle.class); + + private List> extensions = Collections.emptyList(); + + private String id; + + private boolean enabled=true; + + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + public void setExtensions(List> extensions) + { + this.extensions = extensions; + } + + public void setId(String id) + { + this.id = id; + } + + @Override + public void afterPropertiesSet() throws Exception + { + if (this.enabled) + { + logger.info("Starting extension bundle " + id); + + RegistryExtensionBundle extensionBundle = new RegistryExtensionBundle(id); + + for (SpringBeanExtension springExtension : extensions) + { + try + { + springExtension.register(extensionBundle); + } + catch (Exception error) + { + throw new InvalidExtension("Could not register extension " + springExtension + " with " + + extensionBundle, + error); + } + + } + + Extender.getInstance().start(extensionBundle); + } + else + { + logger.info("Extension bundle " + id + " is disabled."); + } + } + +} diff --git a/source/java/org/alfresco/traitextender/SpringExtensionPoint.java b/source/java/org/alfresco/traitextender/SpringExtensionPoint.java new file mode 100644 index 0000000000..c1573b3ce8 --- /dev/null +++ b/source/java/org/alfresco/traitextender/SpringExtensionPoint.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ + +package org.alfresco.traitextender; + +public class SpringExtensionPoint +{ + private String trait; + + private String extension; + + public void setTrait(String trait) + { + this.trait = trait; + } + + public void setExtension(String extension) + { + this.extension = extension; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void register(RegistryExtensionBundle bundle, SpringBeanExtension extensionBean) + throws InvalidExtension + { + + try + { + Class extensionAPI = Class.forName(extension); + Class traitAPI = (Class) Class.forName(trait); + + // perform RTTIs in order to avoid later cast exceptions + + if (!Trait.class.isAssignableFrom(traitAPI)) + { + throw new InvalidExtension("Non " + Trait.class + " spring extension point : " + traitAPI); + } + + if (!extensionBean.acceptsTraitClass(traitAPI)) + { + throw new InvalidExtension("Unsupported trait class : " + traitAPI + " in extension point targeting " + + extensionBean); + } + + bundle.register(new ExtensionPoint(extensionAPI, + traitAPI), + new SingletonExtensionFactory(extensionBean, + extensionAPI)); + + } + catch (InvalidExtension error) + { + throw error; + } + catch (ClassNotFoundException error) + { + throw new InvalidExtension("Extension point definition class not found.", + error); + } + + } +} diff --git a/source/java/org/alfresco/traitextender/SpringTraitExtenderConfigurator.java b/source/java/org/alfresco/traitextender/SpringTraitExtenderConfigurator.java new file mode 100644 index 0000000000..8a1523d909 --- /dev/null +++ b/source/java/org/alfresco/traitextender/SpringTraitExtenderConfigurator.java @@ -0,0 +1,15 @@ +package org.alfresco.traitextender; + +import org.springframework.beans.factory.InitializingBean; + +public class SpringTraitExtenderConfigurator implements InitializingBean +{ + + private boolean enableTraitExtender; + + @Override + public void afterPropertiesSet() throws Exception + { + } + +} diff --git a/source/java/org/alfresco/traitextender/Trait.java b/source/java/org/alfresco/traitextender/Trait.java new file mode 100644 index 0000000000..cca5f18151 --- /dev/null +++ b/source/java/org/alfresco/traitextender/Trait.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see http://www.gnu.org/licenses/. + */ +package org.alfresco.traitextender; + +public interface Trait +{ + +}