001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.isis.core.metamodel.services; 021 022import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg; 023import static org.hamcrest.CoreMatchers.is; 024import static org.hamcrest.CoreMatchers.not; 025import static org.hamcrest.CoreMatchers.nullValue; 026 027import java.lang.reflect.Field; 028import java.lang.reflect.InvocationTargetException; 029import java.lang.reflect.Method; 030import java.util.Arrays; 031import java.util.Collections; 032import java.util.List; 033import java.util.Map; 034 035import javax.inject.Inject; 036 037import com.google.common.base.Predicate; 038import com.google.common.collect.Iterables; 039import com.google.common.collect.Lists; 040import com.google.common.collect.Maps; 041 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045import org.apache.isis.applib.DomainObjectContainer; 046import org.apache.isis.core.commons.ensure.Assert; 047import org.apache.isis.core.commons.lang.ObjectExtensions; 048import org.apache.isis.core.commons.util.ToString; 049import org.apache.isis.core.metamodel.exceptions.MetaModelException; 050import org.apache.isis.core.metamodel.runtimecontext.ServicesInjectorAware; 051 052/** 053 * Must be a thread-safe. 054 */ 055public class ServicesInjectorDefault implements ServicesInjectorSpi { 056 057 private static final Logger LOG = LoggerFactory.getLogger(ServicesInjectorDefault.class); 058 059 /** 060 * If no key, not yet searched for type; otherwise the {@link List} indicates 061 * whether a service was found. 062 */ 063 private final Map<Class<?>, List<Object>> servicesByType = Maps.newHashMap(); 064 065 private DomainObjectContainer container; 066 private final List<Object> services = Lists.newArrayList(); 067 068 069 // ///////////////////////////////////////////////////////// 070 // Constructor, init, shutdown 071 // ///////////////////////////////////////////////////////// 072 073 074 @Override 075 public void init() { 076 autowireServicesAndContainer(); 077 } 078 079 @Override 080 public void shutdown() { 081 } 082 083 // ///////////////////////////////////////////////////////// 084 // Container 085 // ///////////////////////////////////////////////////////// 086 087 @Override 088 public DomainObjectContainer getContainer() { 089 return container; 090 } 091 092 @Override 093 public void setContainer(final DomainObjectContainer container) { 094 ensureThatArg(container, is(not(nullValue()))); 095 this.container = container; 096 autowireServicesAndContainer(); 097 } 098 099 // ///////////////////////////////////////////////////////// 100 // Services 101 // ///////////////////////////////////////////////////////// 102 103 @Override 104 public void setServices(final List<Object> services) { 105 this.services.clear(); 106 addServices(services); 107 autowireServicesAndContainer(); 108 } 109 110 @Override 111 public List<Object> getRegisteredServices() { 112 return Collections.unmodifiableList(services); 113 } 114 115 private void addServices(final List<Object> services) { 116 for (final Object service : services) { 117 if (service instanceof List) { 118 final List<Object> serviceList = ObjectExtensions.asListT(service, Object.class); 119 addServices(serviceList); 120 } else { 121 addService(service); 122 } 123 } 124 } 125 126 private boolean addService(final Object service) { 127 return services.add(service); 128 } 129 130 // ///////////////////////////////////////////////////////// 131 // Inject Dependencies 132 // ///////////////////////////////////////////////////////// 133 134 @Override 135 public void injectServicesInto(final Object object) { 136 Assert.assertNotNull("no container", container); 137 Assert.assertNotNull("no services", services); 138 139 final List<Object> servicesCopy = Lists.newArrayList(services); 140 servicesCopy.add(container); 141 injectServices(object, servicesCopy); 142 } 143 144 @Override 145 public void injectServicesInto(final List<Object> objects) { 146 for (final Object object : objects) { 147 injectServicesInto(object); 148 } 149 } 150 151 // //////////////////////////////////////////////////////////////////// 152 // injectInto 153 // //////////////////////////////////////////////////////////////////// 154 155 /** 156 * That is, injecting this injector... 157 */ 158 @Override 159 public void injectInto(final Object candidate) { 160 if (ServicesInjectorAware.class.isAssignableFrom(candidate.getClass())) { 161 final ServicesInjectorAware cast = ServicesInjectorAware.class.cast(candidate); 162 cast.setServicesInjector(this); 163 } 164 } 165 166 167 // ///////////////////////////////////////////////////////// 168 // Helpers 169 // ///////////////////////////////////////////////////////// 170 171 private static void injectServices(final Object object, final List<Object> services) { 172 final Class<?> cls = object.getClass(); 173 174 autowireViaFields(object, services, cls); 175 autowireViaPrefixedMethods(object, services, cls, "set"); 176 autowireViaPrefixedMethods(object, services, cls, "inject"); 177 } 178 179 private static void autowireViaFields(final Object object, final List<Object> services, final Class<?> cls) { 180 final List<Field> fields = Arrays.asList(cls.getDeclaredFields()); 181 final Iterable<Field> injectFields = Iterables.filter(fields, new Predicate<Field>() { 182 @Override 183 public boolean apply(Field input) { 184 final Inject annotation = input.getAnnotation(javax.inject.Inject.class); 185 return annotation != null; 186 } 187 }); 188 189 for (final Field field : injectFields) { 190 for (final Object service : services) { 191 final Class<?> serviceClass = service.getClass(); 192 boolean isInjectorField = isInjectorFieldFor(field, serviceClass); 193 if(isInjectorField) { 194 field.setAccessible(true); 195 invokeInjectorField(field, object, service); 196 } 197 } 198 } 199 200 // recurse up the hierarchy 201 final Class<?> superclass = cls.getSuperclass(); 202 if(superclass != null) { 203 autowireViaFields(object, services, superclass); 204 } 205 } 206 207 private static void autowireViaPrefixedMethods(final Object object, final List<Object> services, final Class<?> cls, final String prefix) { 208 final List<Method> methods = Arrays.asList(cls.getMethods()); 209 final Iterable<Method> prefixedMethods = Iterables.filter(methods, new Predicate<Method>(){ 210 public boolean apply(Method method) { 211 final String methodName = method.getName(); 212 return methodName.startsWith(prefix); 213 } 214 }); 215 216 for (final Method prefixedMethod : prefixedMethods) { 217 for (final Object service : services) { 218 final Class<?> serviceClass = service.getClass(); 219 boolean isInjectorMethod = isInjectorMethodFor(prefixedMethod, serviceClass); 220 if(isInjectorMethod) { 221 prefixedMethod.setAccessible(true); 222 invokeInjectorMethod(prefixedMethod, object, service); 223 } 224 } 225 } 226 } 227 228 private static boolean isInjectorFieldFor(final Field field, final Class<?> serviceClass) { 229 final Class<?> type = field.getType(); 230 // don't think that type can ever be null, but Javadoc for java.lang.reflect.Field doesn't say 231 return type != null && type.isAssignableFrom(serviceClass); 232 } 233 234 public static boolean isInjectorMethodFor(Method method, final Class<?> serviceClass) { 235 final String methodName = method.getName(); 236 if (methodName.startsWith("set") || methodName.startsWith("inject")) { 237 final Class<?>[] parameterTypes = method.getParameterTypes(); 238 if (parameterTypes.length == 1 && parameterTypes[0] != Object.class && parameterTypes[0].isAssignableFrom(serviceClass)) { 239 return true; 240 } 241 } 242 return false; 243 } 244 245 private static void invokeMethod(final Method method, final Object target, final Object[] parameters) { 246 try { 247 method.invoke(target, parameters); 248 } catch (final SecurityException e) { 249 throw new MetaModelException(String.format("Cannot access the %s method in %s", method.getName(), target.getClass().getName())); 250 } catch (final IllegalArgumentException e1) { 251 throw new MetaModelException(e1); 252 } catch (final IllegalAccessException e1) { 253 throw new MetaModelException(String.format("Cannot access the %s method in %s", method.getName(), target.getClass().getName())); 254 } catch (final InvocationTargetException e) { 255 final Throwable targetException = e.getTargetException(); 256 if (targetException instanceof RuntimeException) { 257 throw (RuntimeException) targetException; 258 } else { 259 throw new MetaModelException(targetException); 260 } 261 } 262 } 263 264 private static void invokeInjectorField(final Field field, final Object target, final Object parameter) { 265 try { 266 field.set(target, parameter); 267 } catch (IllegalArgumentException e) { 268 throw new MetaModelException(e); 269 } catch (IllegalAccessException e) { 270 throw new MetaModelException(String.format("Cannot access the %s field in %s", field.getName(), target.getClass().getName())); 271 } 272 if (LOG.isDebugEnabled()) { 273 LOG.debug("injected " + parameter + " into " + new ToString(target)); 274 } 275 } 276 277 private static void invokeInjectorMethod(final Method method, final Object target, final Object parameter) { 278 final Object[] parameters = new Object[] { parameter }; 279 invokeMethod(method, target, parameters); 280 if (LOG.isDebugEnabled()) { 281 LOG.debug("injected " + parameter + " into " + new ToString(target)); 282 } 283 } 284 285 private void autowireServicesAndContainer() { 286 injectServicesInto(this.services); 287 injectServicesInto(this.container); 288 } 289 290 @Override 291 public <T> T lookupService(Class<T> serviceClass) { 292 List<T> services = lookupServices(serviceClass); 293 return !services.isEmpty() ? services.get(0) : null; 294 } 295 296 @SuppressWarnings("unchecked") 297 @Override 298 public <T> List<T> lookupServices(Class<T> serviceClass) { 299 locateAndCache(serviceClass); 300 return (List<T>) servicesByType.get(serviceClass); 301 }; 302 303 private void locateAndCache(Class<?> serviceClass) { 304 if(servicesByType.containsKey(serviceClass)) { 305 return; 306 } 307 308 List<Object> matchingServices = Lists.newArrayList(); 309 addAssignableTo(serviceClass, services, matchingServices); 310 addAssignableTo(serviceClass, singletonListFor(container), matchingServices); 311 312 servicesByType.put(serviceClass, matchingServices); 313 } 314 315 private static List<Object> singletonListFor(Object obj) { 316 return obj!=null? Collections.singletonList(obj): Collections.emptyList(); 317 } 318 319 private static void addAssignableTo(Class<?> type, List<Object> candidates, List<Object> filteredServicesAndContainer) { 320 Iterable<Object> filteredServices = Iterables.filter(candidates, ofType(type)); 321 filteredServicesAndContainer.addAll(Lists.newArrayList(filteredServices)); 322 } 323 324 private static final Predicate<Object> ofType(final Class<?> cls) { 325 return new Predicate<Object>() { 326 @Override 327 public boolean apply(Object input) { 328 return cls.isAssignableFrom(input.getClass()); 329 } 330 }; 331 } 332 333}