package org.fluxtream.core.connectors;
import org.apache.commons.lang.ArrayUtils;
import org.apache.velocity.util.StringUtils;
import org.fluxtream.core.aspects.FlxLogger;
import org.fluxtream.core.connectors.annotations.ObjectTypeSpec;
import org.fluxtream.core.connectors.annotations.Updater;
import org.fluxtream.core.connectors.bodytrackResponders.AbstractBodytrackResponder;
import org.fluxtream.core.connectors.updaters.AbstractUpdater;
import org.fluxtream.core.domain.AbstractFacet;
import org.fluxtream.core.domain.AbstractUserProfile;
import org.fluxtream.core.facets.extractors.AbstractFacetExtractor;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class Connector {
static FlxLogger logger = FlxLogger.getLogger(Connector.class);
UpdateStrategyType updateStrategyType = UpdateStrategyType.INCREMENTAL;
private static Map<String, Connector> connectors = new ConcurrentHashMap<String, Connector>();
private static Map<Integer, Connector> connectorsByValue = new ConcurrentHashMap<Integer, Connector>();
private static Map<String, Connector> connectorsByPrettyName = new ConcurrentHashMap<String, Connector>();
private static Map<String, Connector> connectorsByDeviceNickname = new ConcurrentHashMap<String, Connector>();
private Class<? extends AbstractFacetExtractor> extractorClass;
private Map<Integer, Class<? extends AbstractFacetExtractor>> objectTypeExtractorClasses;
private Class<? extends AbstractUserProfile> userProfileClass;
private ObjectType[] objectTypes;
private Class<? extends AbstractFacet> facetClass;
private int value;
private String name;
private String prettyName;
private int[] objectTypeValues;
private boolean hasFacets;
private String[] defaultChannels;
private Class<? extends AbstractUpdater> updaterClass;
private Class<? extends AbstractBodytrackResponder> bodytrackResponder;
private String deviceNickname;
static {
Connector flxConnector = new Connector();
flxConnector.name = "fluxtream";
flxConnector.deviceNickname = "FluxtreamCapture";
connectors.put(flxConnector.name, flxConnector);
connectorsByValue.put(0xCAFEBABE, flxConnector);
// NOTE! This connector has no pretty name, and ConcurrentHashMaps don't allow keys or values to be null, so
// we won't add it to the connectorsByPrettyName map.
ObjectType objectType = new ObjectType();
objectType.value = 0xBABEFACE;
objectType.name = "comment";
ObjectType.addObjectType(objectType.name, flxConnector, objectType);
}
private int[] deleteOrder;
private Class<? extends SharedConnectorFilter> sharedConnectorFilterClass;
public Class<? extends SharedConnectorFilter> sharedConnectorFilterClass() {
return sharedConnectorFilterClass;
}
public boolean supportsFiltering() {
return this.sharedConnectorFilterClass != DefaultSharedConnectorFilter.class;
}
public String toString() {
String string = "{name:" + name;
if (this.objectTypes != null) {
string += ", objectTypes:[";
for (int i = 0; i < objectTypes.length; i++) {
if (i > 0)
string += ", ";
string += objectTypes[i].getName() + "/"
+ objectTypes[i].value();
}
string += "], objectTypeValues: [" + toString(objectTypeValues())
+ "], objectTypeExtractorClasses: "
+ this.objectTypeExtractorClasses + "}";
} else {
string += ", extractorClass: " + this.extractorClass + "}";
}
return string;
}
private String toString(int[] values) {
String s = "";
for (int i = 0; i < values.length; i++) {
if (i > 0)
s += ", ";
s += values[i];
}
return s;
}
public static Collection<Connector> getAllConnectors() {
return connectors.values();
}
public static Connector fromString(String s) {
return connectors.get(s);
}
private static boolean initialized = false;
static {
if (!initialized) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(
false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Updater.class));
for (BeanDefinition bd : scanner
.findCandidateComponents("org.fluxtream.connectors"))
extractConnectorMetadata(bd);
initialized = true;
for (Connector connector : connectors.values()) {
System.out.println(connector);
}
}
}
private static void extractConnectorMetadata(final BeanDefinition bd) {
String beanClassName = bd.getBeanClassName();
String connectorName = getConnectorName(beanClassName);
Connector connector = new Connector();
connector.updaterClass = getUpdaterClass(beanClassName);
Updater updaterAnnotation = connector.updaterClass
.getAnnotation(Updater.class);
// set connectors' pretty name
connector.prettyName = updaterAnnotation.prettyName();
connector.deviceNickname = updaterAnnotation.deviceNickname().equals(Updater.DEVICE_NICKNAME_NONE)
? updaterAnnotation.prettyName()==null ? connectorName : updaterAnnotation.prettyName()
: updaterAnnotation.deviceNickname();
connector.value = updaterAnnotation.value();
connector.updateStrategyType = updaterAnnotation
.updateStrategyType();
connector.hasFacets = updaterAnnotation.hasFacets();
connector.name = connectorName;
connector.sharedConnectorFilterClass = updaterAnnotation.sharedConnectorFilter();
connector.deleteOrder = updaterAnnotation.deleteOrder();
// set connectors' object types
Class<? extends AbstractFacet>[] facetTypes = updaterAnnotation
.objectTypes();
if (updaterAnnotation.extractor() != AbstractFacetExtractor.class)
connector.extractorClass = updaterAnnotation.extractor();
if (facetTypes.length == 1) {
connector.facetClass = facetTypes[0];
}
if (updaterAnnotation.userProfile() != AbstractUserProfile.class)
connector.userProfileClass = updaterAnnotation
.userProfile();
connector.defaultChannels = updaterAnnotation.defaultChannels();
List<ObjectType> connectorObjectTypes = new ArrayList<ObjectType>();
for (Class<? extends AbstractFacet> facetType : facetTypes) {
final ObjectType objectType = getFacetTypeMetadata(connector, facetTypes, facetType);
connectorObjectTypes.add(objectType);
ObjectType.addObjectType(objectType.name(), connector, objectType);
}
if (connectorObjectTypes.size()>0)
connector.objectTypes = connectorObjectTypes.toArray(new ObjectType[0]);
connectors.put(connectorName, connector);
connectorsByValue.put(connector.value(), connector);
connectorsByDeviceNickname.put(connector.deviceNickname, connector);
if (connector.prettyName != null) {
connectorsByPrettyName.put(connector.prettyName.toLowerCase(), connector);
}
final Class<? extends AbstractBodytrackResponder> bodytrackResponderClass = updaterAnnotation.bodytrackResponder();
if (!(bodytrackResponderClass == AbstractBodytrackResponder.class))
connector.bodytrackResponder = bodytrackResponderClass;
}
private static ObjectType getFacetTypeMetadata(final Connector connector,
final Class<? extends AbstractFacet>[] facetTypes,
final Class<? extends AbstractFacet> facetType) {
ObjectTypeSpec ots = facetType
.getAnnotation(ObjectTypeSpec.class);
// objectTypes are mandatory only if there are more than 1
if (ots == null) {
if (facetTypes.length>1)
throw new RuntimeException(
"No ObjectTypeSpec Annotation for Facet ["
+ facetType.getName() + "]");
else
return null;
}
ObjectType objectType = new ObjectType();
objectType.facetClass = facetType;
objectType.value = ots.value();
objectType.name = ots.name();
objectType.prettyname = ots.prettyname();
objectType.isImageType = ots.isImageType();
objectType.isDateBased = ots.isDateBased();
objectType.isMixedType = ots.isMixedType();
objectType.isClientFacet = ots.clientFacet();
objectType.visibleClause = ots.visibleClause().equals("")?null:ots.visibleClause();
objectType.orderBy = ots.orderBy().equals("")?null:ots.orderBy();
if (ots.extractor() != null && ots.extractor()!=AbstractFacetExtractor.class) {
connector.addObjectTypeExtractorClass(
objectType.value, ots.extractor(),
ots.parallel());
}
return objectType;
}
public boolean hasImageObjectType() {
if (objectTypes==null) return false;
for (ObjectType objectType: objectTypes) {
if (objectType.isImageType())
return true;
}
return false;
}
@SuppressWarnings("unchecked")
private static Class<? extends AbstractUpdater> getUpdaterClass(
String beanClassName) {
Class<? extends AbstractUpdater> updaterClass = null;
try {
updaterClass = (Class<? extends AbstractUpdater>) Class
.forName(beanClassName);
} catch (Throwable t) {
throw new RuntimeException("Could not get Updater Class for ["
+ beanClassName + "]");
}
return updaterClass;
}
private void addObjectTypeExtractorClass(int objectTypeValue,
Class<? extends AbstractFacetExtractor> extractorClass,
boolean parallel) {
if (this.objectTypeExtractorClasses == null)
this.objectTypeExtractorClasses = new ConcurrentHashMap<Integer, Class<? extends AbstractFacetExtractor>>();
if (!parallel
&& this.objectTypeExtractorClasses
.containsValue(extractorClass)) {
Set<Integer> keySet = this.objectTypeExtractorClasses.keySet();
int previousObjectType = -1;
for (Integer objectType : keySet) {
if (this.objectTypeExtractorClasses.get(objectType) == extractorClass) {
previousObjectType = objectType;
this.objectTypeExtractorClasses.remove(objectType);
break;
}
}
this.objectTypeExtractorClasses.put(previousObjectType
+ objectTypeValue, extractorClass);
} else
this.objectTypeExtractorClasses
.put(objectTypeValue, extractorClass);
}
public static String getConnectorName(String beanClassName) {
final String[] splits = StringUtils.split(beanClassName, ".");
return splits[splits.length-2];
}
public Class<? extends AbstractUpdater> getUpdaterClass() {
return updaterClass;
}
public String statusNotificationName() {
return new StringBuilder(getName()).append(".status").toString();
}
public enum UpdateStrategyType {
ALWAYS_UPDATE, INCREMENTAL
}
public boolean isAutonomous() {
final Class<?>[] interfaces = this.updaterClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
if (anInterface==Autonomous.class)
return true;
}
return false;
}
private Connector() {
}
public String[] getDefaultChannels(){
return defaultChannels;
}
public ObjectType[] objectTypes() {
return this.objectTypes;
}
public int[] objectTypeValues() {
if (this.objectTypeValues == null) {
if (this.objectTypeExtractorClasses != null
&& this.objectTypeExtractorClasses.size() > 0) {
Set<Integer> keySet = this.objectTypeExtractorClasses.keySet();
Iterator<Integer> eachKey = keySet.iterator();
this.objectTypeValues = new int[keySet.size()];
for (int i = 0; eachKey.hasNext(); i++) {
this.objectTypeValues[i] = eachKey.next().intValue();
}
} else {
this.objectTypeValues = new int[] { -1 };
}
}
return this.objectTypeValues;
}
public Class<? extends AbstractUserProfile> userProfileClass() {
return userProfileClass;
}
public Class<? extends AbstractFacet> facetClass() {
return facetClass;
}
public String getName() {
return name;
}
public int getValue() {
return value;
}
public int value() {
return value;
}
public AbstractFacetExtractor extractor(int objectTypes, BeanFactory beanFactory) {
if (extractorClass != null)
try {
return beanFactory.getBean(extractorClass);
} catch (Exception e) {
e.printStackTrace();
}
else if (objectTypes != -1) {
Iterator<Integer> eachObjectTypeValue = objectTypeExtractorClasses.keySet().iterator();
Class<? extends AbstractFacetExtractor> extractorClass = null;
while (eachObjectTypeValue.hasNext()) {
int objectTypeValue = eachObjectTypeValue.next();
if ((objectTypes&objectTypeValue)!=0) {
extractorClass = objectTypeExtractorClasses.get(objectTypeValue);
break;
}
}
try {
if (extractorClass!=null)
return beanFactory.getBean(extractorClass);
else {
logger.error("COULD NOT FIND EXTRACTOR CLASS FOR " + objectTypes);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
public UpdateStrategyType updateStrategyType() {
return this.updateStrategyType;
}
public String getPrettyName() {
return prettyName();
}
public String getDeviceNickname() {
return deviceNickname;
}
public String prettyName() {
return prettyName;
}
public int[] getDeleteOrder() {
return deleteOrder;
}
public boolean hasDeleteOrder() {
return !ArrayUtils.isEquals(deleteOrder, new int[]{-1});
}
public ObjectType[] getObjectTypesForValue(int value) {
if (this.objectTypes==null) return null;
List<ObjectType> result = new ArrayList<ObjectType>();
for(ObjectType objectType : objectTypes) {
if ((value&objectType.value)!=0)
result.add(objectType);
}
return result.toArray(new ObjectType[0]);
}
public boolean hasFacets() {
return hasFacets;
}
public static Connector getConnector(String apiName) {
return fromString(apiName.toLowerCase());
}
public static Connector fromValue(int api) {
return connectorsByValue.get(api);
}
public static Connector fromDeviceNickname(String deviceNickname) {
return connectorsByDeviceNickname.get(deviceNickname);
}
/**
* Returns the Connector having the given pretty name. Returns <code>null</code> if no such connector exists or
* if the given pretty name is <code>null</code>.
*/
public static Connector fromPrettyName(@Nullable final String prettyName) {
if (prettyName != null) {
return connectorsByPrettyName.get(prettyName);
}
return null;
}
public AbstractBodytrackResponder getBodytrackResponder(BeanFactory beanFactory){
try{
if (bodytrackResponder!=null) {
final AbstractBodytrackResponder bean = beanFactory.getBean(bodytrackResponder);
return bean;
}
}
catch (Exception e){
System.out.println("COULD NOT INSTANTIATE RESPONDER: " + bodytrackResponder);
System.out.println("PLEASE CHECK THAT IT HAS THE @Component ANNOTATION!");
return null;
}
return null;
}
}