Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Java #
*.class

# Package Files #
*.jar
*.war
*.ear

# IDEA #
*.iml
.idea
*~

# eclipse specific git ignore
.project
.metadata
.classpath
.settings/

# Maven files #
data/
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties

# Misc #
*.log
*.bat

56 changes: 56 additions & 0 deletions cdi/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Copyright 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

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.

-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.github.mbenson.therian</groupId>
<artifactId>therian-parent</artifactId>
<version>0.6-SNAPSHOT</version>
</parent>

<artifactId>therian-cdi</artifactId>
<name>therian CDI extension</name>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0-6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.mbenson.therian</groupId>
<artifactId>therian</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>openejb-core</artifactId>
<version>4.7.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
26 changes: 26 additions & 0 deletions cdi/src/main/java/therian/cdi/Mapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 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
*
* 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.
*/
package therian.cdi;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Mapper {
}
107 changes: 107 additions & 0 deletions cdi/src/main/java/therian/cdi/internal/MapperBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 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
*
* 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.
*/
package therian.cdi.internal;

import therian.cdi.internal.literal.AnyLiteral;
import therian.cdi.internal.literal.DefaultLiteral;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionPoint;
import java.lang.annotation.Annotation;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;

import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;

public class MapperBean<T> implements Bean<T> {
private final Set<Type> types;
private final Set<Annotation> qualifiers;
private final Class<T> clazz;
private final Class<?>[] proxyTypes;
private final MapperHandler handler;

public MapperBean(final AnnotatedType at) {
clazz = at.getJavaClass();
types = new HashSet<>(asList(clazz, Object.class));
qualifiers = new HashSet<>(asList(DefaultLiteral.INSTANCE, AnyLiteral.INSTANCE));
proxyTypes = new Class<?>[] { clazz };
handler = new MapperHandler(at);
}

@Override
public Set<Type> getTypes() {
return types;
}

@Override
public Set<Annotation> getQualifiers() {
return qualifiers;
}

@Override
public Class<? extends Annotation> getScope() {
return ApplicationScoped.class;
}

@Override
public String getName() {
return null;
}

@Override
public boolean isNullable() {
return false;
}

@Override
public Set<InjectionPoint> getInjectionPoints() {
return emptySet();
}

@Override
public Class<?> getBeanClass() {
return clazz;
}

@Override
public Set<Class<? extends Annotation>> getStereotypes() {
return emptySet();
}

@Override
public boolean isAlternative() {
return false;
}

@Override
public T create(final CreationalContext<T> context) {
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
return (T) Proxy.newProxyInstance(
contextClassLoader == null ? ClassLoader.getSystemClassLoader() : contextClassLoader,
proxyTypes, handler);
}

@Override
public void destroy(final T instance, final CreationalContext<T> context) {
// no-op
}
}
110 changes: 110 additions & 0 deletions cdi/src/main/java/therian/cdi/internal/MapperHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 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
*
* 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.
*/
package therian.cdi.internal;

import static java.util.Optional.of;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.function.Function;

import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;

import org.apache.commons.lang3.reflect.TypeUtils;
import org.apache.commons.lang3.reflect.Typed;

import therian.Therian;
import therian.TherianModule;
import therian.operation.Convert;
import therian.operator.copy.PropertyCopier;
import therian.util.Positions;

public class MapperHandler implements InvocationHandler {
private final Map<Method, Meta<?, ?>> mapping;
private final String toString;

public MapperHandler(final AnnotatedType<?> type) {
// just for error handling
of(type.getMethods().stream()
.filter(m -> m.isAnnotationPresent(PropertyCopier.Mapping.class)
&& (m.getParameters().size() != 1 || m.getJavaMember().getReturnType() == void.class))
.collect(toList())).filter(l -> !l.isEmpty()).ifPresent(l -> {
throw new IllegalArgumentException("@Mapping only supports one parameter and not void signatures");
});

// TODO: use a single Therian instance if there are not redundant conversions specified by interface methods

this.mapping = type.getMethods().stream().filter(m -> m.isAnnotationPresent(PropertyCopier.Mapping.class))
.collect(toMap(AnnotatedMethod::getJavaMember, am -> {
final Method member = am.getJavaMember();
final Typed<Object> source = TypeUtils.wrap(member.getGenericParameterTypes()[0]);
final Typed<Object> target = TypeUtils.wrap(member.getGenericReturnType());

final Therian therian =
Therian.standard()
.withAdditionalModules(TherianModule.expandingDependencies(TherianModule.create()
.withOperators(PropertyCopier.getInstance(source, target,
am.getAnnotation(PropertyCopier.Mapping.class),
am.getAnnotation(PropertyCopier.Matching.class)))));

@SuppressWarnings({ "unchecked", "rawtypes" })
final Meta<?, ?> result = new Meta(therian,
sourceInstance -> Convert.to(target, Positions.<Object> readOnly(source, sourceInstance)));
return result;
}));

this.toString = getClass().getSimpleName() + "[" + type.getJavaClass().getName() + "]";
}

@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (final InvocationTargetException ite) {
throw ite.getCause();
}
}

@SuppressWarnings("unchecked")
final Meta<Object, Object> meta = (Meta<Object, Object>) mapping.get(method);
return meta.convert(args[0]);
}

@Override
public String toString() {
return toString;
}

private static final class Meta<A, B> {
private final Therian therian;
private final Function<A, Convert<A, B>> convert;

public Meta(final Therian therian, final Function<A, Convert<A, B>> convert) {
this.therian = therian;
this.convert = convert;
}

public B convert(final A in) {
return therian.context().eval(convert.apply(in));
}
}
}
42 changes: 42 additions & 0 deletions cdi/src/main/java/therian/cdi/internal/TherianExtension.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 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
*
* 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.
*/
package therian.cdi.internal;

import therian.cdi.Mapper;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import java.util.ArrayList;
import java.util.Collection;

public class TherianExtension implements Extension {
private final Collection<AnnotatedType<?>> detectedMappers = new ArrayList<>();

void captureMapper(@Observes final ProcessAnnotatedType<?> potentialMapper) {
final AnnotatedType<?> annotatedType = potentialMapper.getAnnotatedType();
if (annotatedType.isAnnotationPresent(Mapper.class)) {
detectedMappers.add(annotatedType);
}
}

void addMapperBeans(@Observes final AfterBeanDiscovery abd) {
detectedMappers.stream().forEach(at -> abd.addBean(new MapperBean(at)));
detectedMappers.clear();
}
}
Loading