SnmpTools.java
package eu.linuxengineering.snmp;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import eu.javaexperience.collection.map.SmallMap;
import eu.javaexperience.interfaces.simple.SimpleGet;
import eu.javaexperience.interfaces.simple.getBy.GetBy1;
import eu.javaexperience.interfaces.simple.getBy.GetBy2;
import eu.javaexperience.interfaces.simple.getBy.GetBy3;
import eu.javaexperience.log.JavaExperienceLoggingFacility;
import eu.javaexperience.log.LogLevel;
import eu.javaexperience.log.Loggable;
import eu.javaexperience.log.Logger;
import eu.javaexperience.log.LoggingTools;
import eu.javaexperience.reflect.CastTo;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.reflect.PrimitiveTools;
import eu.javaexperience.semantic.references.MayNull;
import eu.javaexperience.reflect.Mirror.BelongTo;
import eu.javaexperience.reflect.Mirror.ClassData;
import eu.javaexperience.reflect.Mirror.MethodSelector;
import eu.javaexperience.reflect.Mirror.Select;
import eu.javaexperience.reflect.Mirror.Visibility;
import eu.javaexperience.text.Format;
import eu.javaexperience.text.StringTools;
import eu.linuxengineering.snmp.annotations.SnmpAlternativeElementsCount;
import eu.linuxengineering.snmp.annotations.SnmpAlternativeNodeType;
import eu.linuxengineering.snmp.annotations.SnmpExtraInformation;
import eu.linuxengineering.snmp.annotations.SnmpIndex;
import eu.linuxengineering.snmp.annotations.SnmpNodeDetails;
import eu.linuxengineering.snmp.annotations.SnmpSubnode;
import eu.linuxengineering.snmp.nodes.SnmpAllReflectInformation;
import eu.linuxengineering.snmp.nodes.SnmpNodeType;
import eu.linuxengineering.snmp.nodes.SnmpReflectAltType;
import eu.linuxengineering.snmp.nodes.SnmpReflectFields;
import net.sf.snmpadaptor4j.api.AttributeAccessor;
import net.sf.snmpadaptor4j.object.SnmpDataType;
import net.sf.snmpadaptor4j.object.SnmpOid;
public class SnmpTools
{
private SnmpTools(){}
protected static final Logger LOG = JavaExperienceLoggingFacility.getLogger(new Loggable("SnmpTools"));
public static SnmpDispatchNode ensurePath
(
SnmpDispatchNode from,
int[] path
)
{
for(int i=0;i<path.length;++i)
{
SnmpNode node = from.subNodes.get(path[i]);
if(null == node)
{
SnmpDispatchNode tmp = new SnmpDispatchNode();
from.addEntry(path[i], tmp);
node = tmp;
}
if(node instanceof SnmpDispatchNode)
{
from = (SnmpDispatchNode) node;
}
else
{
throw new RuntimeException
(
"A non SnmpDispatchNode node already present in path `"+Arrays.toString(path)+" at "+i+" "+node
);
}
}
return from;
}
/**
* Returns the node already presen on the requested path
* returns null when nothing present on path and adds the node
* return the node found ont the path and node will not be added
* */
public static SnmpNode tryAddToPath
(
SnmpDispatchNode from,
int[] path,
SnmpNode node
)
{
SnmpDispatchNode to = ensurePath(from, Arrays.copyOf(path, path.length-1));
Integer tar = path[path.length-1];
SnmpNode ret = null;
ret = to.subNodes.get(tar);
if(null != ret && ret != node)
{
return ret;
}
to.addEntry(tar, node);
return null;
}
public static void addToPath
(
SnmpDispatchNode from,
int[] path,
SnmpNode node,
boolean forceOverride
)
{
SnmpDispatchNode to = ensurePath(from, Arrays.copyOf(path, path.length-1));
Integer tar = path[path.length-1];
if(to.subNodes.containsKey(tar) && !forceOverride)
{
throw new RuntimeException("Node at path "+Arrays.toString(path)+" already exists: "+to.subNodes.get(tar));
}
to.addEntry(tar, node);
}
public static AttributeAccessor createReadOnlyAttributeAccessor(SnmpOid id, SnmpDataType type, SimpleGet<?> source)
{
return new AttributeAccessor()
{
@Override
public void setValue(Object value) throws Exception {}
@Override
public boolean isWritable()
{
return false;
}
@Override
public boolean isReadable()
{
return true;
}
@Override
public Object getValue() throws Exception
{
return source.get();
}
@Override
public SnmpDataType getSnmpDataType()
{
return type;
}
@Override
public SnmpOid getOid()
{
return id;
}
@Override
public Class<?> getJmxDataType()
{
return null;
}
};
}
public static SnmpFinalNode addRoAccessorToPath(SnmpDispatchNode root, int[] id, SnmpDataType type, SimpleGet<Object> getter, boolean forceOverride)
{
SnmpOid oid = SnmpOid.newInstance(id);
SnmpFinalNode ret = new SnmpFinalNode(createReadOnlyAttributeAccessor(oid, type, getter));
addToPath(root, id, ret, forceOverride);
return ret;
}
public static SnmpFinalNode createFinalNode(SnmpOid oid, SnmpDataType type, SimpleGet<?> getter)
{
return new SnmpFinalNode(createReadOnlyAttributeAccessor(oid, type, getter));
}
public static SnmpNode addSnmpObjectToPath(SnmpMibDispatch root, int[] path, Object node, boolean forceOverride)
{
return addSnmpObjectToPath(root, path, node, forceOverride, null);
}
public static SnmpNode addSnmpObjectToPath(SnmpMibDispatch root, int[] path, Object node, boolean forceOverride, ReflectionExtraDataProvider redp)
{
SnmpNode add = wrapSnmpObject(null, null, null, redp, node);
addToPath(root, path, add, forceOverride);
return add;
}
public static final MethodSelector SELECT_ALL_PUBLIC_INSTANCE_METHOD = new MethodSelector
(
true,
Visibility.Public,
BelongTo.Instance,
Select.IsNot,
Select.All,
Select.All,
Select.All,
Select.All
);
public static SnmpDataType recogniseSnmpDataType(Class cls)
{
cls = PrimitiveTools.toObjectClassType(cls, cls);
if(String.class == cls)
{
return SnmpDataType.octetString;
}
if
(
Boolean.class == cls
||
Byte.class == cls
||
Character.class == cls
||
Short.class == cls
||
Integer.class == cls
)
{
return SnmpDataType.integer32;
}
if(Double.class == cls || Float.class == cls)
{
return SnmpDataType.octetString;
}
if
(
Long.class == cls
)
{
return SnmpDataType.counter64;
}
if(SnmpOid.class == cls)
{
return SnmpDataType.objectIdentifier;
}
return null;
}
protected static SnmpNode createReflectNode
(
String name,
String description,
SnmpNodeType type,
SimpleGet<Integer> itemsCount,
ReflectionExtraDataProvider redp,
Object source,
Object connection,
Object target
)
{
SnmpRequestSensitiveDispatchCollection ret = new SnmpRequestSensitiveDispatchCollection();
if(!StringTools.isNullOrTrimEmpty(name))
{
ret.addNodeFactory(1, (oid)->createFinalNode(oid, SnmpDataType.octetString, ()->name));
}
if(!StringTools.isNullOrTrimEmpty(description))
{
ret.addNodeFactory(2, (oid)->createFinalNode(oid, SnmpDataType.octetString, ()->description));
}
ret.addNodeFactory(3, (oid)->createFinalNode(oid, SnmpDataType.octetString, ()->type.name()));
ret.addNodeFactory(4, (oid)->createFinalNode(oid, SnmpDataType.integer32, itemsCount));
if(null != redp)
{
Object info = redp.getBy(source, connection, target);
if(null != info)
{
ret.addNodeFactory(10, (oid)->wrapSnmpObject(oid, null, null, null, info));
}
}
if(connection instanceof Method)
{
Method m = (Method) connection;
//11 annotation the target itself
SnmpExtraInformation[] infos = m.getAnnotationsByType(SnmpExtraInformation.class);
if(null != infos && infos.length > 0)
{
Map<Integer, String> add = new SmallMap<>();
for(SnmpExtraInformation info:infos)
{
add.put(info.index(), info.data());
}
ret.addNodeFactory(11, (oid)->wrapSnmpObject(oid, null, null, null, add));
}
}
if(target instanceof SnmpReflectExtraData)
{
Map<Integer, String> tar = ((SnmpReflectExtraData) target).getExtraData();
ret.addNodeFactory(12, (oid)->wrapMapSource(tar, null, null, ()->tar, null));
}
else if(target instanceof GetBy1)
{
try
{
GetBy1<Object, SnmpOid> get = ((GetBy1<Object, SnmpOid>) target);
if(get instanceof SnmpReflectExtraData)
{
ret.addNodeFactory(12, (oid)->
{
Map<Integer, String> map = ((SnmpReflectExtraData) get.getBy(oid)).getExtraData();
return wrapMapSource(map, null, null, ()->map, null);
});
}
}
catch(Exception e)
{
LoggingTools.tryLogFormatException(LOG, LogLevel.ERROR, e, "Error while getting SnmpReflectExtraData from `%s`: ", target);
}
}
return ret;
}
/**
* Well, looks like there's a bug or somethin' in the SNMP agent server
* because can't accept SnmpOid object marked with the type of
* {@link SnmpDataType#objectIdentifier} even when providing with strings.
* So now i propagate them as an octetString 'til this situation gonna be
* resolved.
* */
public static GetBy1<SnmpNode, SnmpOid> wrapToSnmpObjectCreator(Object o)
{
return wrapToSnmpObjectCreator(null, null, null, o);
}
public static GetBy1<SnmpNode, SnmpOid> wrapToSnmpObjectCreator(Object o, ReflectionExtraDataProvider redp)
{
return wrapToSnmpObjectCreator(null, null, redp, o);
}
public static GetBy1<SnmpNode, SnmpOid> wrapToSnmpObjectCreator(Object source, Object connection, ReflectionExtraDataProvider redp, Object o)
{
if(o instanceof SnmpRelativeOid)
{
return (oid)->createFinalNode(oid, SnmpDataType.octetString, ()->((SnmpRelativeOid)o).resolve(oid).toString());
}
else if(String.class == o.getClass() || PrimitiveTools.isPrimitiveTypeObject(PrimitiveTools.translatePrimitiveToObjectType(o.getClass())))
{
return (oid)->createFinalNode(oid, recogniseSnmpDataType(o.getClass()), ()->o);
}
else if(o instanceof Collection)
{
return (oid)->wrapCollectionSource
(
source,
connection,
redp,
()->(Collection)o,
null
);
}
else if(o instanceof Map)
{
return (oid)->wrapMapSource(source, connection, redp, ()->(Map)o, null);
}
return (oid)->wrapSnmpObject(oid, source, connection, redp, o);
}
protected static SnmpNode wrapCollectionSource
(
Object source,
Object connection,
ReflectionExtraDataProvider redp,
SimpleGet<Collection> src,
SnmpReflectFields info
)
{
return new SnmpProxyNode()
{
protected Collection prev;
public synchronized void beforeAccess()
{
try
{
Collection now = src.get();
if(null == original || !Mirror.equals(prev, now))
{
SnmpRequestSensitiveDispatchCollection ret = new SnmpRequestSensitiveDispatchCollection();
//SnmpNodeDetails info = m.getAnnotation(SnmpNodeDetails.class);
int[] items = new int[]{0};
if(null != info)
{
ret.addNodeFactory
(
0,
createReflectNode
(
null == info?null:info.getName(),
null == info?null:info.getDescription(),
SnmpNodeType.ENUMERATION,
()->items[0],
redp,
source,
connection,
src
)
);
}
int i = 1;
for(Object o:now)
{
if(null != o)
{
ret.addNodeFactory(i, wrapToSnmpObjectCreator(source, connection, redp, o));
++items[0];
}
++i;
}
prev = now;
original = ret;
}
}
catch(Exception e)
{
Mirror.propagateAnyway(e);
}
}
@Override
public String toString()
{
return "SnmpTools.wrapCollectionSource(src: `"+src+"`, info: `"+info+"`)";
}
};
}
protected static SnmpNode wrapMapSource
(
Object source,
Object connection,
ReflectionExtraDataProvider redp,
SimpleGet<Map> src,
SnmpReflectFields info
)
{
return new SnmpProxyNode()
{
protected Map<Object, Object> prev;
public synchronized void beforeAccess()
{
try
{
Map<Object, Object> now = src.get();
if(null == original || !Mirror.equals(prev, now))
{
SnmpRequestSensitiveDispatchCollection ret = new SnmpRequestSensitiveDispatchCollection();
int[] items = new int[]{0};
if(null != info)
{
ret.addNodeFactory
(
0,
createReflectNode
(
null == info?null:info.getName(),
null == info?null:info.getDescription(),
SnmpNodeType.ENUMERATION,
()->items[0],
redp,
source,
connection,
src
)
);
}
if(null != now)
{
for(Entry<Object, Object> kv:now.entrySet())
{
Object v = kv.getValue();
if(null != v)
{
Integer index = (Integer) CastTo.Int.cast(kv.getKey());
if(null != index)
{
ret.addNodeFactory(index, wrapToSnmpObjectCreator(source, connection, redp, v));
++items[0];
}
}
}
}
prev = now;
original = ret;
}
}
catch(Exception e)
{
Mirror.propagateAnyway(e);
}
}
@Override
public String toString()
{
return "SnmpTools.wrapMapSource(src: `"+src+"`, info: `"+info+"`)";
}
};
}
public static SnmpNode wrapSnmpObject(Object node, ReflectionExtraDataProvider redp)
{
return wrapSnmpObject(null, null, null, redp, node);
}
/**
*
* nocovered cases:
* - constant values: int, boolean, double, String (enum as string)
* - collection or array
*
* covered:
* - object contains methods returns int, string, collection values
*
* */
protected static SnmpNode wrapSnmpObject(@MayNull SnmpOid oid, Object source, Object connection, ReflectionExtraDataProvider redp, Object node)
{
if(null == node)
{
return null;
}
if(node instanceof SnmpNode)
{
return (SnmpNode) node;
}
else if(node instanceof Collection)
{
return wrapCollectionSource(source, connection, redp, ()->(Collection)node, null);
}
else if(node instanceof Map)
{
return wrapMapSource(source, connection, redp, ()->(Map)node, null);
}
else
{
SnmpDataType type = recogniseSnmpDataType(node.getClass());
if(null != type)
{
if(null == oid)
{
SnmpRequestSensitiveDispatchCollection ret = new SnmpRequestSensitiveDispatchCollection();
ret.addNodeFactory(1, (foid)->SnmpFinalNode.wrap(foid, type, ()->node));
return ret;
}
else
{
return createFinalNode(oid, type, ()->node);
}
}
}
return wrapSnmpBean(node, redp);
}
public static GetBy1<SnmpNode, SnmpOid> createRelativeDemandProxy
(
GetBy2<SnmpNode, Object, SnmpOid> nodeCreator,
SimpleGet<Object> source,
boolean useCache
)
{
return (f)->new SnmpProxyNode()
{
protected Object prev;
public void beforeAccess()
{
Object now = null;
try
{
now = source.get();
}
catch(Exception e)
{
now = Format.getPrintedStackTrace(e);
//Mirror.propagateAnyway(e);
}
if(null == original || !useCache || !Mirror.equals(prev, now))
{
if(null == now)
{
now = "null";
}
original = nodeCreator.getBy(now, f);
prev = now;
}
}
@Override
public String toString()
{
return "createRelativeDemandProxy(nodeCreator: `"+nodeCreator+"`, source: `"+source+"`, useCache: `"+useCache+"`)";
}
};
}
/**
* Intentionally for internal usage, but you can use for other SNMP related
* functions, but don't abuse and use in other facility type.
* Because errors written to the "SnmpTools" label.
* */
@Deprecated
public static SimpleGet<Object> wrapMethodGetterWithLogging(Method m, Object subject)
{
return ()->
{
try
{
return m.invoke(subject);
}
catch(Exception e)
{
LoggingTools.tryLogFormatException
(
LOG,
LogLevel.ERROR,
e,
"Exception while invoking method `%s` on `%s`",
m,
subject
);
Mirror.propagateAnyway(e);
return null;
}
};
}
public static @MayNull SnmpAllReflectInformation tryExtractReflectInformation(Method m)
{
SnmpNodeDetails info = m.getAnnotation(SnmpNodeDetails.class);
SnmpAlternativeNodeType type = m.getAnnotation(SnmpAlternativeNodeType.class);
SnmpAlternativeElementsCount count = m.getAnnotation(SnmpAlternativeElementsCount.class);
if(null == info && null == type)
{
return null;
}
return new SnmpAllReflectInformation()
{
@Override
public SnmpNodeType getType()
{
return null == type? null:type.getType();
}
@Override
public int getLength()
{
return null == count?-1:count.length();
}
@Override
public String getName()
{
return null == info?null:info.name();
}
@Override
public String getDescription()
{
return null == info?null:info.description();
}
};
}
public static interface ReflectionExtraDataProvider extends GetBy3<Object, Object, Object, Object>{}
public static SnmpNode wrapSnmpBean(Object node)
{
return wrapSnmpBean(node, null);
}
public static SnmpNode wrapSnmpBean(Object node, ReflectionExtraDataProvider redp)
{
ClassData cd = Mirror.getClassData(node.getClass());
Method[] methods = cd.select(SELECT_ALL_PUBLIC_INSTANCE_METHOD);
SnmpRequestSensitiveDispatchCollection ret = new SnmpRequestSensitiveDispatchCollection();
int[] itemsCount = new int[]{0};
if(node instanceof SnmpReflectFields || node instanceof SnmpReflectExtraData)
{
String name = null;
String detail = null;
if(node instanceof SnmpReflectFields)
{
SnmpReflectFields srf = (SnmpReflectFields) node;
name = srf.getName();
detail = srf.getDescription();
}
SnmpNode cre = createReflectNode
(
name,
detail,
node instanceof SnmpReflectAltType?((SnmpReflectAltType)node).getType():SnmpNodeType.COLLECTION,
node instanceof SnmpReflectAltType?()->((SnmpReflectAltType)node).getLength():()->itemsCount[0],
redp,
null,
null,
node
);
ret.addNodeFactory(0, cre);
}
for(Method m:methods)
{
if(0 != m.getParameters().length)
{
continue;
}
SnmpIndex index = m.getAnnotation(SnmpIndex.class);
if(null != index)
{
Class retType = m.getReturnType();
SnmpDataType type = recogniseSnmpDataType(retType);
SnmpNodeType nodeType = SnmpNodeType.EXACT_VALUE;
SimpleGet<Integer> nodeItemCount = ()->1;
GetBy1<SnmpNode, SnmpOid> getter = null;
SimpleGet<Object> invoke = wrapMethodGetterWithLogging(m, node);
SnmpReflectFields info = tryExtractReflectInformation(m);
if(Collection.class.isAssignableFrom(retType))
{
ret.addNodeFactory(index.index(), wrapCollectionSource(node, m, redp, (SimpleGet)invoke, info));
++itemsCount[0];
continue;
}
else if(Map.class.isAssignableFrom(retType))
{
ret.addNodeFactory(index.index(), wrapMapSource(node, m, redp, (SimpleGet)invoke, info));
++itemsCount[0];
continue;
}
else if(SnmpRelativeOid.class.isAssignableFrom(retType))
{
getter = (oid) ->
createRelativeDemandProxy
(
(o, foid)->wrapToSnmpObjectCreator(node, m, redp, o).getBy(foid),
invoke,
true
).getBy(oid);
}
else if(null == type)
{
getter = createRelativeDemandProxy((o, oid)->wrapSnmpObject(oid, node, m, redp, o), invoke, true);
}
else
{
getter = (oid)->SnmpFinalNode.wrap(oid, type, invoke);
}
if(null == getter)
{
continue;
}
++itemsCount[0];
boolean subnode = null != m.getAnnotation(SnmpSubnode.class);
if(subnode)
{
SnmpRequestSensitiveDispatchCollection add = new SnmpRequestSensitiveDispatchCollection();
add.addNodeFactory
(
0,
createReflectNode
(
null == info?null:info.getName(),
null == info?null:info.getDescription(),
node instanceof SnmpReflectAltType?((SnmpReflectAltType)node).getType():nodeType,
node instanceof SnmpReflectAltType?()->((SnmpReflectAltType)node).getLength():nodeItemCount,
redp,
node,
m,
invoke
)
);
add.addNodeFactory(1, getter);
ret.addNodeFactory(index.index(), add);
}
else
{
ret.addNodeFactory(index.index(), getter);
}
}
}
return ret;
}
}