diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f58fd5e
--- /dev/null
+++ b/.gitignore
@@ -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
+
diff --git a/cdi/pom.xml b/cdi/pom.xml
new file mode 100644
index 0000000..3b8acc8
--- /dev/null
+++ b/cdi/pom.xml
@@ -0,0 +1,56 @@
+
+
+
+ 4.0.0
+
+
+ com.github.mbenson.therian
+ therian-parent
+ 0.6-SNAPSHOT
+
+
+ therian-cdi
+ therian CDI extension
+ jar
+
+
+
+ org.apache.openejb
+ javaee-api
+ 6.0-6
+ provided
+
+
+ com.github.mbenson.therian
+ therian
+ ${project.version}
+
+
+
+ junit
+ junit
+
+
+ org.apache.openejb
+ openejb-core
+ 4.7.2
+ test
+
+
+
diff --git a/cdi/src/main/java/therian/cdi/Mapper.java b/cdi/src/main/java/therian/cdi/Mapper.java
new file mode 100644
index 0000000..f63b4ab
--- /dev/null
+++ b/cdi/src/main/java/therian/cdi/Mapper.java
@@ -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 {
+}
diff --git a/cdi/src/main/java/therian/cdi/internal/MapperBean.java b/cdi/src/main/java/therian/cdi/internal/MapperBean.java
new file mode 100644
index 0000000..cacd2de
--- /dev/null
+++ b/cdi/src/main/java/therian/cdi/internal/MapperBean.java
@@ -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 implements Bean {
+ private final Set types;
+ private final Set qualifiers;
+ private final Class 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 getTypes() {
+ return types;
+ }
+
+ @Override
+ public Set 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 getInjectionPoints() {
+ return emptySet();
+ }
+
+ @Override
+ public Class> getBeanClass() {
+ return clazz;
+ }
+
+ @Override
+ public Set> getStereotypes() {
+ return emptySet();
+ }
+
+ @Override
+ public boolean isAlternative() {
+ return false;
+ }
+
+ @Override
+ public T create(final CreationalContext 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 context) {
+ // no-op
+ }
+}
diff --git a/cdi/src/main/java/therian/cdi/internal/MapperHandler.java b/cdi/src/main/java/therian/cdi/internal/MapperHandler.java
new file mode 100644
index 0000000..7625ee5
--- /dev/null
+++ b/cdi/src/main/java/therian/cdi/internal/MapperHandler.java
@@ -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> 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