diff --git a/pom.xml b/pom.xml index d895665e2..1830c6fcc 100644 --- a/pom.xml +++ b/pom.xml @@ -265,6 +265,17 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + ${java-module-name} + + + + org.apache.maven.plugins maven-assembly-plugin diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java index bf794b9ca..4d2eaa423 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java @@ -133,7 +133,7 @@ public long getCas(final Object entity) { long cas = 0; if (versionProperty != null) { - Object casObject = (Number) accessor.getProperty(versionProperty); + Object casObject = accessor.getProperty(versionProperty); if (casObject instanceof Number) { cas = ((Number) casObject).longValue(); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java index 87f22af66..2a2c36cb7 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java @@ -140,7 +140,7 @@ public Long getCas(final Object entity) { long cas = 0; if (versionProperty != null) { - Object casObject = (Number) accessor.getProperty(versionProperty); + Object casObject = accessor.getProperty(versionProperty); if (casObject instanceof Number) { cas = ((Number) casObject).longValue(); } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java index 9f84ffa95..bfffbe514 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ public class CouchbaseCustomConversions extends org.springframework.data.convert converters.addAll(DateConverters.getConvertersToRegister()); converters.addAll(CouchbaseJsr310Converters.getConvertersToRegister()); + converters.addAll(OtherConverters.getConvertersToRegister()); STORE_CONVERTERS = Collections.unmodifiableList(converters); STORE_CONVERSIONS = StoreConversions.of(SimpleTypeHolder.DEFAULT, STORE_CONVERTERS); diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java index 306e8a470..bc8da6a1a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2021 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.annotation.Transient; +import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.core.mapping.CouchbaseList; @@ -117,11 +118,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem private @Nullable EntityCallbacks entityCallbacks; public MappingCouchbaseConverter() { - super(new DefaultConversionService()); - - this.typeMapper = new DefaultCouchbaseTypeMapper(TYPEKEY_DEFAULT); - this.mappingContext = new CouchbaseMappingContext(); - this.spELContext = new SpELContext(CouchbaseDocumentPropertyAccessor.INSTANCE); + this(new CouchbaseMappingContext(), null); } /** @@ -131,7 +128,7 @@ public MappingCouchbaseConverter() { */ public MappingCouchbaseConverter( final MappingContext, CouchbasePersistentProperty> mappingContext) { - this(mappingContext, TYPEKEY_DEFAULT); + this(mappingContext, null); } /** @@ -145,8 +142,14 @@ public MappingCouchbaseConverter( final MappingContext, CouchbasePersistentProperty> mappingContext, final String typeKey) { super(new DefaultConversionService()); - this.mappingContext = mappingContext; + // this is how the MappingCouchbaseConverter gets the custom conversions. + // the conversions Service gets them in afterPropertiesSet() + CustomConversions customConversions = new CouchbaseCustomConversions(Collections.emptyList()); + this.setCustomConversions(customConversions); + // if the mappingContext does not have the SimpleTypes, it will not know that they have converters, then it will + // try to access the fields of the type and (maybe) fail with InaccessibleObjectException + ((CouchbaseMappingContext) mappingContext).setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); typeMapper = new DefaultCouchbaseTypeMapper(typeKey != null ? typeKey : TYPEKEY_DEFAULT); spELContext = new SpELContext(CouchbaseDocumentPropertyAccessor.INSTANCE); } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/OtherConverters.java b/src/main/java/org/springframework/data/couchbase/core/convert/OtherConverters.java new file mode 100644 index 000000000..1d2fe5902 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/convert/OtherConverters.java @@ -0,0 +1,93 @@ +/* + * Copyright 2021 the original author or authors + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.data.couchbase.core.convert; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.convert.WritingConverter; + +/** + * Out of the box conversions for java dates and calendars. + * + * @author Michael Reiche + */ +public final class OtherConverters { + + private OtherConverters() {} + + /** + * Returns all converters by this class that can be registered. + * + * @return the list of converters to register. + */ + public static Collection> getConvertersToRegister() { + List> converters = new ArrayList>(); + + converters.add(UuidToString.INSTANCE); + converters.add(StringToUuid.INSTANCE); + converters.add(BigIntegerToString.INSTANCE); + converters.add(StringToBigInteger.INSTANCE); + + return converters; + } + + @WritingConverter + public enum UuidToString implements Converter { + INSTANCE; + + @Override + public String convert(UUID source) { + return source == null ? null : source.toString(); + } + } + + @ReadingConverter + public enum StringToUuid implements Converter { + INSTANCE; + + @Override + public UUID convert(String source) { + return source == null ? null : UUID.fromString(source); + } + } + + @WritingConverter + public enum BigIntegerToString implements Converter { + INSTANCE; + + @Override + public String convert(BigInteger source) { + return source == null ? null : source.toString(); + } + } + + @ReadingConverter + public enum StringToBigInteger implements Converter { + INSTANCE; + + @Override + public BigInteger convert(String source) { + return source == null ? null : new BigInteger(source); + } + } +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseMappingContext.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseMappingContext.java index 6421dcd60..0160e627c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseMappingContext.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseMappingContext.java @@ -131,7 +131,7 @@ public void setAutoIndexCreation(boolean autoCreateIndexes) { /** * override method from AbstractMappingContext as that method will not publishEvent() if it finds the entity has * already been cached - * + * * @param typeInformation - entity type */ @Override diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java new file mode 100644 index 000000000..de107eab7 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java @@ -0,0 +1,78 @@ +/* + * Copyright 2014-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ +package org.springframework.data.couchbase.repository.cdi; + +import java.lang.annotation.Annotation; +import java.util.Optional; +import java.util.Set; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; + +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; +import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; +import org.springframework.data.repository.cdi.CdiRepositoryBean; +import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; +import org.springframework.util.Assert; + +/** + * A bean which represents a Couchbase repository. + * + * @author Mark Paluch + */ +public class CouchbaseRepositoryBean extends CdiRepositoryBean { + + private final Bean couchbaseOperationsBean; + + /** + * Creates a new {@link CouchbaseRepositoryBean}. + * + * @param operations must not be {@literal null}. + * @param qualifiers must not be {@literal null}. + * @param repositoryType must not be {@literal null}. + * @param beanManager must not be {@literal null}. + * @param detector detector for the custom {@link org.springframework.data.repository.Repository} implementations + * {@link org.springframework.data.repository.config.CustomRepositoryImplementationDetector}, can be + * {@literal null}. + */ + public CouchbaseRepositoryBean(Bean operations, Set qualifiers, + Class repositoryType, BeanManager beanManager, CustomRepositoryImplementationDetector detector) { + super(qualifiers, repositoryType, beanManager, Optional.of(detector)); + + Assert.notNull(operations, "Cannot create repository with 'null' for CouchbaseOperations."); + this.couchbaseOperationsBean = operations; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class) + */ + @Override + protected T create(CreationalContext creationalContext, Class repositoryType) { + + CouchbaseOperations couchbaseOperations = getDependencyInstance(couchbaseOperationsBean, CouchbaseOperations.class); + RepositoryOperationsMapping couchbaseOperationsMapping = new RepositoryOperationsMapping(couchbaseOperations); + + return create(() -> new CouchbaseRepositoryFactory(couchbaseOperationsMapping), repositoryType); + } + + @Override + public Class getScope() { + return couchbaseOperationsBean.getScope(); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java new file mode 100644 index 000000000..5800cdb5b --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java @@ -0,0 +1,103 @@ +/* + * Copyright 2014-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.data.couchbase.repository.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.UnsatisfiedResolutionException; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.ProcessBean; + +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.repository.cdi.CdiRepositoryBean; +import org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport; + +/** + * A portable CDI extension which registers beans for Spring Data Couchbase repositories. + * + * @author Mark Paluch + */ +public class CouchbaseRepositoryExtension extends CdiRepositoryExtensionSupport { + + private final Map, Bean> couchbaseOperationsMap = new HashMap, Bean>(); + + /** + * Implementation of a an observer which checks for CouchbaseOperations beans and stores them in + * {@link #couchbaseOperationsMap} for later association with corresponding repository beans. + * + * @param The type. + * @param processBean The annotated type as defined by CDI. + */ + @SuppressWarnings("unchecked") + void processBean(@Observes ProcessBean processBean) { + Bean bean = processBean.getBean(); + for (Type type : bean.getTypes()) { + if (type instanceof Class && CouchbaseOperations.class.isAssignableFrom((Class) type)) { + couchbaseOperationsMap.put(bean.getQualifiers(), ((Bean) bean)); + } + } + } + + /** + * Implementation of a an observer which registers beans to the CDI container for the detected Spring Data + * repositories. + *

+ * The repository beans are associated to the CouchbaseOperations using their qualifiers. + * + * @param beanManager The BeanManager instance. + */ + void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { + for (Map.Entry, Set> entry : getRepositoryTypes()) { + + Class repositoryType = entry.getKey(); + Set qualifiers = entry.getValue(); + + CdiRepositoryBean repositoryBean = createRepositoryBean(repositoryType, qualifiers, beanManager); + afterBeanDiscovery.addBean(repositoryBean); + registerBean(repositoryBean); + } + } + + /** + * Creates a {@link Bean}. + * + * @param The type of the repository. + * @param repositoryType The class representing the repository. + * @param beanManager The BeanManager instance. + * @return The bean. + */ + private CdiRepositoryBean createRepositoryBean(Class repositoryType, Set qualifiers, + BeanManager beanManager) { + + Bean couchbaseOperationsBean = this.couchbaseOperationsMap.get(qualifiers); + + if (couchbaseOperationsBean == null) { + throw new UnsatisfiedResolutionException(String.format("Unable to resolve a bean for '%s' with qualifiers %s.", + CouchbaseOperations.class.getName(), qualifiers)); + } + + return new CouchbaseRepositoryBean(couchbaseOperationsBean, qualifiers, repositoryType, beanManager, + getCustomImplementationDetector()); + } +} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java index b754514db..49652573a 100644 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java @@ -29,6 +29,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions; @@ -67,17 +68,35 @@ void shouldNotThrowNPE() { @Test void doesNotAllowSimpleType1() { - assertThrows(MappingException.class, () -> converter.write("hello", new CouchbaseDocument())); + try { + converter.write("hello", new CouchbaseDocument()); + } catch(Exception e){ + if(!(e instanceof MappingException) && !e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")){ + throw new RuntimeException("Should have thrown MappingException or InaccessibleObjectException", e); + } + } } @Test void doesNotAllowSimpleType2() { - assertThrows(MappingException.class, () -> converter.write(true, new CouchbaseDocument())); + try { + converter.write(true, new CouchbaseDocument()); + } catch(Exception e){ + if(!(e instanceof MappingException) && !e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")){ + throw new RuntimeException("Should have thrown MappingException or InaccessibleObjectException", e); + } + } } @Test void doesNotAllowSimpleType3() { - assertThrows(MappingException.class, () -> converter.write(42, new CouchbaseDocument())); + try { + converter.write(42, new CouchbaseDocument()); + } catch(Exception e){ + if(!(e instanceof MappingException) && !e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")){ + throw new RuntimeException("Should have thrown MappingException or InaccessibleObjectException", e); + } + } } @Test @@ -421,8 +440,11 @@ void writesAndReadsCustomConvertedClass() { List converters = new ArrayList<>(); converters.add(BigDecimalToStringConverter.INSTANCE); converters.add(StringToBigDecimalConverter.INSTANCE); - converter.setCustomConversions(new CouchbaseCustomConversions(converters)); + CustomConversions customConversions = new CouchbaseCustomConversions(converters); + converter.setCustomConversions(customConversions); converter.afterPropertiesSet(); + ((CouchbaseMappingContext) converter.getMappingContext()) + .setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); CouchbaseDocument converted = new CouchbaseDocument(); @@ -469,8 +491,11 @@ void writesAndReadsCustomFieldsConvertedClass() { List converters = new ArrayList<>(); converters.add(BigDecimalToStringConverter.INSTANCE); converters.add(StringToBigDecimalConverter.INSTANCE); - converter.setCustomConversions(new CouchbaseCustomConversions(converters)); + CustomConversions customConversions = new CouchbaseCustomConversions(converters); + converter.setCustomConversions(customConversions); converter.afterPropertiesSet(); + ((CouchbaseMappingContext) converter.getMappingContext()) + .setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); CouchbaseDocument converted = new CouchbaseDocument(); @@ -517,8 +542,11 @@ void writesAndReadsClassContainingCustomConvertedObjects() { List converters = new ArrayList<>(); converters.add(BigDecimalToStringConverter.INSTANCE); converters.add(StringToBigDecimalConverter.INSTANCE); - converter.setCustomConversions(new CouchbaseCustomConversions(converters)); + CustomConversions customConversions = new CouchbaseCustomConversions(converters); + converter.setCustomConversions(customConversions); converter.afterPropertiesSet(); + ((CouchbaseMappingContext) converter.getMappingContext()) + .setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); CouchbaseDocument converted = new CouchbaseDocument();