Skip to content

Commit

Permalink
coherence-communitygh-119 Fix Spring Framework 6.0.3 breaks Coherence…
Browse files Browse the repository at this point in the history
… annotation support

- Provide default ValueExtractors via ExtractorService
- Provide default Filters via FilterService
- Add custom logic to retrieve custom + annotated Filters & Extractors from the ApplicationContext
- Refactor common code into CoherenceAnnotationUtils
- All tests pass unchanged
  • Loading branch information
ghillert committed Mar 31, 2023
1 parent 79f49c9 commit 952b7f1
Show file tree
Hide file tree
Showing 11 changed files with 388 additions and 266 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2021, Oracle and/or its affiliates.
* Copyright (c) 2013, 2023, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
Expand Down Expand Up @@ -48,9 +48,6 @@
@Retention(RetentionPolicy.RUNTIME)
@interface Extractors {

// Dummy field - without it, the annotation does not work with Spring
String can_be_anything() default "";

/**
* An array of {@link ChainedExtractor}s.
*
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2021, Oracle and/or its affiliates.
* Copyright (c) 2013, 2023, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
Expand Down Expand Up @@ -73,9 +73,6 @@
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface Extractors {

// Dummy field - without it, the annotation does not work with Spring
String can_be_anything() default "";
PofExtractor[] value();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2021, Oracle and/or its affiliates.
* Copyright (c) 2013, 2023, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
Expand All @@ -26,7 +26,7 @@
@Repeatable(PropertyExtractor.Extractors.class)
public @interface PropertyExtractor {
/**
* Returns the a method or property name to use when creating a {@link
* Returns a method or property name to use when creating a {@link
* com.tangosol.util.extractor.UniversalExtractor}.
* <p>
* If the value does not end in {@code "()"} the value is assumed to be a
Expand All @@ -48,10 +48,6 @@
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface Extractors {

// Dummy field - without it, the annotation does not work with Spring
String can_be_anything() default "";

PropertyExtractor[] value();
}
}
Expand Up @@ -6,13 +6,6 @@
*/
package com.oracle.coherence.spring.configuration;

import java.util.Arrays;

import com.oracle.coherence.spring.annotation.ChainedExtractor;
import com.oracle.coherence.spring.annotation.ExtractorFactory;
import com.oracle.coherence.spring.annotation.PofExtractor;
import com.oracle.coherence.spring.annotation.PropertyExtractor;
import com.tangosol.util.Extractors;
import com.tangosol.util.ValueExtractor;
import jakarta.inject.Inject;

Expand Down Expand Up @@ -49,129 +42,4 @@ public class ExtractorConfiguration {
public ValueExtractor<?, ?> getExtractor(InjectionPoint injectionPoint) {
return this.extractorService.getExtractor(injectionPoint);
}

/**
* A {@link ExtractorFactory} that produces {@link ValueExtractor}
* instances for a given property or method name.
* @return {@link ExtractorFactory} that produces an instance of an
* {@link ValueExtractor} for a given property or method name.
*/
@Bean
@PropertyExtractor("")
ExtractorFactory<PropertyExtractor, Object, Object> universalExtractor() {
return (annotation) -> Extractors.extract(annotation.value());
}

/**
* A {@link ExtractorFactory} that produces {@link
* com.tangosol.util.extractor.MultiExtractor} containing {@link
* ValueExtractor} instances produced from the annotations contained in a
* {@link PropertyExtractor.Extractors} annotation.
* @return a {@link ExtractorFactory} that produces {@link
* com.tangosol.util.extractor.MultiExtractor} containing {@link
* ValueExtractor} instances produced from the annotations contained in a
* {@link PropertyExtractor.Extractors} annotation.
*/
@Bean
@PropertyExtractor.Extractors({})
@SuppressWarnings({"unchecked", "rawtypes"})
ExtractorFactory<PropertyExtractor.Extractors, ?, ?> universalExtractors() {
return (annotation) -> {
ValueExtractor[] extractors = Arrays.stream(annotation.value())
.map((ann) -> Extractors.extract(ann.value()))
.toArray(ValueExtractor[]::new);
return Extractors.multi(extractors);
};
}

/**
* A {@link ExtractorFactory} that produces chained {@link
* ValueExtractor} instances for an array of property or method names.
* @return a {@link ExtractorFactory} that produces chained {@link
* ValueExtractor} instances for an array of property or method names.
*/
@Bean
@ChainedExtractor("")
ExtractorFactory<ChainedExtractor, ?, ?> chainedExtractor() {
return (annotation) -> Extractors.chained(annotation.value());
}


/**
* A {@link ExtractorFactory} that produces {@link
* com.tangosol.util.extractor.MultiExtractor} containing {@link
* ValueExtractor} instances produced from the annotations contained in a
* {@link ChainedExtractor.Extractors} annotation.
* @return a {@link ExtractorFactory} that produces {@link
* com.tangosol.util.extractor.MultiExtractor} containing {@link
* ValueExtractor} instances produced from the annotations contained in a
* {@link ChainedExtractor.Extractors} annotation.
*/
@Bean
@ChainedExtractor.Extractors({})
@SuppressWarnings({"unchecked", "rawtypes"})
ExtractorFactory<ChainedExtractor.Extractors, ?, ?> chainedExtractors() {
return (annotation) -> {
ValueExtractor[] extractors = Arrays.stream(annotation.value())
.map((ann) -> Extractors.chained(ann.value()))
.toArray(ValueExtractor[]::new);
return Extractors.multi(extractors);
};
}

/**
* A {@link ExtractorFactory} that produces{@link ValueExtractor}
* instances for a given POF index or property path.
* @return a {@link ExtractorFactory} that produces{@link ValueExtractor}
* instances for a given POF index or property path.
*/
@Bean
@PofExtractor
@SuppressWarnings({"unchecked", "rawtypes"})
ExtractorFactory<PofExtractor, ?, ?> pofExtractor() {
return (annotation) -> {
Class clazz = (annotation.type().equals(Object.class))
? null
: annotation.type();
String sPath = annotation.path();
int[] anIndex = annotation.index();

if (sPath.length() == 0 && anIndex.length == 0) {
throw new IllegalArgumentException("Neither 'index' nor 'path' are defined within @PofExtractor annotation. One is required.");
}
if (sPath.length() > 0 && anIndex.length > 0) {
throw new IllegalArgumentException("Both 'index' and 'path' are defined within @PofExtractor annotation. Only one is allowed.");
}
if (sPath.length() > 0 && clazz == null) {
throw new IllegalArgumentException("'type' must be specified within @PofExtractor annotation when property path is used.");
}

return (sPath.length() > 0)
? Extractors.fromPof(clazz, sPath)
: Extractors.fromPof(clazz, anIndex);
};
}

/**
* A {@link ExtractorFactory} that produces {@link
* com.tangosol.util.extractor.MultiExtractor} containing {@link
* ValueExtractor} instances produced from the annotations contained in a
* {@link PofExtractor.Extractors} annotation.
* @return a {@link ExtractorFactory} that produces {@link
* com.tangosol.util.extractor.MultiExtractor} containing {@link
* ValueExtractor} instances produced from the annotations contained in a
* {@link PofExtractor.Extractors} annotation.
*/
@Bean
@PofExtractor.Extractors({})
@SuppressWarnings({"unchecked", "rawtypes"})
ExtractorFactory<PofExtractor.Extractors, ?, ?> pofExtractors() {
final ExtractorFactory<PofExtractor, ?, ?> factory = pofExtractor();
return (annotation) -> {
ValueExtractor[] extractors = Arrays.stream(annotation.value())
.map(factory::create)
.toArray(ValueExtractor[]::new);
return Extractors.multi(extractors);
};
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2021, Oracle and/or its affiliates.
* Copyright (c) 2013, 2023, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
Expand All @@ -8,21 +8,23 @@

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import com.oracle.coherence.spring.annotation.ExtractorBinding;
import com.oracle.coherence.spring.annotation.ExtractorFactory;
import com.oracle.coherence.spring.configuration.support.CoherenceAnnotationUtils;
import com.oracle.coherence.spring.configuration.support.CommonExtractorFactories;
import com.tangosol.util.Extractors;
import com.tangosol.util.ValueExtractor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.InjectionPoint;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

/**
* Service that supports the {@link ExtractorConfiguration}.
Expand All @@ -32,10 +34,41 @@
*/
public class ExtractorService {

final ConfigurableApplicationContext applicationContext;
private static final Logger LOGGER = LoggerFactory.getLogger(ExtractorService.class);

private final ConfigurableApplicationContext applicationContext;

private final Map<String, ExtractorFactory> extractorFactories;

public ExtractorService(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.extractorFactories = CommonExtractorFactories.getExtractorFactories();
}

private ValueExtractor getExtractorFromApplicationContext(Annotation coherenceAnnotation) {
Assert.notNull(coherenceAnnotation, "coherenceAnnotation must not be null.");

final Class extractorFactoryClass = ExtractorFactory.class;
final String[] beanNames = this.applicationContext.getBeanNamesForType(ExtractorFactory.class);
LOGGER.debug("Found {} beans in the application context for bean type {}", beanNames.length, extractorFactoryClass.getName());

final Collection<ExtractorFactory> beans = CoherenceAnnotationUtils.getBeansOfTypeWithAnnotation(
this.applicationContext,
extractorFactoryClass,
coherenceAnnotation.annotationType());

final List<ValueExtractor> foundExtractors = beans.stream()
.map((extractorFactory) -> extractorFactory.create(coherenceAnnotation))
.collect(Collectors.toList());

if (!foundExtractors.isEmpty() && foundExtractors.size() == 1) {
return foundExtractors.iterator().next();
}
else if (foundExtractors.size() > 1) {
throw new IllegalStateException(String.format("Needed 1 but found %s beans annotated with '%s'",
foundExtractors.size(), coherenceAnnotation.annotationType().getName()));
}
return null;
}

/**
Expand All @@ -48,45 +81,8 @@ public ExtractorService(ConfigurableApplicationContext applicationContext) {
*/
ValueExtractor<?, ?> getExtractor(InjectionPoint injectionPoint, boolean returnNullIfNotFound) {
Assert.notNull(injectionPoint, "injectionPoint must not be null.");

final List<Annotation> extractorAnnotations = CoherenceAnnotationUtils.getAnnotationsMarkedWithMarkerAnnotation(injectionPoint, ExtractorBinding.class);

final List<ValueExtractor<?, ?>> valueExtractors = new ArrayList<>();

if (!CollectionUtils.isEmpty(extractorAnnotations)) {
for (Annotation annotation : extractorAnnotations) {
final Class<? extends Annotation> annotationType = annotation.annotationType();
final Map<String, Object> beans = this.applicationContext.getBeansWithAnnotation(annotationType);

if (beans.isEmpty()) {
throw new IllegalStateException(String.format("No bean annotated with '%s' found.", annotationType.getCanonicalName()));
}
else if (beans.size() > 1) {
throw new IllegalStateException(String.format("Needed 1 but found %s beans annotated with '%s': %s.",
beans.size(), annotationType.getCanonicalName(), StringUtils.collectionToCommaDelimitedString(beans.keySet())));
}
@SuppressWarnings({"unchecked", "rawtypes"})
final ExtractorFactory<Annotation, Object, Object> extractorFactory = (ExtractorFactory) beans.values().iterator().next();
valueExtractors.add(extractorFactory.create(annotation));
}
}

@SuppressWarnings("unchecked")
final ValueExtractor<Object, Object>[] valueExtractorsAsArray = valueExtractors.toArray(new ValueExtractor[0]);
if (valueExtractorsAsArray.length == 0) {
if (returnNullIfNotFound) {
return null;
}
else {
throw new IllegalStateException("Unsatisfied dependency - no ExtractorFactory bean found annotated with "); // + bindings);
}
}
else if (valueExtractorsAsArray.length == 1) {
return valueExtractorsAsArray[0];
}
else {
return Extractors.multi(valueExtractorsAsArray);
}
final List<Annotation> annotations = CoherenceAnnotationUtils.getAnnotationsMarkedWithMarkerAnnotation(injectionPoint, ExtractorBinding.class);
return this.resolve(annotations);
}

/**
Expand All @@ -107,21 +103,22 @@ else if (valueExtractorsAsArray.length == 1) {
* @return a {@link ValueExtractor} implementation created from the specified qualifiers.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public <T, E> ValueExtractor<T, E> resolve(Set<Annotation> annotations) {
public <T, E> ValueExtractor<T, E> resolve(Collection<Annotation> annotations) {
final List<ValueExtractor> list = new ArrayList<>();

for (Annotation annotation : annotations) {
final Class<? extends Annotation> annotationType = annotation.annotationType();

final ExtractorFactory<Annotation, Object, Object> extractorFactory =
CoherenceAnnotationUtils.getSingleBeanWithAnnotation(this.applicationContext, annotationType);
ValueExtractor extractor = this.getExtractorFromApplicationContext(annotation);
if (extractor != null) {
list.add(extractor);
continue;
}

final ValueExtractor extractor = extractorFactory.create(annotation);
if (extractor == null) {
throw new IllegalStateException("Unsatisfied dependency - no extractor could be created by "
+ extractorFactory + " extractor factory.");
final Class<? extends Annotation> annotationType = annotation.annotationType();
final ExtractorFactory filterFactory = this.extractorFactories.get(annotationType.getName());
if (filterFactory == null) {
throw new IllegalStateException(String.format("No filterFactory found for annotation %s.", annotationType.getCanonicalName()));
}
list.add(extractor);
list.add(filterFactory.create(annotation));
}

ValueExtractor[] aExtractors = list.toArray(new ValueExtractor[0]);
Expand Down

0 comments on commit 952b7f1

Please sign in to comment.