package org.hivedb.util.classgen; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import org.hibernate.shards.strategy.access.ShardAccessStrategy; import org.hibernate.shards.util.Lists; import org.hivedb.Hive; import org.hivedb.HiveRuntimeException; import org.hivedb.annotations.AnnotationHelper; import org.hivedb.annotations.IndexParam; import org.hivedb.annotations.IndexParamPagingPair; import org.hivedb.annotations.IndexParamPairs; import org.hivedb.configuration.EntityConfig; import org.hivedb.configuration.EntityHiveConfig; import org.hivedb.hibernate.*; import org.hivedb.services.Service; import org.hivedb.services.ServiceContainer; import org.hivedb.services.ServiceResponse; import org.hivedb.util.PrimitiveUtils; import org.hivedb.util.functional.*; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.Map.Entry; public class GeneratedServiceInterceptor implements MethodInterceptor, Service { public static Service load(String className, String serviceClassName, String serviceResponseClassName, String serviceContainerClassName, Hive hive, EntityHiveConfig entityHiveConfig, ShardAccessStrategy strategy) { try { return load(Class.forName(className), Class.forName(serviceClassName), Class.forName(serviceResponseClassName), Class.forName(serviceContainerClassName), hive, entityHiveConfig, strategy); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } public static Service load(Class clazz, Class serviceClass, Class serviceResponseClass, Class serviceContainerClass, Hive hive, EntityHiveConfig entityHiveConfig, ShardAccessStrategy strategy) { EntityConfig config = entityHiveConfig.getEntityConfig(clazz.getCanonicalName()); List<Class<?>> classes = Lists.newArrayList(); classes.addAll(new EntityResolver(entityHiveConfig).getEntityClasses()); HiveSessionFactory factory = new HiveSessionFactoryBuilderImpl(entityHiveConfig, hive, strategy); DataAccessObject dao = new BaseDataAccessObject(config, hive, factory); return (Service) GeneratedClassFactory.newInstance(serviceClass, new GeneratedServiceInterceptor(clazz, serviceClass, serviceResponseClass, serviceContainerClass, dao, config)); } public static Service load(Class clazz, Class serviceClass, Class serviceResponseClass, Class serviceContainerClass, Hive hive, ConfigurationReader reader, ShardAccessStrategy strategy) { return load(clazz, serviceClass, serviceResponseClass, serviceContainerClass, hive, reader.getHiveConfiguration(), strategy); } Class clazz; Class serviceClass; Class serviceResponseClass; Class serviceContainerClass; DataAccessObject dao; EntityConfig config; public GeneratedServiceInterceptor(Class clazz, Class serviceClass, Class serviceResponseClass, Class serviceContainerClass, DataAccessObject dao, EntityConfig config) { this.clazz = clazz; this.serviceClass = serviceClass; this.serviceResponseClass = serviceResponseClass; this.serviceContainerClass = serviceContainerClass; this.dao = dao; this.config = config; } public Collection<Object> unProxy(Collection<Object> classInstances) { return Transform.map(new Unary<Object, Object>() { public Object f(Object item) { return unProxy(item); } }, classInstances); } public Object unProxy(Object instance) { return new GenerateInstance(clazz).generateAndCopyProperties(instance); } public boolean exists(Object id) { return dao.exists(id); } public String getPersistedClass() { return clazz.getName(); } public Object delete(Object id) { dao.delete(id); return id; } public ServiceResponse get(Object id) { final Object instance = dao.get(id); return formulateResponse((Collection<Object>) (instance != null ? Arrays.asList(instance) : Collections.emptyList())); } public ServiceResponse save(Object instance) { return formulateResponse(dao.save(unProxy(instance))); } public ServiceResponse saveAll(Iterable instances) { return formulateResponse(dao.saveAll((Collection) unProxy((Collection) instances))); } @SuppressWarnings("unchecked") private ServiceResponse formulateResponse(Object... instances) { return formulateResponse(Arrays.asList(instances)); } @SuppressWarnings("unchecked") private ServiceResponse formulateResponse(Collection instances) { validateNonNull(instances); ServiceResponse serviceResponse = createServiceResponse(instances); return serviceResponse; } public ServiceResponse createServiceResponse(Collection instances) { ServiceResponse serviceResponse = (ServiceResponse) GeneratedClassFactory.newInstance(serviceResponseClass); GeneratedInstanceInterceptor.setProperty(serviceResponse, "containers", Transform.map(new Unary<Object, ServiceContainer>() { public ServiceContainer f(Object item) { return createServiceContainer(item, config.getVersion(item)); } }, instances)); return serviceResponse; } public ServiceContainer createServiceContainer(Object instance, Integer version) { ServiceContainer serviceContainer = (ServiceContainer) GeneratedClassFactory.newInstance(serviceContainerClass); // Fill the ServiceContainer methods GeneratedInstanceInterceptor.setProperty(serviceContainer, "instance", instance); GeneratedInstanceInterceptor.setProperty(serviceContainer, "version", version); // Create a getter for ServiceContainer implementors (e.g. getWeatherReport() for the WeatherReport class) // A strongly-typed method like this is needed by SOAP clients, otherwise getInstance() would suffice. GeneratedInstanceInterceptor.setProperty(serviceContainer, clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1), instance); return serviceContainer; } private void validateNonNull(final Collection<Object> collection) { if (Filter.isMatch(new Filter.NullPredicate<Object>(), collection)) { String ids = Amass.joinByToString(new Joiner.ConcatStrings<String>(", "), Transform.map(new Unary<Object, String>() { public String f(Object item) { return item != null ? config.getId(item).toString() : "null"; } }, collection)); throw new HiveRuntimeException(String.format("Encountered null items in collection: %s", ids)); } } public ServiceResponse findByProperty(String propertyName, Object value) { return formulateResponse(dao.findByProperty(propertyName, value)); } public ServiceResponse findByPropertyRange(String propertyName, Object start, Object end) { return formulateResponse(dao.findByPropertyRange(propertyName, start, end)); } public ServiceResponse findByPropertyRange(String propertyName, Object start, Object end, Integer firstResult, Integer maxResults) { return formulateResponse(dao.findByPropertyRange(propertyName, start, end, firstResult, maxResults)); } public Integer getCountByPropertyRange(String propertyName, Object start, Object end) { return dao.getCountByRange(propertyName, start, end); } public ServiceResponse findByPropertyPaged(String propertyName, Object value, Integer firstResult, Integer maxResults) { return formulateResponse(dao.findByProperty(propertyName, value, firstResult, maxResults)); } public ServiceResponse findByProperties(String partitioningProperty, Map<String, Object> propertyNameValueMap, Integer firstResult, Integer maxResults) { return formulateResponse(dao.findByProperties(partitioningProperty, propertyNameValueMap, firstResult, maxResults)); } public Integer getCountByProperty(String propertyName, Object propertyValue) { return dao.getCount(propertyName, propertyValue); } public Integer getCountByProperties(String partitioningProperty, Map<String, Object> propertyNameValueMap, Integer firstResult, Integer maxResults) { return dao.getCountByProperties(partitioningProperty, propertyNameValueMap, firstResult, maxResults); } public Integer getCountByRange(String propertyName, Object minValue, Object maxValue) { return dao.getCountByRange(propertyName, minValue, maxValue); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object retValFromSuper = null; try { if (!Modifier.isAbstract(method.getModifiers())) { retValFromSuper = proxy.invokeSuper(obj, args); } } finally { } String name = method.getName(); if (name.equals("get")) return get(args[0]); else if (name.equals("exists")) return exists(args[0]); else if (name.equals("save")) return save(args[0]); else if (name.equals("saveAll")) { return saveAll(Arrays.asList((Object[]) args[0])); } else if (name.equals("delete")) return delete(args[0]); else if (name.equals("getPersistedClass")) return getPersistedClass(); else if (method.getDeclaringClass().equals(serviceClass)) return makeGeneralizedCall(obj, method, args); return retValFromSuper; } private Object makeGeneralizedCall(Object obj, final Method method, final Object[] args) { Collection<Entry<String, Object>> entries = null; IndexParamPairs indexParamPairs = AnnotationHelper.getAnnotationDeeply(method, IndexParamPairs.class); if (indexParamPairs != null) { // Collect method parameters pairs that represent an EntityIndexConfig property name and corresponding value int[] ints = indexParamPairs.value(); // java array stupidity Integer[] pairs = new Integer[ints.length]; for (int i : ints) pairs[i] = i; entries = Transform.map(new Unary<Entry<Integer, Integer>, Entry<String, Object>>() { public Entry<String, Object> f(Entry<Integer, Integer> indexPair) { String property = ((String) args[indexPair.getKey()]); Object value = PrimitiveUtils.parseString( config.getEntityIndexConfig(property).getIndexClass(), (String) args[indexPair.getValue()]); return new Pair<String, Object>(property, value); } }, new PairIterator<Integer>(Arrays.asList(pairs))); } else { // Collect method parameters that represent and EntityIndexConfig property value entries = Transform.map(new Unary<Integer, Entry<String, Object>>() { public Entry<String, Object> f(Integer index) { IndexParam annotation = AnnotationHelper.getMethodArgumentAnnotationDeeply(method, index - 1, IndexParam.class); return new Pair<String, Object>(config.getEntityIndexConfig(((IndexParam) annotation).value()).getPropertyName(), args[index - 1]); } }, Filter.grep(new Predicate<Integer>() { public boolean f(Integer index) { return AnnotationHelper.getMethodArgumentAnnotationDeeply(method, index - 1, IndexParam.class) != null; } }, new NumberIterator(args.length))); } IndexParamPagingPair indexParamPagingPair = AnnotationHelper.getAnnotationDeeply(method, IndexParamPagingPair.class); Entry<Integer, Integer> pagingPair = new Pair<Integer, Integer>(0, 0); if (indexParamPagingPair != null) { // Collect method parameters pairs that represent an EntityIndexConfig property name and corresponding value pagingPair = new Pair<Integer, Integer>( (Integer) args[indexParamPagingPair.startIndexIndex()], (Integer) args[indexParamPagingPair.maxResultsIndex()]); } if (entries.size() == 1) { Entry<String, Object> entry = Atom.getFirstOrThrow(entries); if (method.getName().equals("getCountByProperty")) return getCountByProperty(entry.getKey(), entry.getValue()); else if (method.getName().equals("findByProperty")) return findByProperty(entry.getKey(), entry.getValue()); } if (entries.size() == 2) { Entry<String, Object> entry1 = Atom.getFirstOrThrow(entries); Entry<String, Object> entry2 = Atom.getFirstOrThrow(Atom.getRestOrThrow(entries)); if (entry1.getKey().equals(entry2.getKey())) { if (method.getName().startsWith("getCountBy")) { return getCountByPropertyRange(entry1.getKey(), entry1.getValue(), entry2.getValue()); } return indexParamPagingPair != null ? findByPropertyRange(entry1.getKey(), entry1.getValue(), entry2.getValue(), pagingPair.getKey(), pagingPair.getValue()) : findByPropertyRange(entry1.getKey(), entry1.getValue(), entry2.getValue()); } } if (method.getName().startsWith("getCountBy") && method.getName().endsWith("Properties")) { return this.getCountByProperties(Atom.getFirstOrThrow(entries).getKey(), Transform.toOrderedMap(entries), pagingPair.getKey(), pagingPair.getValue()); } // TODO assumes the first parameter is the partitioning parameter. Use the annotation to specify return findByProperties(Atom.getFirstOrThrow(entries).getKey(), Transform.toOrderedMap(entries), pagingPair.getKey(), pagingPair.getValue()); } }