| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.ignite.internal.processors.resource; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.atomic.AtomicReference; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.internal.managers.deployment.GridDeployment; |
| import org.apache.ignite.internal.util.GridLeanIdentitySet; |
| import org.apache.ignite.internal.util.IgniteUtils; |
| import org.apache.ignite.internal.util.typedef.F; |
| import org.apache.ignite.internal.util.typedef.T2; |
| import org.apache.ignite.internal.util.typedef.X; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.resources.CacheNameResource; |
| import org.apache.ignite.resources.CacheStoreSessionResource; |
| import org.apache.ignite.resources.FileSystemResource; |
| import org.apache.ignite.resources.IgniteInstanceResource; |
| import org.apache.ignite.resources.JobContextResource; |
| import org.apache.ignite.resources.LoadBalancerResource; |
| import org.apache.ignite.resources.LoggerResource; |
| import org.apache.ignite.resources.ServiceResource; |
| import org.apache.ignite.resources.SpringApplicationContextResource; |
| import org.apache.ignite.resources.SpringResource; |
| import org.apache.ignite.resources.TaskContinuousMapperResource; |
| import org.apache.ignite.resources.TaskSessionResource; |
| import org.jetbrains.annotations.Nullable; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| /** |
| * Resource container contains caches for classes used for injection. |
| * Caches used to improve the efficiency of standard Java reflection mechanism. |
| */ |
| public class GridResourceIoc { |
| /** Task class resource mapping. Used to efficiently cleanup resources related to class loader. */ |
| private final ConcurrentMap<ClassLoader, Set<Class<?>>> taskMap = new ConcurrentHashMap<>(); |
| |
| /** Class descriptors cache. */ |
| private AtomicReference<Map<Class<?>, ClassDescriptor>> clsDescs = new AtomicReference<>(); |
| |
| /** |
| * @param ldr Class loader. |
| */ |
| void onUndeployed(ClassLoader ldr) { |
| Set<Class<?>> clss = taskMap.remove(ldr); |
| |
| if (clss != null) { |
| Map<Class<?>, ClassDescriptor> newMap, oldMap; |
| |
| do { |
| oldMap = clsDescs.get(); |
| |
| if (oldMap == null) |
| break; |
| |
| newMap = new HashMap<>(oldMap.size() - clss.size()); |
| |
| for (Map.Entry<Class<?>, ClassDescriptor> entry : oldMap.entrySet()) { |
| if (!clss.contains(entry.getKey())) |
| newMap.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| while (!clsDescs.compareAndSet(oldMap, newMap)); |
| } |
| } |
| |
| /** |
| * Clears all internal caches. |
| */ |
| void undeployAll() { |
| taskMap.clear(); |
| |
| clsDescs.set(null); |
| } |
| |
| /** |
| * Injects given resource via field or setter with specified annotations on provided target object. |
| * |
| * @param target Target object. |
| * @param annCls Setter annotation. |
| * @param injector Resource to inject. |
| * @param dep Deployment. |
| * @param depCls Deployment class. |
| * @return {@code True} if resource was injected. |
| * @throws IgniteCheckedException Thrown in case of any errors during injection. |
| */ |
| @SuppressWarnings("SimplifiableIfStatement") |
| boolean inject(Object target, |
| Class<? extends Annotation> annCls, |
| GridResourceInjector injector, |
| @Nullable GridDeployment dep, |
| @Nullable Class<?> depCls) |
| throws IgniteCheckedException { |
| return injectInternal(target, annCls, injector, dep, depCls, null); |
| } |
| |
| /** |
| * @param dep Deployment. |
| * @param cls Class. |
| * @return Descriptor. |
| */ |
| ClassDescriptor descriptor(@Nullable GridDeployment dep, Class<?> cls) { |
| Map<Class<?>, ClassDescriptor> newMap, oldMap; |
| ClassDescriptor res, newDesc = null; |
| |
| do { |
| oldMap = clsDescs.get(); |
| |
| if (oldMap != null && (res = oldMap.get(cls)) != null) |
| break; |
| |
| if (dep != null) { |
| Set<Class<?>> classes = F.addIfAbsent(taskMap, dep.classLoader(), F.<Class<?>>newCSet()); |
| |
| classes.add(cls); |
| |
| dep = null; |
| } |
| |
| if (oldMap == null) |
| newMap = new HashMap<>(); |
| else |
| (newMap = new HashMap<>(oldMap.size() + 1)).putAll(oldMap); |
| |
| newMap.put(cls, res = newDesc == null ? (newDesc = new ClassDescriptor(cls)) : newDesc); |
| } |
| while (!clsDescs.compareAndSet(oldMap, newMap)); |
| |
| return res; |
| } |
| |
| /** |
| * @param target Target object. |
| * @param annCls Setter annotation. |
| * @param injector Resource to inject. |
| * @param dep Deployment. |
| * @param depCls Deployment class. |
| * @param checkedObjs Set of already inspected objects to avoid indefinite recursion. |
| * @return {@code True} if resource was injected. |
| * @throws IgniteCheckedException Thrown in case of any errors during injection. |
| */ |
| private boolean injectInternal(Object target, |
| Class<? extends Annotation> annCls, |
| GridResourceInjector injector, |
| @Nullable GridDeployment dep, |
| @Nullable Class<?> depCls, |
| @Nullable Set<Object> checkedObjs) |
| throws IgniteCheckedException { |
| Class<?> targetCls = target.getClass(); |
| |
| ClassDescriptor descr = descriptor(dep, targetCls); |
| |
| T2<GridResourceField[], GridResourceMethod[]> annotatedMembers = descr.annotatedMembers(annCls); |
| |
| return descr.injectInternal(target, annCls, annotatedMembers, injector, dep, depCls, checkedObjs); |
| } |
| |
| /** |
| * Checks if annotation is presented on a field or method of the specified object. |
| * |
| * @param target Target object. |
| * @param annCls Annotation class to find on fields or methods of target object. |
| * @param dep Deployment. |
| * @return {@code true} if annotation is presented, {@code false} if it's not. |
| */ |
| boolean isAnnotationPresent(Object target, Class<? extends Annotation> annCls, @Nullable GridDeployment dep) { |
| assert target != null; |
| assert annCls != null; |
| |
| ClassDescriptor desc = descriptor(dep, target.getClass()); |
| |
| return desc.recursiveFields().length > 0 || desc.annotatedMembers(annCls) != null; |
| } |
| |
| /** |
| * Checks if annotation is presented on a field or method of the specified object. |
| * |
| * @param target Target object. |
| * @param annSet Annotation classes to find on fields or methods of target object. |
| * @param dep Deployment. |
| * @return {@code true} if any annotation is presented, {@code false} if it's not. |
| */ |
| boolean isAnnotationsPresent(@Nullable GridDeployment dep, Object target, AnnotationSet annSet) { |
| assert target != null; |
| assert annSet != null; |
| |
| return descriptor(dep, target.getClass()).isAnnotated(annSet) != 0; |
| } |
| |
| /** |
| * Gets set of methods with given annotation. |
| * |
| * @param dep Deployment. |
| * @param cls Class in which search for methods. |
| * @param annCls Annotation. |
| * @return Set of methods with given annotations. |
| */ |
| GridResourceMethod[] getMethodsWithAnnotation(@Nullable GridDeployment dep, Class<?> cls, |
| Class<? extends Annotation> annCls) { |
| ClassDescriptor desc = descriptor(dep, cls); |
| |
| T2<GridResourceField[], GridResourceMethod[]> t2 = desc.annotatedMembers(annCls); |
| |
| return t2 == null ? GridResourceMethod.EMPTY_ARRAY : t2.get2(); |
| } |
| |
| /** Print memory statistics */ |
| public void printMemoryStats() { |
| X.println(">>> taskMapSize: " + taskMap.size()); |
| |
| Map<Class<?>, ClassDescriptor> map = clsDescs.get(); |
| X.println(">>> classDescriptorsCacheSize: " + (map == null ? 0 : map.size())); |
| } |
| |
| /** |
| * |
| */ |
| class ClassDescriptor { |
| /** */ |
| private final Field[] recursiveFields; |
| |
| /** */ |
| private final Map<Class<? extends Annotation>, T2<GridResourceField[], GridResourceMethod[]>> annMap; |
| |
| /** |
| * Uses as enum-map with enum {@link AnnotationSet} member as key, |
| * and bitmap as a result of matching found annotations with enum set {@link ResourceAnnotation} as value. |
| */ |
| private final int[] containsAnnSets; |
| |
| /** Uses as enum-map with enum {@link ResourceAnnotation} member as a keys. */ |
| private final T2<GridResourceField[], GridResourceMethod[]>[] annArr; |
| |
| /** |
| * @param cls Class. |
| */ |
| @SuppressWarnings("unchecked") |
| ClassDescriptor(Class<?> cls) { |
| Map<Class<? extends Annotation>, T2<List<GridResourceField>, List<GridResourceMethod>>> annMap |
| = new HashMap<>(); |
| |
| List<Field> recursiveFieldsList = new ArrayList<>(); |
| |
| boolean allowImplicitInjection = !GridNoImplicitInjection.class.isAssignableFrom(cls); |
| |
| for (Class cls0 = cls; !cls0.equals(Object.class); cls0 = cls0.getSuperclass()) { |
| for (Field field : cls0.getDeclaredFields()) { |
| Annotation[] fieldAnns = field.getAnnotations(); |
| |
| for (Annotation ann : fieldAnns) { |
| T2<List<GridResourceField>, List<GridResourceMethod>> t2 = annMap.get(ann.annotationType()); |
| |
| if (t2 == null) { |
| t2 = new T2<List<GridResourceField>, List<GridResourceMethod>>( |
| new ArrayList<GridResourceField>(), |
| new ArrayList<GridResourceMethod>()); |
| |
| annMap.put(ann.annotationType(), t2); |
| } |
| |
| t2.get1().add(new GridResourceField(field, ann)); |
| } |
| |
| if (allowImplicitInjection |
| && fieldAnns.length == 0 |
| && GridResourceUtils.mayRequireResources(field)) { |
| field.setAccessible(true); |
| |
| // Account for anonymous inner classes. |
| recursiveFieldsList.add(field); |
| } |
| } |
| |
| for (Method mtd : cls0.getDeclaredMethods()) { |
| for (Annotation ann : mtd.getAnnotations()) { |
| T2<List<GridResourceField>, List<GridResourceMethod>> t2 = annMap.get(ann.annotationType()); |
| |
| if (t2 == null) { |
| t2 = new T2<List<GridResourceField>, List<GridResourceMethod>>( |
| new ArrayList<GridResourceField>(), |
| new ArrayList<GridResourceMethod>()); |
| |
| annMap.put(ann.annotationType(), t2); |
| } |
| |
| t2.get2().add(new GridResourceMethod(mtd, ann)); |
| } |
| } |
| } |
| |
| recursiveFields = recursiveFieldsList.isEmpty() ? U.EMPTY_FIELDS |
| : recursiveFieldsList.toArray(new Field[recursiveFieldsList.size()]); |
| |
| this.annMap = IgniteUtils.limitedMap(annMap.size()); |
| |
| for (Map.Entry<Class<? extends Annotation>, T2<List<GridResourceField>, List<GridResourceMethod>>> entry |
| : annMap.entrySet()) { |
| GridResourceField[] fields = GridResourceField.toArray(entry.getValue().get1()); |
| GridResourceMethod[] mtds = GridResourceMethod.toArray(entry.getValue().get2()); |
| |
| this.annMap.put(entry.getKey(), new T2<>(fields, mtds)); |
| } |
| |
| T2<GridResourceField[], GridResourceMethod[]>[] annArr = null; |
| |
| if (annMap.isEmpty()) |
| containsAnnSets = null; |
| else { |
| int annotationsBits = 0; |
| |
| for (ResourceAnnotation ann : ResourceAnnotation.values()) { |
| T2<GridResourceField[], GridResourceMethod[]> member = annotatedMembers(ann.clazz); |
| |
| if (member != null) { |
| if (annArr == null) |
| annArr = new T2[ResourceAnnotation.values().length]; |
| |
| annArr[ann.ordinal()] = member; |
| |
| annotationsBits |= 1 << ann.ordinal(); |
| } |
| } |
| |
| AnnotationSet[] annotationSets = AnnotationSet.values(); |
| |
| containsAnnSets = new int[annotationSets.length]; |
| |
| for (int i = 0; i < annotationSets.length; i++) |
| containsAnnSets[i] = annotationsBits & annotationSets[i].annotationsBitSet; |
| } |
| |
| this.annArr = annArr; |
| } |
| |
| /** |
| * @return Recursive fields. |
| */ |
| Field[] recursiveFields() { |
| return recursiveFields; |
| } |
| |
| /** |
| * @param annCls Annotation class. |
| * @return Fields. |
| */ |
| @Nullable T2<GridResourceField[], GridResourceMethod[]> annotatedMembers(Class<? extends Annotation> annCls) { |
| return annMap.get(annCls); |
| } |
| |
| /** |
| * @param set annotation set. |
| * @return {@code Bitmask} > 0 if any annotation is presented, otherwise return 0; |
| */ |
| int isAnnotated(AnnotationSet set) { |
| return recursiveFields.length > 0 ? set.annotationsBitSet : |
| (containsAnnSets == null ? 0 : containsAnnSets[set.ordinal()]); |
| } |
| |
| /** |
| * @param ann Annotation. |
| * @return {@code True} if annotation is presented. |
| */ |
| boolean isAnnotated(ResourceAnnotation ann) { |
| return recursiveFields.length > 0 || (annArr != null && annArr[ann.ordinal()] != null); |
| } |
| |
| /** |
| * @param target Target object. |
| * @param annCls Annotation class. |
| * @param annotatedMembers Setter annotation. |
| * @param injector Resource to inject. |
| * @param dep Deployment. |
| * @param depCls Deployment class. |
| * @param checkedObjs Set of already inspected objects to avoid indefinite recursion. |
| * @return {@code True} if resource was injected. |
| * @throws IgniteCheckedException Thrown in case of any errors during injection. |
| */ |
| boolean injectInternal(Object target, |
| Class<? extends Annotation> annCls, |
| T2<GridResourceField[], GridResourceMethod[]> annotatedMembers, |
| GridResourceInjector injector, |
| @Nullable GridDeployment dep, |
| @Nullable Class<?> depCls, |
| @Nullable Set<Object> checkedObjs) |
| throws IgniteCheckedException { |
| if (recursiveFields.length == 0 && annotatedMembers == null) |
| return false; |
| |
| if (checkedObjs == null && recursiveFields.length > 0) |
| checkedObjs = new GridLeanIdentitySet<>(); |
| |
| if (checkedObjs != null && !checkedObjs.add(target)) |
| return false; |
| |
| boolean injected = false; |
| |
| for (Field field : recursiveFields) { |
| try { |
| Object obj = field.get(target); |
| |
| if (obj != null) { |
| assert checkedObjs != null; |
| |
| ClassDescriptor desc = descriptor(dep, obj.getClass()); |
| injected |= desc.injectInternal(obj, annCls, desc.annotatedMembers(annCls), |
| injector, dep, depCls, checkedObjs); |
| } |
| } |
| catch (IllegalAccessException e) { |
| throw new IgniteCheckedException("Failed to inject resource [field=" + field.getName() + |
| ", target=" + target + ']', e); |
| } |
| } |
| |
| if (annotatedMembers != null) { |
| for (GridResourceField field : annotatedMembers.get1()) { |
| injector.inject(field, target, depCls, dep); |
| |
| injected = true; |
| } |
| |
| for (GridResourceMethod mtd : annotatedMembers.get2()) { |
| injector.inject(mtd, target, depCls, dep); |
| |
| injected = true; |
| } |
| } |
| |
| return injected; |
| } |
| |
| /** |
| * @param target Target object. |
| * @param ann Setter annotation. |
| * @param injector Resource to inject. |
| * @param dep Deployment. |
| * @param depCls Deployment class. |
| * @return {@code True} if resource was injected. |
| * @throws IgniteCheckedException Thrown in case of any errors during injection. |
| */ |
| public boolean inject(Object target, |
| ResourceAnnotation ann, |
| GridResourceInjector injector, |
| @Nullable GridDeployment dep, |
| @Nullable Class<?> depCls) |
| throws IgniteCheckedException { |
| return injectInternal(target, |
| ann.clazz, |
| annArr == null ? null : annArr[ann.ordinal()], |
| injector, |
| dep, |
| depCls, |
| null); |
| } |
| } |
| |
| /** |
| * |
| */ |
| enum ResourceAnnotation { |
| /** */ |
| CACHE_NAME(CacheNameResource.class), |
| |
| /** */ |
| SPRING_APPLICATION_CONTEXT(SpringApplicationContextResource.class), |
| |
| /** */ |
| SPRING(SpringResource.class), |
| |
| /** */ |
| IGNITE_INSTANCE(IgniteInstanceResource.class), |
| |
| /** */ |
| LOGGER(LoggerResource.class), |
| |
| /** */ |
| SERVICE(ServiceResource.class), |
| |
| /** */ |
| TASK_SESSION(TaskSessionResource.class), |
| |
| /** */ |
| LOAD_BALANCER(LoadBalancerResource.class), |
| |
| /** */ |
| TASK_CONTINUOUS_MAPPER(TaskContinuousMapperResource.class), |
| |
| /** */ |
| JOB_CONTEXT(JobContextResource.class), |
| |
| /** */ |
| CACHE_STORE_SESSION(CacheStoreSessionResource.class), |
| |
| /** */ |
| FILESYSTEM_RESOURCE(FileSystemResource.class); |
| |
| /** */ |
| public final Class<? extends Annotation> clazz; |
| |
| /** |
| * @param clazz annotation class. |
| */ |
| ResourceAnnotation(Class<? extends Annotation> clazz) { |
| this.clazz = clazz; |
| } |
| } |
| |
| /** |
| * |
| */ |
| public enum AnnotationSet { |
| /** */ |
| GENERIC( |
| ResourceAnnotation.SPRING_APPLICATION_CONTEXT, |
| ResourceAnnotation.SPRING, |
| ResourceAnnotation.IGNITE_INSTANCE, |
| ResourceAnnotation.LOGGER, |
| ResourceAnnotation.SERVICE |
| ), |
| |
| /** */ |
| ENTRY_PROCESSOR( |
| ResourceAnnotation.CACHE_NAME, |
| |
| ResourceAnnotation.SPRING_APPLICATION_CONTEXT, |
| ResourceAnnotation.SPRING, |
| ResourceAnnotation.IGNITE_INSTANCE, |
| ResourceAnnotation.LOGGER, |
| ResourceAnnotation.SERVICE |
| ), |
| |
| /** */ |
| TASK( |
| ResourceAnnotation.TASK_SESSION, |
| ResourceAnnotation.LOAD_BALANCER, |
| ResourceAnnotation.TASK_CONTINUOUS_MAPPER, |
| |
| ResourceAnnotation.SPRING_APPLICATION_CONTEXT, |
| ResourceAnnotation.SPRING, |
| ResourceAnnotation.IGNITE_INSTANCE, |
| ResourceAnnotation.LOGGER, |
| ResourceAnnotation.SERVICE |
| ), |
| |
| /** */ |
| JOB( |
| ResourceAnnotation.TASK_SESSION, |
| ResourceAnnotation.JOB_CONTEXT, |
| |
| ResourceAnnotation.SPRING_APPLICATION_CONTEXT, |
| ResourceAnnotation.SPRING, |
| ResourceAnnotation.IGNITE_INSTANCE, |
| ResourceAnnotation.LOGGER, |
| ResourceAnnotation.SERVICE |
| ); |
| |
| /** Resource annotations bits for fast checks. */ |
| public final int annotationsBitSet; |
| |
| /** Holds annotations in order */ |
| public final ResourceAnnotation[] annotations; |
| |
| /** |
| * @param annotations ResourceAnnotations. |
| */ |
| AnnotationSet(ResourceAnnotation... annotations) { |
| assert annotations.length < 32 : annotations.length; |
| |
| this.annotations = annotations; |
| |
| int mask = 0; |
| |
| for (ResourceAnnotation ann : annotations) |
| mask |= 1 << ann.ordinal(); |
| |
| annotationsBitSet = mask; |
| } |
| } |
| } |