This is an automated email from the ASF dual-hosted git repository. gk pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/turbine-core.git
commit 84d70b620c521127965d06ab7a073f2cc46c1735 Author: Georg Kallidis <[email protected]> AuthorDate: Tue Nov 14 13:00:39 2023 +0100 Turbine Services annotation: allow class level declaration as an alternative to set the service identifier and the ability to 'inherit' turbine services in fields and methods. Add ready to use Field- and MethodAnnotatedTurbineBaseService to allow for referencing dependent Turbine Services. Remove role field in DateTimeFormatterService, which might result in wrong service name (in AnnotationProcessor checkServiceOrRoleInField method). Test: Adding three level dependency Turbine Service classes: VelocityActionWithExtendedServiceInjection -> ServiceWithServiceInjection -> ServiceWithServiceInjection2. Added test class VelocityActionWithExtendedServiceInjection with field ServiceWithServiceInjection. Adapt test configurations. --- conf/test/CompleteTurbineResources.properties | 8 ++ conf/test/fulcrumComponentConfiguration.xml | 2 + conf/test/fulcrumRoleConfiguration.xml | 6 ++ .../turbine/annotation/AnnotationProcessor.java | 63 ++++++++++++++-- .../apache/turbine/annotation/TurbineService.java | 6 +- .../services/FieldAnnotatedTurbineBaseService.java | 56 ++++++++++++++ .../MethodAnnotatedTurbineBaseService.java | 57 ++++++++++++++ .../localization/DateTimeFormatterService.java | 9 +-- .../apache/turbine/modules/ActionLoaderTest.java | 21 ++++++ ...VelocityActionWithExtendedServiceInjection.java | 61 +++++++++++++++ .../services/ServiceWithServiceInjection.java | 88 ++++++++++++++++++++++ .../services/ServiceWithServiceInjection2.java | 61 +++++++++++++++ 12 files changed, 426 insertions(+), 12 deletions(-) diff --git a/conf/test/CompleteTurbineResources.properties b/conf/test/CompleteTurbineResources.properties index c8cbc016..4df93ad8 100644 --- a/conf/test/CompleteTurbineResources.properties +++ b/conf/test/CompleteTurbineResources.properties @@ -679,3 +679,11 @@ services.URLMapperService.classname=org.apache.turbine.services.urlmapper.Turbin #tool.request.mlink=org.apache.turbine.services.urlmapper.MappedTemplateLink services.URLMapperService.configFile = conf/turbine-url-mapping.xml + +# +# +# Additional Services +# +# +services.ServiceWithService.classname=org.apache.turbine.services.ServiceWithServiceInjection +services.ServiceWithService2.classname=org.apache.turbine.services.ServiceWithServiceInjection2 diff --git a/conf/test/fulcrumComponentConfiguration.xml b/conf/test/fulcrumComponentConfiguration.xml index c45ee5bf..30003e01 100644 --- a/conf/test/fulcrumComponentConfiguration.xml +++ b/conf/test/fulcrumComponentConfiguration.xml @@ -110,5 +110,7 @@ </properties> </configuration> </quartz> + + <serviceWithServiceInjection/> </componentConfig> diff --git a/conf/test/fulcrumRoleConfiguration.xml b/conf/test/fulcrumRoleConfiguration.xml index 531c364c..dd2468f9 100644 --- a/conf/test/fulcrumRoleConfiguration.xml +++ b/conf/test/fulcrumRoleConfiguration.xml @@ -121,6 +121,12 @@ name="org.apache.fulcrum.security.model.ACLFactory" shorthand="aclFactory" default-class="org.apache.fulcrum.security.model.turbine.TurbineACLFactory"/> + + <role + name="org.apache.turbine.services.ServiceWithServiceInjection" + shorthand="serviceWithServiceInjection" + default-class="org.apache.turbine.services.ServiceWithServiceInjection" + /> </role-list> diff --git a/src/java/org/apache/turbine/annotation/AnnotationProcessor.java b/src/java/org/apache/turbine/annotation/AnnotationProcessor.java index 8979f51f..ad5ad9f6 100644 --- a/src/java/org/apache/turbine/annotation/AnnotationProcessor.java +++ b/src/java/org/apache/turbine/annotation/AnnotationProcessor.java @@ -37,6 +37,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.turbine.Turbine; import org.apache.turbine.modules.Loader; +import org.apache.turbine.services.Service; import org.apache.turbine.services.ServiceManager; import org.apache.turbine.services.TurbineServices; import org.apache.turbine.services.assemblerbroker.AssemblerBrokerService; @@ -249,6 +250,13 @@ public class AnnotationProcessor AssemblerBrokerService assembler = null; PoolService pool= null; Class<?> clazz = object.getClass(); + + boolean isTurbineService = false; + if ( clazz.isAnnotationPresent(TurbineService.class)) { + TurbineService service = clazz.getAnnotation(TurbineService.class); + log.debug("retrieved class annotation: "+ service); + isTurbineService = true; + } while (clazz != null) { @@ -295,17 +303,29 @@ public class AnnotationProcessor injectTurbineTool(object, pool, field, (TurbineTool) a); } } + if (isTurbineService) + { + if (field.getType().isAnnotationPresent(TurbineService.class)) { + TurbineService service = field.getType().getAnnotation(TurbineService.class); + log.debug("retrieved implicit class annotation: "+ service); + if (manager == null) + { + manager = TurbineServices.getInstance(); + } + injectTurbineService(object, manager, field, service); + } + } } if (hasTurbineServicesInMethodFields) { - manager = processMethods(object, manager, clazz); + manager = processMethods(object, manager, clazz, isTurbineService); } clazz = clazz.getSuperclass(); } } - private static ServiceManager processMethods(Object object, ServiceManager manager, Class<?> clazz) throws TurbineException { + private static ServiceManager processMethods(Object object, ServiceManager manager, Class<?> clazz, boolean isTurbineService) throws TurbineException { Method[] methods = clazz.getMethods(); for (Method method : methods) @@ -323,6 +343,23 @@ public class AnnotationProcessor injectTurbineService(object, manager, method, (TurbineService) a); } } + if (isTurbineService) + { + if (manager == null) + { + manager = TurbineServices.getInstance(); + } + Class<?>[] classes = method.getParameterTypes(); + for (Class<?> c : classes) + { + if ( c.isAnnotationPresent(TurbineService.class)) { + TurbineService service = c.getAnnotation(TurbineService.class); + log.debug("retrieved implicit service in Turbien service: "+ service); + injectTurbineService(object, manager, method, service); + } + + } + } } return manager; } @@ -538,15 +575,23 @@ public class AnnotationProcessor { String serviceName = null; // Check for annotation value - if (StringUtils.isNotEmpty(annotation.value())) + if (annotation != null && StringUtils.isNotEmpty(annotation.value())) { serviceName = annotation.value(); } // Check for fields SERVICE_NAME and ROLE else - { + { + // check field level annotation Field[] typeFields = field.getType().getFields(); serviceName = checkServiceOrRoleInField(serviceName, typeFields); + // if it is the default Service, we check class level annotation + if ( (serviceName == null || serviceName.equals(Service.SERVICE_NAME)) && + field.getType().isAnnotationPresent(TurbineService.class)) { + TurbineService service = field.getType().getAnnotation(TurbineService.class); + log.debug("retrieved class annotation: "+ service); + serviceName = service.value(); + } } if (StringUtils.isEmpty(serviceName)) @@ -586,7 +631,7 @@ public class AnnotationProcessor { String serviceName = null; // Check for annotation value - if (StringUtils.isNotEmpty(annotation.value())) + if (annotation != null && StringUtils.isNotEmpty(annotation.value())) { serviceName = annotation.value(); } @@ -598,6 +643,14 @@ public class AnnotationProcessor Field[] fields = c.getFields(); // Check for fields SERVICE_NAME and ROLE serviceName = checkServiceOrRoleInField(serviceName, fields); + + if ( (serviceName == null || serviceName.equals(Service.SERVICE_NAME)) && + c.isAnnotationPresent(TurbineService.class)) { + TurbineService service = c.getAnnotation(TurbineService.class); + log.debug("retrieved class annotation: "+ service); + serviceName = service.value(); + } + } } diff --git a/src/java/org/apache/turbine/annotation/TurbineService.java b/src/java/org/apache/turbine/annotation/TurbineService.java index 453d8b27..e9d653d2 100644 --- a/src/java/org/apache/turbine/annotation/TurbineService.java +++ b/src/java/org/apache/turbine/annotation/TurbineService.java @@ -26,10 +26,12 @@ import java.lang.annotation.Target; /** - * Annotation to mark fields in modules that require a service to be injected + * Annotation to mark class and fields in modules that require a service to be injected + * + * Explicit field annotation of {@link #SERVICE_NAME} will take precedence of class annotation. */ @Retention( RetentionPolicy.RUNTIME ) -@Target( {ElementType.FIELD, ElementType.METHOD} ) +@Target( {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD} ) public @interface TurbineService { /** diff --git a/src/java/org/apache/turbine/services/FieldAnnotatedTurbineBaseService.java b/src/java/org/apache/turbine/services/FieldAnnotatedTurbineBaseService.java new file mode 100644 index 00000000..11248d3f --- /dev/null +++ b/src/java/org/apache/turbine/services/FieldAnnotatedTurbineBaseService.java @@ -0,0 +1,56 @@ +package org.apache.turbine.services; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.turbine.annotation.AnnotationProcessor; +import org.apache.turbine.util.TurbineException; + +/** + * <p>This class provides a <code>Service</code> implementation that + * Services used in Turbine are required to extend. + * This class provides the ability to process field annotation {@link TurbineServices} in a Turbine service. + * </p> + * + */ +public abstract class FieldAnnotatedTurbineBaseService + extends TurbineBaseService +{ + + /** + * Performs late initialization. + * + * If your class relies on early initialization, and the object it + * expects was not received, you can use late initialization to + * throw an exception and complain. + * + * @throws InitializationException if initialization of this + * class was not successful. + */ + @Override + public void init() throws InitializationException + { + try { + AnnotationProcessor.process(this, false); + } catch (TurbineException e) { + throw new InitializationException(e.getMessage(), e); + } + setInit(true); + } +} diff --git a/src/java/org/apache/turbine/services/MethodAnnotatedTurbineBaseService.java b/src/java/org/apache/turbine/services/MethodAnnotatedTurbineBaseService.java new file mode 100644 index 00000000..5fcd9212 --- /dev/null +++ b/src/java/org/apache/turbine/services/MethodAnnotatedTurbineBaseService.java @@ -0,0 +1,57 @@ +package org.apache.turbine.services; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.turbine.annotation.AnnotationProcessor; +import org.apache.turbine.util.TurbineException; + +/** + * <p>This class provides a <code>Service</code> implementation that + * Services used in Turbine are required to extend. + * This class provides the ability to process field and method annotations {@link TurbineServices} in a Turbine service. + * </p> + * + */ +public abstract class MethodAnnotatedTurbineBaseService + extends TurbineBaseService +{ + + /** + * Performs late initialization. + * + * If your class relies on early initialization, and the object it + * expects was not received, you can use late initialization to + * throw an exception and complain. + * + * @throws InitializationException if initialization of this + * class was not successful. + */ + @Override + public void init() throws InitializationException + { + setInit(true); + try { + // if second parameter is true, we get into an endless loop if setInit is done last + AnnotationProcessor.process(this, true); + } catch (TurbineException e) { + throw new InitializationException(e.getMessage(), e); + } + } +} diff --git a/src/java/org/apache/turbine/services/localization/DateTimeFormatterService.java b/src/java/org/apache/turbine/services/localization/DateTimeFormatterService.java index 9733be0b..33659901 100644 --- a/src/java/org/apache/turbine/services/localization/DateTimeFormatterService.java +++ b/src/java/org/apache/turbine/services/localization/DateTimeFormatterService.java @@ -11,6 +11,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.turbine.Turbine; +import org.apache.turbine.annotation.TurbineService; import org.apache.turbine.services.TurbineBaseService; import org.apache.turbine.util.LocaleUtils; @@ -24,12 +25,10 @@ import org.apache.turbine.util.LocaleUtils; * if the source and the target format do not match appropriately. * */ +@TurbineService("DateTimeFormatterService") public class DateTimeFormatterService - extends TurbineBaseService implements DateTimeFormatterInterface { - - public static final String SERVICE_NAME = "DateTimeFormatterService"; - - public static final String ROLE = DateTimeFormatterService.class.getName(); + extends TurbineBaseService implements DateTimeFormatterInterface +{ private String formatPattern = null; diff --git a/src/test/org/apache/turbine/modules/ActionLoaderTest.java b/src/test/org/apache/turbine/modules/ActionLoaderTest.java index 3f06d316..fc5a4be6 100644 --- a/src/test/org/apache/turbine/modules/ActionLoaderTest.java +++ b/src/test/org/apache/turbine/modules/ActionLoaderTest.java @@ -332,4 +332,25 @@ public class ActionLoaderTest extends BaseTestCase fail("Should not have thrown an exception."); } } + + @Test + public void testDoPerformWithExtendedServiceInjection() throws Exception + { + RunData data = getRunData(request, response, config); + PipelineData pipelineData = data; + data.setAction("VelocityActionWithExtendedServiceInjection"); + + try + { + ActionLoader.getInstance().exec(pipelineData, data.getAction()); + Context context = (Context) + data.getTemplateInfo().getTemplateContext(VelocityService.CONTEXT); + assertTrue( context.get( "mykey" ) != null ); + } + catch (Exception e) + { + e.printStackTrace(); + fail("Should not have thrown an exception."); + } + } } diff --git a/src/test/org/apache/turbine/modules/actions/VelocityActionWithExtendedServiceInjection.java b/src/test/org/apache/turbine/modules/actions/VelocityActionWithExtendedServiceInjection.java new file mode 100644 index 00000000..0e3700cb --- /dev/null +++ b/src/test/org/apache/turbine/modules/actions/VelocityActionWithExtendedServiceInjection.java @@ -0,0 +1,61 @@ +package org.apache.turbine.modules.actions; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +import static org.junit.Assert.assertNotNull; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.turbine.annotation.TurbineService; +import org.apache.turbine.pipeline.PipelineData; +import org.apache.turbine.services.ServiceWithServiceInjection; +import org.apache.velocity.context.Context; + +/** + * Annnotating even an assembler as TurbineService on class level we could omit annotations for dependent Turbine services. + */ +@TurbineService +public class VelocityActionWithExtendedServiceInjection extends VelocityAction +{ + private static Log log = LogFactory.getLog(VelocityActionWithExtendedServiceInjection.class); + + // Test for class level SERVICE_NAME in ServiceWithServiceInjection + // Annotation could be omitted as the class is annotated + // @TurbineService + private ServiceWithServiceInjection serviceWithServiceInjection; + + + /** + * Default action is nothing. + * + * @param pipelineData Current RunData information + * @param context Context to populate + * @throws Exception Thrown on error + */ + @Override + public void doPerform(PipelineData pipelineData, Context context) throws Exception + { + log.debug("Calling doPerform(PipelineData)"); + assertNotNull("field injected serviceWithServiceInjection object was Null.", serviceWithServiceInjection); + serviceWithServiceInjection.callService(); + context.put("mykey","x"); + } +} diff --git a/src/test/org/apache/turbine/services/ServiceWithServiceInjection.java b/src/test/org/apache/turbine/services/ServiceWithServiceInjection.java new file mode 100644 index 00000000..5ab0b64d --- /dev/null +++ b/src/test/org/apache/turbine/services/ServiceWithServiceInjection.java @@ -0,0 +1,88 @@ +package org.apache.turbine.services; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +import static org.junit.Assert.assertNotNull; + +import org.apache.avalon.framework.activity.Initializable; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.fulcrum.localization.LocalizationService; +import org.apache.turbine.annotation.TurbineService; +/** + * This service is used for testing injection of services in fields and methods and class level declaration of + * {@link TurbineService} without interface. + * + * @author <a href="mailto:[email protected]">Georg Kallidis</a> + */ +@TurbineService( ServiceWithServiceInjection.SERVICE_NAME ) +public class ServiceWithServiceInjection extends MethodAnnotatedTurbineBaseService implements Initializable /* ServiceWithService */ +{ + + String ROLE = ServiceWithServiceInjection.class.getName(); + + static final String SERVICE_NAME = "ServiceWithService"; + + private static Log log = LogFactory.getLog(ServiceWithServiceInjection.class); + + // Test for implicit SERVICE_NAME + // we need the declaration as this is not by default a Turbine Service + @TurbineService + private LocalizationService localizationService; + + static private ServiceWithServiceInjection2 serviceWithServiceInjection2; + + + // Test for method injected class level SERVICE_NAME + // Annotation could be omitted as the class is annotated + // @TurbineService + public void setServiceWithServiceInjection2(ServiceWithServiceInjection2 serviceWithServiceInjection) { + serviceWithServiceInjection2 = serviceWithServiceInjection; + } + + /** + * Initializes the service. + */ + @Override + public void initialize() throws Exception + { + log.debug("Calling initializable()"); + // do not call AnnotationProcessor.process(this); here as it will result in an endless looping; + } + + /** + * Initializes the service. + */ + @Override + public void init() throws InitializationException + { + super.init(); + log.info("localizationService is: " + localizationService); +// setInit(true); + } + + public void callService() + { + assertNotNull("field injected localizationService object was Null.", localizationService); + assertNotNull("method injected service serviceWithServiceInjection2 object was Null.", serviceWithServiceInjection2); + ServiceWithServiceInjection.serviceWithServiceInjection2.callService(); + } +} diff --git a/src/test/org/apache/turbine/services/ServiceWithServiceInjection2.java b/src/test/org/apache/turbine/services/ServiceWithServiceInjection2.java new file mode 100644 index 00000000..1e5b96f9 --- /dev/null +++ b/src/test/org/apache/turbine/services/ServiceWithServiceInjection2.java @@ -0,0 +1,61 @@ +package org.apache.turbine.services; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static org.junit.Assert.assertNotNull; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.fulcrum.localization.LocalizationService; +import org.apache.turbine.annotation.TurbineService; +/** + * This service is used for testing 2nd level injection of services and class level declaration of + * {@link TurbineService} (interface is optional). + * + * @author <a href="mailto:[email protected]">Georg Kallidis</a> + */ +@TurbineService( ServiceWithServiceInjection2.SERVICE_NAME ) +public class ServiceWithServiceInjection2 extends FieldAnnotatedTurbineBaseService +{ + + static final String SERVICE_NAME = "ServiceWithService2"; + + private static Log log = LogFactory.getLog(ServiceWithServiceInjection2.class); + + // Test for implicit SERVICE_NAME + @TurbineService + private LocalizationService localizationService2; + + /** + * Initializes the service. + */ + @Override + public void init() throws InitializationException + { + super.init(); + log.info("localizationService2 is: " + localizationService2); +// setInit(true); + } + + public void callService() + { + assertNotNull("localizationService2 object was Null.", localizationService2); + } +}
