blob: 7d9e34f3677bb1d84eabc3467d082d6eac178174 [file] [log] [blame]
/*
* 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.managers.deployment;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicStampedReference;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.compute.ComputeTask;
import org.apache.ignite.configuration.DeploymentMode;
import org.apache.ignite.internal.processors.task.GridInternal;
import org.apache.ignite.internal.processors.task.GridVisorManagementTask;
import org.apache.ignite.internal.util.GridLeanSet;
import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter;
import org.apache.ignite.internal.util.lang.GridPeerDeployAware;
import org.apache.ignite.internal.util.lang.GridTuple;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteBiTuple;
import org.apache.ignite.lang.IgniteUuid;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents single class deployment.
*/
public class GridDeployment extends GridMetadataAwareAdapter implements GridDeploymentInfo {
/** Timestamp. */
private final long ts = U.currentTimeMillis();
/** Deployment mode. */
private final DeploymentMode depMode;
/** Class loader. */
private final ClassLoader clsLdr;
/** Class loader ID. */
private final IgniteUuid clsLdrId;
/** User version. */
private final String userVer;
/** Flag indicating local (non-p2p) deployment. */
private final boolean loc;
/** Sample class name.*/
private final String sampleClsName;
/** {@code True} if undeploy was scheduled. */
private volatile boolean pendingUndeploy;
/** Undeployed flag and current usage count. */
@GridToStringExclude
private final AtomicStampedReference<Boolean> usage = new AtomicStampedReference<>(false, 0);
/** Class annotations. */
@GridToStringExclude
private final ConcurrentMap<Class<?>,
ConcurrentMap<Class<? extends Annotation>, GridTuple<Annotation>>> anns =
new ConcurrentHashMap<>();
/** Classes. */
@GridToStringExclude
private final ConcurrentMap<String, Class<?>> clss = new ConcurrentHashMap<>();
/** Task classes 'internal' flags. */
@GridToStringExclude
private final ConcurrentMap<Class<?>, Boolean> internalTasks = new ConcurrentHashMap<>();
/** Field cache. */
@GridToStringExclude
private final ConcurrentMap<Class<?>, ConcurrentMap<Class<? extends Annotation>, Collection<Field>>> fieldCache =
new ConcurrentHashMap<>();
/** Method cache. */
@GridToStringExclude
private final ConcurrentMap<Class<?>, ConcurrentMap<Class<? extends Annotation>, Collection<Method>>> mtdCache =
new ConcurrentHashMap<>();
/** Default constructor cache. */
@GridToStringExclude
private final ConcurrentMap<Class<?>, GridTuple<Constructor<?>>> dfltCtorsCache =
new ConcurrentHashMap<>();
/**
* @param depMode Deployment mode.
* @param clsLdr Class loader.
* @param clsLdrId Class loader ID.
* @param userVer User version.
* @param sampleClsName Sample class name.
* @param loc {@code True} if local deployment.
*/
GridDeployment(DeploymentMode depMode, ClassLoader clsLdr, IgniteUuid clsLdrId, String userVer,
String sampleClsName, boolean loc) {
assert depMode != null;
assert clsLdr != null;
assert clsLdrId != null;
assert userVer != null;
assert sampleClsName != null;
this.clsLdr = clsLdr;
this.clsLdrId = clsLdrId;
this.userVer = userVer;
this.depMode = depMode;
this.sampleClsName = sampleClsName;
this.loc = loc;
}
/**
* Gets timestamp.
*
* @return Timestamp.
*/
public long timestamp() {
return ts;
}
/**
* @return Sample class name.
*/
public String sampleClassName() {
return sampleClsName;
}
/**
* Gets property depMode.
*
* @return Property depMode.
*/
@Override public DeploymentMode deployMode() {
return depMode;
}
/** {@inheritDoc} */
@Override public boolean localDeploymentOwner() {
return false;
}
/** {@inheritDoc} */
@Override public long sequenceNumber() {
return clsLdrId.localId();
}
/**
* @return Class loader.
*/
public ClassLoader classLoader() {
return clsLdr;
}
/**
* Gets property clsLdrId.
*
* @return Property clsLdrId.
*/
@Override public IgniteUuid classLoaderId() {
return clsLdrId;
}
/**
* Gets property userVer.
*
* @return Property userVer.
*/
@Override public String userVersion() {
return userVer;
}
/**
* @param name Either class name or alias.
* @return {@code True} if name is equal to either class name or alias.
*/
public boolean hasName(String name) {
assert name != null;
return clss.containsKey(name);
}
/**
* Gets property local.
*
* @return Property local.
*/
public boolean local() {
return loc;
}
/**
* Gets property undeployed.
*
* @return Property undeployed.
*/
public boolean undeployed() {
return usage.getReference();
}
/**
* Sets property undeployed.
*/
public void undeploy() {
int[] stamp = new int[1];
while (true) {
boolean undeployed = usage.get(stamp);
if (undeployed)
return;
int r = stamp[0];
if (usage.compareAndSet(false, true, r, r))
return;
}
}
/**
* Gets property pendingUndeploy.
*
* @return Property pendingUndeploy.
*/
public boolean pendingUndeploy() {
return pendingUndeploy;
}
/**
* Invoked whenever this deployment is scheduled to be undeployed.
* Used for handling obsolete or phantom requests.
*/
public void onUndeployScheduled() {
pendingUndeploy = true;
}
/**
* Increments usage count for deployment. If deployment is undeployed,
* then usage count is not incremented.
*
* @return {@code True} if deployment is still active.
*/
public boolean acquire() {
int[] stamp = new int[1];
while (true) {
boolean undeployed = usage.get(stamp);
int r = stamp[0];
if (undeployed && r == 0)
// Obsolete deployment.
return false;
if (usage.compareAndSet(undeployed, undeployed, r, r + 1))
return true;
}
}
/**
* Decrements usage count.
*/
public void release() {
int[] stamp = new int[1];
while (true) {
boolean undeployed = usage.get(stamp);
int r = stamp[0];
assert r > 0 : "Invalid usages count: " + r;
if (usage.compareAndSet(undeployed, undeployed, r, r - 1))
return;
}
}
/**
* Checks if deployment is obsolete, i.e. is not used and has been undeployed.
*
* @return {@code True} if deployment is obsolete.
*/
public boolean obsolete() {
int[] stamp = new int[1];
boolean undeployed = usage.get(stamp);
return undeployed && stamp[0] == 0;
}
/**
* @return Node participant map.
*/
@Nullable @Override public Map<UUID, IgniteUuid> participants() {
if (clsLdr instanceof GridDeploymentClassLoader)
return ((GridDeploymentInfo)clsLdr).participants();
return null;
}
/**
* Deployment callback.
*
* @param cls Deployed class.
*/
public void onDeployed(Class<?> cls) {
// No-op.
}
/**
* @param cls Class to get annotation for.
* @param annCls Annotation class.
* @return Annotation value.
* @param <T> Annotation class.
*/
public <T extends Annotation> T annotation(Class<?> cls, Class<T> annCls) {
ConcurrentMap<Class<? extends Annotation>, GridTuple<Annotation>> clsAnns = anns.get(cls);
if (clsAnns == null) {
ConcurrentMap<Class<? extends Annotation>, GridTuple<Annotation>> old = anns.putIfAbsent(cls,
clsAnns = new ConcurrentHashMap<>());
if (old != null)
clsAnns = old;
}
GridTuple<T> ann = (GridTuple<T>)clsAnns.get(annCls);
if (ann == null) {
ann = F.t(U.getAnnotation(cls, annCls));
clsAnns.putIfAbsent(annCls, (GridTuple<Annotation>)ann);
}
return ann.get();
}
/**
* Checks whether task class is annotated with {@link GridInternal}.
*
* @param task Task.
* @param taskCls Task class.
* @return {@code True} if task is internal.
*/
public boolean internalTask(@Nullable ComputeTask task, Class<?> taskCls) {
assert taskCls != null;
Boolean res = internalTasks.get(taskCls);
if (res == null) {
res = annotation(task instanceof GridPeerDeployAware ?
((GridPeerDeployAware)task).deployClass() : taskCls,
GridInternal.class) != null;
internalTasks.put(taskCls, res);
}
return res;
}
/**
* Checks whether task class is annotated with {@link GridVisorManagementTask}.
*
* @param task Task.
* @param taskCls Task class.
* @return {@code True} if task is internal.
*/
public boolean visorManagementTask(@Nullable ComputeTask task, @NotNull Class<?> taskCls) {
return annotation(task instanceof GridPeerDeployAware ?
((GridPeerDeployAware)task).deployClass() : taskCls,
GridVisorManagementTask.class) != null;
}
/**
* @param cls Class to create new instance of (using default constructor).
* @return New instance.
* @throws IgniteCheckedException If failed.
*/
@Nullable public <T> T newInstance(Class<T> cls) throws IgniteCheckedException {
assert cls != null;
GridTuple<Constructor<?>> t = dfltCtorsCache.get(cls);
if (t == null) {
try {
Constructor<T> ctor = cls.getDeclaredConstructor();
if (ctor != null && !ctor.isAccessible())
ctor.setAccessible(true);
dfltCtorsCache.putIfAbsent(cls, t = F.<Constructor<?>>t(ctor));
}
catch (NoSuchMethodException e) {
throw new IgniteCheckedException("Failed to find empty constructor for class: " + cls, e);
}
}
Constructor<?> ctor = t.get();
if (ctor == null)
return null;
try {
return (T)ctor.newInstance();
}
catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
throw new IgniteCheckedException("Failed to create new instance for class: " + cls, e);
}
}
/**
* @param clsName Class name to check.
* @return Class for given name if it was previously deployed.
*/
public Class<?> existingDeployedClass(String clsName) {
return clss.get(clsName);
}
/**
* Gets class for a name.
*
* @param clsName Class name.
* @param alias Optional array of aliases.
* @return Class for given name.
*/
@SuppressWarnings({"StringEquality"})
@Nullable public Class<?> deployedClass(String clsName, String... alias) {
Class<?> cls = clss.get(clsName);
if (cls == null) {
try {
cls = U.forName(clsName, clsLdr);
Class<?> cur = clss.putIfAbsent(clsName, cls);
if (cur == null) {
for (String a : alias) {
clss.putIfAbsent(a, cls);
}
onDeployed(cls);
}
}
catch (ClassNotFoundException ignored) {
// Check aliases.
for (String a : alias) {
cls = clss.get(a);
if (cls != null)
return cls;
else if (!a.equals(clsName)) {
try {
cls = U.forName(a, clsLdr);
}
catch (ClassNotFoundException ignored0) {
continue;
}
Class<?> cur = clss.putIfAbsent(a, cls);
if (cur == null) {
for (String a1 : alias) {
// The original alias has already been put into the map,
// so we don't try to put it again here.
if (a1 != a)
clss.putIfAbsent(a1, cls);
}
onDeployed(cls);
}
return cls;
}
}
}
catch (IgniteException e) {
if (!X.hasCause(e, TimeoutException.class))
throw e;
}
}
return cls;
}
/**
* Adds deployed class together with aliases.
*
* @param cls Deployed class.
* @param aliases Class aliases.
* @return {@code True} if class was added.
*/
public boolean addDeployedClass(Class<?> cls, String... aliases) {
boolean res = false;
if (cls != null) {
Class<?> cur = clss.putIfAbsent(cls.getName(), cls);
if (cur == null) {
onDeployed(cls);
res = true;
}
for (String alias : aliases) {
if (alias != null)
clss.putIfAbsent(alias, cls);
}
}
return res;
}
/**
* @return Deployed classes.
*/
public Collection<Class<?>> deployedClasses() {
return Collections.unmodifiableCollection(clss.values());
}
/**
* @return Deployed class map, keyed by class name or alias.
*/
public Map<String, Class<?>> deployedClassMap() {
return Collections.unmodifiableMap(clss);
}
/**
* Gets value of annotated field or method.
*
* @param target Object to find a value in.
* @param annCls Annotation class.
* @return Value of annotated field or method.
* @throws IgniteCheckedException If failed to find.
*/
@Nullable public Object annotatedValue(Object target, Class<? extends Annotation> annCls) throws IgniteCheckedException {
return annotatedValue(target, annCls, null, false).get1();
}
/**
* @param target Object to find a value in.
* @param annCls Annotation class.
* @param visited Set of visited objects to avoid cycling.
* @param annFound Flag indicating if value has already been found.
* @return Value of annotated field or method.
* @throws IgniteCheckedException If failed to find.
*/
private IgniteBiTuple<Object, Boolean> annotatedValue(Object target, Class<? extends Annotation> annCls,
@Nullable Set<Object> visited, boolean annFound) throws IgniteCheckedException {
assert target != null;
// To avoid infinite recursion.
if (visited != null && visited.contains(target))
return F.t(null, annFound);
Object val = null;
for (Class<?> cls = target.getClass(); !cls.equals(Object.class); cls = cls.getSuperclass()) {
// Fields.
for (Field f : fieldsWithAnnotation(cls, annCls)) {
f.setAccessible(true);
Object fieldVal;
try {
fieldVal = f.get(target);
}
catch (IllegalAccessException e) {
throw new IgniteCheckedException("Failed to get annotated field value [cls=" + cls.getName() +
", ann=" + annCls.getSimpleName(), e);
}
if (needsRecursion(f)) {
if (fieldVal != null) {
if (visited == null)
visited = new GridLeanSet<>();
visited.add(target);
// Recursion.
IgniteBiTuple<Object, Boolean> tup = annotatedValue(fieldVal, annCls, visited, annFound);
if (!annFound && tup.get2())
// Update value only if annotation was found in recursive call.
val = tup.get1();
annFound = tup.get2();
}
}
else {
if (annFound)
throw new IgniteCheckedException("Multiple annotations have been found [cls=" + cls.getName() +
", ann=" + annCls.getSimpleName() + "]");
val = fieldVal;
annFound = true;
}
}
// Methods.
for (Method m : methodsWithAnnotation(cls, annCls)) {
if (annFound)
throw new IgniteCheckedException("Multiple annotations have been found [cls=" + cls.getName() +
", ann=" + annCls.getSimpleName() + "]");
m.setAccessible(true);
try {
val = m.invoke(target);
}
catch (Exception e) {
throw new IgniteCheckedException("Failed to get annotated method value [cls=" + cls.getName() +
", ann=" + annCls.getSimpleName(), e);
}
annFound = true;
}
}
return F.t(val, annFound);
}
/**
* Gets all entries from the specified class or its super-classes that have
* been annotated with annotation provided.
*
* @param cls Class in which search for methods.
* @param annCls Annotation.
* @return Set of entries with given annotations.
*/
private Iterable<Field> fieldsWithAnnotation(Class<?> cls, Class<? extends Annotation> annCls) {
assert cls != null;
assert annCls != null;
Collection<Field> fields = fieldsFromCache(cls, annCls);
if (fields == null) {
fields = new ArrayList<>();
for (Field field : cls.getDeclaredFields()) {
Annotation ann = field.getAnnotation(annCls);
if (ann != null || needsRecursion(field))
fields.add(field);
}
cacheFields(cls, annCls, fields);
}
return fields;
}
/**
* Gets set of methods with given annotation.
*
* @param cls Class in which search for methods.
* @param annCls Annotation.
* @return Set of methods with given annotations.
*/
private Iterable<Method> methodsWithAnnotation(Class<?> cls, Class<? extends Annotation> annCls) {
assert cls != null;
assert annCls != null;
Collection<Method> mtds = methodsFromCache(cls, annCls);
if (mtds == null) {
mtds = new ArrayList<>();
for (Method mtd : cls.getDeclaredMethods()) {
Annotation ann = mtd.getAnnotation(annCls);
if (ann != null)
mtds.add(mtd);
}
cacheMethods(cls, annCls, mtds);
}
return mtds;
}
/**
* @param f Field.
* @return {@code true} if recursive inspection is required.
*/
private boolean needsRecursion(Field f) {
assert f != null;
// Need to inspect anonymous classes, callable and runnable instances.
return f.getName().startsWith("this$") || f.getName().startsWith("val$") ||
Callable.class.isAssignableFrom(f.getType()) || Runnable.class.isAssignableFrom(f.getType());
}
/**
* Gets all fields for a given class with given annotation from cache.
*
* @param cls Class to get fields from.
* @param annCls Annotation class for fields.
* @return List of fields with given annotation, possibly {@code null}.
*/
@Nullable private Collection<Field> fieldsFromCache(Class<?> cls, Class<? extends Annotation> annCls) {
assert cls != null;
assert annCls != null;
Map<Class<? extends Annotation>, Collection<Field>> annCache = fieldCache.get(cls);
return annCache != null ? annCache.get(annCls) : null;
}
/**
* Caches list of fields with given annotation from given class.
*
* @param cls Class the fields belong to.
* @param annCls Annotation class for the fields.
* @param fields Fields to cache.
*/
private void cacheFields(Class<?> cls, Class<? extends Annotation> annCls, Collection<Field> fields) {
assert cls != null;
assert annCls != null;
assert fields != null;
Map<Class<? extends Annotation>, Collection<Field>> annFields = F.addIfAbsent(fieldCache,
cls, F.<Class<? extends Annotation>, Collection<Field>>newCMap());
assert annFields != null;
annFields.put(annCls, fields);
}
/**
* Gets all methods for a given class with given annotation from cache.
*
* @param cls Class to get methods from.
* @param annCls Annotation class for fields.
* @return List of methods with given annotation, possibly {@code null}.
*/
@Nullable private Collection<Method> methodsFromCache(Class<?> cls, Class<? extends Annotation> annCls) {
assert cls != null;
assert annCls != null;
Map<Class<? extends Annotation>, Collection<Method>> annCache = mtdCache.get(cls);
return annCache != null ? annCache.get(annCls) : null;
}
/**
* Caches list of methods with given annotation from given class.
*
* @param cls Class the fields belong to.
* @param annCls Annotation class for the fields.
* @param mtds Methods to cache.
*/
private void cacheMethods(Class<?> cls, Class<? extends Annotation> annCls,
Collection<Method> mtds) {
assert cls != null;
assert annCls != null;
assert mtds != null;
Map<Class<? extends Annotation>, Collection<Method>> annMtds = F.addIfAbsent(mtdCache,
cls, F.<Class<? extends Annotation>, Collection<Method>>newCMap());
assert annMtds != null;
annMtds.put(annCls, mtds);
}
/** {@inheritDoc} */
@Override public String toString() {
int[] stamp = new int[1];
boolean undeployed = usage.get(stamp);
return S.toString(GridDeployment.class, this, "undeployed", undeployed, "usage", stamp[0]);
}
}