/*
* Copyright (c) 2014 Red Hat, Inc.
*
* 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 org.ovirt.engine.api.restapi.types;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.ws.rs.WebApplicationException;
import org.ovirt.engine.api.common.util.PackageExplorer;
import org.ovirt.engine.api.restapi.utils.MalformedIdException;
import org.ovirt.engine.api.restapi.utils.MappingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discovers and manages type mappers.
*/
public class MappingLocator {
/**
* The logger used by this class.
*/
private static final Logger log = LoggerFactory.getLogger(MappingLocator.class);
private String discoverPackageName;
private Map<ClassPairKey, Mapper<?, ?>> mappers;
/**
* Normal constructor used when injected
*/
public MappingLocator() {
mappers = new HashMap<>();
}
/**
* Constructor intended only for testing.
*
* @param discoverPackageName
* package to look under
*/
MappingLocator(String discoverPackageName) {
this.discoverPackageName = discoverPackageName;
mappers = new HashMap<>();
}
/**
* Discover mappers and populate internal registry. The classloading
* environment is scanned for classes contained under the
* org.ovirt.engine.api.restapi.types package and exposing methods decorated
* with the @Mapping annotation.
*/
public void populate() {
String packageName = discoverPackageName != null? discoverPackageName: this.getClass().getPackage().getName();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
List<String> classNames = PackageExplorer.discoverClasses(packageName);
for (String className : classNames) {
try {
Class<?> mapperClass = classLoader.loadClass(className);
for (Method method : mapperClass.getMethods()) {
Mapping mapping = method.getAnnotation(Mapping.class);
if (mapping != null) {
mappers.put(new ClassPairKey(mapping.from(), mapping.to()),
new MethodInvokerMapper(method, mapping.to()));
}
}
}
catch (ClassNotFoundException exception) {
log.error(
"Error while trying to load mapper class \"{}\".",
className,
exception
);
}
}
}
/**
* Get an appropriate mapper mediating between the required types.
*
* @param <F>
* the from type
* @param <T>
* the to type
* @param from
* the from class
* @param to
* the to class
* @return a mapped instance of the to type
*/
@SuppressWarnings("unchecked")
public <F, T> Mapper<F, T> getMapper(Class<F> from, Class<T> to) {
return (Mapper<F, T>) mappers.get(new ClassPairKey(from, to));
}
private static class ClassPairKey {
private Class<?> from;
private Class<?> to;
private ClassPairKey(Class<?> from, Class<?> to) {
this.from = from;
this.to = to;
}
public int hashCode() {
return Objects.hash(
to,
from
);
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ClassPairKey)) {
return false;
}
ClassPairKey other = (ClassPairKey) obj;
return Objects.equals(to, other.to)
&& Objects.equals(from, other.from);
}
public String toString() {
return "map from: " + from + " to: " + to;
}
}
private static class MethodInvokerMapper implements Mapper<Object, Object> {
private Method method;
private Class<?> to;
private MethodInvokerMapper(Method method, Class<?> to) {
this.method = method;
this.to = to;
}
@Override
public Object map(Object from, Object template) {
try {
// REVISIT support non-static mapping methods also
return to.cast(method.invoke(null, from, template));
} catch (InvocationTargetException ite) {
if (ite.getTargetException() instanceof MalformedIdException) {
throw (MalformedIdException) ite.getTargetException();
} else if (ite.getTargetException() instanceof WebApplicationException) {
throw (WebApplicationException) ite.getTargetException();
} else {
throw new MappingException(ite);
}
} catch (IllegalAccessException e) {
throw new MappingException(e);
}
}
public String toString() {
return "map to: " + to + " via " + method;
}
}
}