/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.core.ping;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import org.eclipse.riena.core.util.Nop;
/**
* The {@code PingVisitor} is responsible for crawling down the <i>hierarchy</i>
* of pingable services. The entry point for starting a ping is method
* {@link #ping(IPingable) ping()}.
*/
public class PingVisitor {
protected final List<PingFingerprint> cycleDectector = new ArrayList<PingFingerprint>();
protected final List<PingResult> pingResultList = new ArrayList<PingResult>();
protected final Stack<PingResult> resultStack = new Stack<PingResult>();
private final String name;
/**
* Default Constructor
*/
public PingVisitor() {
name = "PingVisitor#" + System.identityHashCode(this); //$NON-NLS-1$
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return name;
}
/**
* Return a List of the {@link PingResult PingResult}s.
*
* @return a List of the {@link PingResult PingResult}s.
*/
public List<PingResult> getPingResults() {
return pingResultList;
}
/**
* Calls {@link IPingable#ping(PingVisitor) ping()} on all members that
* implement the {@link IPingable IPingable} interface. If you have
* additional IPingables that are not members, just provide them in a method
* <code>Iterable<IPingable> getAdditionalPingables()</code>. Also all
* methods that start with <code>ping</code> (see
* {@link #isPingMethod(IPingable, Method) isPingMethod()}) are called,
* which is useful to ping non-Java objects such as Databases ( e.g.
* <code>pingDatabase()</code>). If a cycle is detected, the method returns
* immediately.
*
* @param pingable
* the IPingable to visit.
* @return this.
*/
public PingVisitor visit(final IPingable pingable) {
PingVisitor visitor = this;
PingFingerprint fingerprint = null;
fingerprint = pingable.getPingFingerprint();
if (cycleDectector.contains(fingerprint)) {
// cycle detected
return visitor;
}
cycleDectector.add(fingerprint);
// do not recurse on PingMethodAdapter
if (pingable instanceof PingMethodAdapter) {
return visitor;
}
try {
final Collection<IPingable> children = getChildPingablesOf(pingable);
for (final IPingable child : children) {
visitor = visitor.ping(child);
}
} finally {
visitor.cycleDectector.remove(fingerprint);
}
return visitor;
}
/**
* This is the entry point to the pingable API. This method creates a
* {@link PingResult PingResult}, calls {@link IPingable#ping(PingVisitor)
* ping() } on the given pingable, catches any occurring Exception and
* reports it in the PingResult.
*
* @param pingable
* the {@link IPingable IPingable} to ping.
*
* @return the PingVisitor. It might not be 'this' (e.g. in case of remote
* calls where the visitor is serialized), so always work on the one
* returned by this method.
*/
public PingVisitor ping(final IPingable pingable) {
PingVisitor visitor = this;
PingResult pingResult = new PingResult(getPingableName(pingable));
if (resultStack.isEmpty()) {
// root of a new ping, add to list
pingResultList.add(pingResult);
} else {
final PingResult parent = resultStack.peek();
parent.addNestedResult(pingResult);
}
resultStack.push(pingResult);
Exception caughtException = null;
try {
visitor = pingable.ping(visitor);
} catch (final Exception e) {
caughtException = e;
} finally {
pingResult = visitor.resultStack.pop();
pingResult.setPingFailure(caughtException);
}
return visitor;
}
/**
* Returns all IPingable belonging to the given one.
*
* @param pingable
* @return all IPingable belonging to the given one.
*/
public Collection<IPingable> getChildPingablesOf(final IPingable pingable) {
final Set<IPingable> pingableList = new HashSet<IPingable>();
collectPingableMembers(pingable, pingableList);
collectPingMethods(pingable, pingableList);
collectAdditionalPingables(pingable, pingableList);
return pingableList;
}
/**
* Collects all <code>ping..()</code> methods.
*
* @param pingable
* @param pingableList
* @see #isPingMethod(IPingable, Method)
*/
public void collectPingMethods(final IPingable pingable, final Set<IPingable> pingableList) {
collectPingMethods(pingable.getClass(), pingable, pingableList);
}
private void collectPingMethods(final Class<?> clazz, final IPingable pingable, final Set<IPingable> pingableList) {
final Method[] methods = clazz.getDeclaredMethods();
for (final Method method : methods) {
if (isPingMethod(pingable, method)) {
setAccessible(method);
pingableList.add(new PingMethodAdapter(pingable, method));
}
}
final Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
collectPingMethods(superClass, pingable, pingableList);
}
}
/**
* Collects all IPingable members.
*
* @param pingable
* @param pingableList
*/
public void collectPingableMembers(final IPingable pingable, final Set<IPingable> pingableList) {
final Field[] fields = pingable.getClass().getDeclaredFields();
for (final Field field : fields) {
if (!IPingable.class.isAssignableFrom(field.getType())) {
continue;
}
try {
setAccessible(field);
if (field.get(pingable) == null) {
// skip null members
continue;
}
pingableList.add((IPingable) field.get(pingable));
} catch (final Exception e) {
pingableList.add(new UnavailablePingable(field.getName(),
"Pingable member " + field.getName() + " not accessible: " + e.getMessage())); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
/**
* Collects all IPingable provided by (optional) method
* <code>getAdditionalPingables()</code>..
*
* @param pingable
* @param pingableList
*/
public void collectAdditionalPingables(final IPingable pingable, final Set<IPingable> pingableList) {
collectAdditionalPingables(pingable.getClass(), pingable, pingableList);
}
private void collectAdditionalPingables(final Class<?> clazz, final IPingable pingable,
final Set<IPingable> pingableList) {
try {
final Method method = clazz.getDeclaredMethod("getAdditionalPingables", new Class[0]); //$NON-NLS-1$
setAccessible(method);
final Type returnType = method.getGenericReturnType();
if (isIterableOfPingables(returnType)) {
final Iterable<IPingable> pingables = (Iterable<IPingable>) method.invoke(pingable, new Object[0]);
for (final IPingable additionalPingable : pingables) {
pingableList.add(additionalPingable);
}
}
} catch (final NoSuchMethodException nsme) {
Nop.reason("no getAdditionalPingables() method"); //$NON-NLS-1$
} catch (final Exception e) {
pingableList.add(new UnavailablePingable("getAdditionalPingables", //$NON-NLS-1$
"Method getAdditionalPingables() not accessible: " + e.getMessage())); //$NON-NLS-1$
}
final Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
collectAdditionalPingables(superClass, pingable, pingableList);
}
}
private void setAccessible(final Method method) {
try {
method.setAccessible(true);
} catch (final SecurityException e) {
Nop.reason("security restriction, hopefully it's public :-|"); //$NON-NLS-1$
}
}
private void setAccessible(final Field field) {
try {
field.setAccessible(true);
} catch (final SecurityException e) {
Nop.reason("security restriction, hopefully it's public :-|"); //$NON-NLS-1$
}
}
private boolean isIterableOfPingables(final Type returnType) {
if (!(returnType instanceof ParameterizedType)) {
return false;
}
final ParameterizedType parameterizedType = (ParameterizedType) returnType;
if (!(parameterizedType.getRawType() == Iterable.class)) {
return false;
}
Type[] types = parameterizedType.getActualTypeArguments();
if (types.length != 1) {
return false;
}
Type type = types[0];
if (type instanceof WildcardType) {
types = ((WildcardType) type).getUpperBounds();
if (types.length != 1) {
return false;
}
type = types[0];
}
return (type instanceof Class<?>) && ((Class<?>) type).getName().equals(IPingable.class.getName());
}
/**
* Returns <code>true</code> if it is a <code>ping...()</code> method. A
* ping() method must start with <code>ping</code> followed by an upper case
* letter. It must be non-arg and returning void.
*
* @param pingable
* the Pingable object.
* @param method
* the method to check.
* @return <code>true</code> if it is a <code>ping...()</code> method.
*/
protected static boolean isPingMethod(final IPingable pingable, final Method method) {
if (!method.getName().startsWith("ping")) { //$NON-NLS-1$
return false;
}
if (method.getName().length() <= 4) {
return false;
}
if (!Character.isUpperCase(method.getName().charAt(4))) {
return false;
}
if (method.getParameterTypes().length != 0) {
return false;
}
if (method.getReturnType() != Void.TYPE) {
return false;
}
return true;
}
private static String getPingableName(final IPingable pingable) {
try {
return pingable.getPingFingerprint().getName();
} catch (final Exception e) {
return pingable.toString();
}
}
}