package org.fluxtream.core.services.impl; import org.apache.commons.lang.StringUtils; import org.fluxtream.core.Configuration; import org.fluxtream.core.SimpleTimeInterval; import org.fluxtream.core.TimeInterval; import org.fluxtream.core.TimeUnit; import org.fluxtream.core.aspects.FlxLogger; import org.fluxtream.core.connectors.Connector; import org.fluxtream.core.connectors.ObjectType; import org.fluxtream.core.connectors.bodytrackResponders.AbstractBodytrackResponder; import org.fluxtream.core.connectors.updaters.AbstractUpdater; import org.fluxtream.core.domain.*; import org.fluxtream.core.services.*; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.persistence.TypedQuery; import java.lang.reflect.Field; import java.util.*; @Service @Component public class BodyTrackStorageServiceImpl implements BodyTrackStorageService { static FlxLogger logger = FlxLogger.getLogger(BodyTrackStorageServiceImpl.class); @Autowired Configuration env; @Autowired private DataUpdateService dataUpdateSerivce; @Autowired GuestService guestService; @Autowired ApiDataService apiDataService; @Autowired MetadataService metadataService; @Autowired BodyTrackHelper bodyTrackHelper; @Autowired BeanFactory beanFactory; @PersistenceContext EntityManager em; private Hashtable<String, FieldHandler> fieldHandlers = new Hashtable<String, FieldHandler>(); @Override public void storeApiData(ApiKey apiKey, List<? extends AbstractFacet> facets) { logStoreApiData(apiKey.getGuestId(), facets); Map<String, List<AbstractFacet>> facetsByFacetName = sortFacetsByFacetName(facets); for (final String facetName : facetsByFacetName.keySet()) { List<BodyTrackHelper.BodyTrackUploadResult> results = storeDeviceData(apiKey, facetsByFacetName, facetName); if (!results.isEmpty()){ AbstractFacet facet = facetsByFacetName.get(facetName).get(0); long apiKeyId = facet.apiKeyId; long objectTypeId = facet.objectType; for (BodyTrackHelper.BodyTrackUploadResult result : results){ if (!(result instanceof BodyTrackHelper.ParsedBodyTrackUploadResult)) continue; BodyTrackHelper.ParsedBodyTrackUploadResult parsedResult = (BodyTrackHelper.ParsedBodyTrackUploadResult) result; dataUpdateSerivce.logBodyTrackDataUpdate(apiKey.getGuestId(),apiKeyId,objectTypeId,parsedResult); } } } } /** * Channels can be thought of as containers: sometimes they have data, some times they don't. If a guest wants * to share their data, these containers need to be explicitely listed, even if they are momentarily empty. This * method will list all channels that we know of in advance, irrespective of their containing data or not. * @param apiKey */ @Override @Transactional(readOnly=false) public boolean mapChannels(ApiKey apiKey) { try { final Connector connector = apiKey.getConnector(); Query saveExistingSharedChannelsQuery = em.createQuery("SELECT sc FROM SharedChannels sc WHERE sc.channelMapping.apiKeyId=? AND sc.channelMapping.creationType=?"); saveExistingSharedChannelsQuery.setParameter(1, apiKey.getId()); saveExistingSharedChannelsQuery.setParameter(2, ChannelMapping.CreationType.mapChannels); List<SharedChannel> savedSharedChannels = saveExistingSharedChannelsQuery.getResultList(); for (SharedChannel savedSharedChannel : savedSharedChannels) em.remove(savedSharedChannel); // only delete mappings that were previously created by this method Query deleteExistingMappingsQuery = em.createQuery("DELETE FROM ChannelMapping mapping WHERE mapping.apiKeyId=? AND mapping.creationType=?"); deleteExistingMappingsQuery.setParameter(1, apiKey.getId()); deleteExistingMappingsQuery.setParameter(2, ChannelMapping.CreationType.mapChannels); deleteExistingMappingsQuery.executeUpdate(); final AbstractBodytrackResponder bodytrackResponder = connector.getBodytrackResponder(beanFactory); if (bodytrackResponder != null) { final List<ChannelMapping> responderMappings = new ArrayList<ChannelMapping>(); bodytrackResponder.addToDeclaredChannelMappings(apiKey, responderMappings); for (ChannelMapping responderMapping : responderMappings) { responderMapping.setCreationType(ChannelMapping.CreationType.mapChannels); em.persist(responderMapping); } } final Iterator<String> keys = env.bodytrackProperties.getKeys(); while (keys.hasNext()) { String key = keys.next(); if (key.startsWith(connector.getName()) && key.indexOf("channel_names") != -1) { String[] keySplits = key.split("\\."); String objectTypeName = keySplits[1]; final ObjectType objectType = ObjectType.getObjectType(connector, objectTypeName); if (objectType == null) { logger.warn("Could not find objectType named " + objectTypeName + " for connector " + connector.getName()); continue; } final String facetName = keySplits[0] + "." + objectTypeName; final List<String> facetFieldChannelNames = getDatastoreChannelNames(facetName); for (String facetFieldChannelName : facetFieldChannelNames) { final ChannelMapping facetFieldChannelMapping = createFacetFieldChanneMapping(facetFieldChannelName, apiKey, objectType); facetFieldChannelMapping.setCreationType(ChannelMapping.CreationType.mapChannels); em.persist(facetFieldChannelMapping); } final List<FieldHandler> handlers = getFieldHandlers(facetName); for (FieldHandler fieldHandler : handlers) { final List<ChannelMapping> fieldHandlerMappings = new ArrayList<ChannelMapping>(); fieldHandler.addToDeclaredChannelMappings(apiKey, fieldHandlerMappings); for (ChannelMapping fieldHandlerMapping : fieldHandlerMappings) { fieldHandlerMapping.setCreationType(ChannelMapping.CreationType.mapChannels); em.persist(fieldHandlerMapping); } } break; } } // add photo channels ObjectType[] objectTypes = apiKey.getConnector().objectTypes(); for (ObjectType objectType : objectTypes) { if (objectType.isImageType()) { ChannelMapping.TimeType timeType = getTimeType(connector, objectType); ChannelMapping photoChannelMapping = new ChannelMapping(apiKey.getId(), apiKey.getGuestId(), ChannelMapping.ChannelType.photo, timeType, objectType.value(), apiKey.getConnector().getDeviceNickname(), "photo", apiKey.getConnector().getDeviceNickname(), "photo"); photoChannelMapping.setCreationType(ChannelMapping.CreationType.mapChannels); em.persist(photoChannelMapping); } } // restore the save sharedChannels for (SharedChannel savedSharedChannel : savedSharedChannels) { ChannelMapping newMapping = getChannelMapping(apiKey.getId(), savedSharedChannel.channelMapping); if (newMapping!=null) { SharedChannel restoredSharedChannel = new SharedChannel(); restoredSharedChannel.buddy = savedSharedChannel.buddy; restoredSharedChannel.channelMapping = newMapping; em.persist(restoredSharedChannel); } else { String message = "Could not restore saved shared channel because a corresponding channel mapping could not be found: "; System.out.println(message); logger.warn(message); } } // finally, possibly create default ChannelStyles final AbstractUpdater updater = beanFactory.getBean(connector.getUpdaterClass()); updater.setDefaultChannelStyles(apiKey); } catch (Throwable t) { System.out.println("Can't map channels"); t.printStackTrace(); return false; } return true; } public ChannelMapping getChannelMapping(long apiKeyId, ChannelMapping oldMapping) { Query query = em.createQuery("SELECT channelMapping FROM ChannelMapping channelMapping WHERE channelMapping.apiKeyId=? AND " + "channelMapping.deviceName=? AND channelMapping.channelName=? AND " + "channelMapping.internalDeviceName=? AND channelMapping.internalChannelName=?"); query.setParameter(1, apiKeyId); query.setParameter(2, oldMapping.getDeviceName()); query.setParameter(3, oldMapping.getChannelName()); query.setParameter(4, oldMapping.getInternalDeviceName()); query.setParameter(5, oldMapping.getInternalChannelName()); List<ChannelMapping> mappings = query.getResultList(); if (mappings.size()>0) return mappings.get(0); return null; } @Override public List<ChannelMapping> getChannelMappings(long apiKeyId) { Query query = em.createQuery("SELECT channelMapping FROM ChannelMapping channelMapping WHERE channelMapping.apiKeyId=?"); query.setParameter(1, apiKeyId); List mappings = query.getResultList(); return mappings; } private ChannelMapping createFacetFieldChanneMapping(String facetFieldChannelName, ApiKey apiKey, ObjectType objectType) { ChannelMapping.ChannelType channelType = ChannelMapping.ChannelType.data; if (objectType.isImageType()) channelType = ChannelMapping.ChannelType.photo; Connector connector = apiKey.getConnector(); final String deviceName = connector.getDeviceNickname(); ChannelMapping.TimeType timeType = getTimeType(connector, objectType); ChannelMapping declaredMapping = new ChannelMapping(apiKey.getId(), apiKey.getGuestId(), channelType, timeType, objectType.value(), deviceName, facetFieldChannelName, deviceName, facetFieldChannelName); return declaredMapping; } private ChannelMapping.TimeType getTimeType(Connector connector, ObjectType objectType) { ChannelMapping.TimeType timeType = ChannelMapping.TimeType.gmt; if (Arrays.asList("zeo", "flickr", "fitbit").contains(connector.getName())) timeType = ChannelMapping.TimeType.local; else if (objectType!=null && objectType.facetClass()!=null && AbstractLocalTimeFacet.class.isAssignableFrom(objectType.facetClass())) return ChannelMapping.TimeType.local; return timeType; } private void logStoreApiData(final long guestId, final List<? extends AbstractFacet> facets) { StringBuilder sb = new StringBuilder("module=updateQueue component=bodytrackStorageService action=storeApiData") .append(" guestId=").append(guestId); if (facets.size()>0) { try { String connectorName = Connector.fromValue(facets.get(0).api).getName(); sb.append(" connector=" + connectorName); } catch (Throwable t) { sb.append(" message=\"could not figure out connector name...\""); } } logger.info(sb.toString()); } private List<BodyTrackHelper.BodyTrackUploadResult> storeDeviceData(ApiKey apiKey, Map<String, List<AbstractFacet>> facetsByDeviceNickname, String facetName) { List<BodyTrackHelper.BodyTrackUploadResult> results = new ArrayList<BodyTrackHelper.BodyTrackUploadResult>(); String deviceName = getDeviceNickname(facetName); List<AbstractFacet> deviceFacets = facetsByDeviceNickname.get(facetName); results.add(uploadDailyData(apiKey, deviceName, deviceFacets, facetName)); List<FieldHandler> facetFieldHandlers = getFieldHandlers(facetName); for (FieldHandler fieldHandler : facetFieldHandlers) { results.addAll(uploadIntradayData(apiKey, deviceFacets, fieldHandler)); } return results; } private BodyTrackHelper.BodyTrackUploadResult uploadDailyData(ApiKey apiKey, String deviceName, List<AbstractFacet> deviceFacets, String facetName) { List<String> datastoreChannelNames = getDatastoreChannelNames(facetName); List<String> facetColumnNames = getFacetColumnNames(facetName); List<List<Object>> dailyDataChannelValues = getDailyDataChannelValues(deviceFacets, facetColumnNames); // TODO: check the status code in the BodyTrackUploadResult final BodyTrackHelper.BodyTrackUploadResult bodyTrackUploadResult = bodyTrackHelper.uploadToBodyTrack(apiKey, deviceName, datastoreChannelNames, dailyDataChannelValues); return bodyTrackUploadResult; } @Override @Transactional(readOnly=false) public void ensureDataChannelMappingsExist(ApiKey apiKey, List<String> datastoreChannelNames, final String internalDeviceName) { ensureChannelMappingsExist(apiKey, datastoreChannelNames, internalDeviceName, ChannelMapping.ChannelType.data, null); } @Override @Transactional(readOnly=false) public void ensurePhotoChannelMappingsExist(ApiKey apiKey, List<String> datastoreChannelNames, final String internalDeviceName, Integer objectTypeId) { ensureChannelMappingsExist(apiKey, datastoreChannelNames, internalDeviceName, ChannelMapping.ChannelType.photo, objectTypeId); } private void ensureChannelMappingsExist(ApiKey apiKey, List<String> datastoreChannelNames, final String internalDeviceName, final ChannelMapping.ChannelType channelType, Integer objectTypeId) { for (String channelName : datastoreChannelNames) { final TypedQuery<ChannelMapping> query = em.createQuery("SELECT mapping FROM ChannelMapping mapping WHERE mapping.apiKeyId=? AND mapping.internalDeviceName=? AND mapping.channelName=?", ChannelMapping.class); query.setParameter(1, apiKey.getId()); query.setParameter(2, internalDeviceName); query.setParameter(3, channelName); final List<ChannelMapping> mappings = query.getResultList(); if (mappings==null||mappings.size()==0) { ObjectType objectType = ObjectType.getObjectType(apiKey.getConnector(), objectTypeId); ChannelMapping.TimeType timeType = getTimeType(apiKey.getConnector(), objectType); ChannelMapping mapping = new ChannelMapping(apiKey.getId(), apiKey.getGuestId(), channelType, timeType, objectTypeId, apiKey.getConnector().getDeviceNickname(), channelName, internalDeviceName!=null ? internalDeviceName : apiKey.getConnector().getDeviceNickname(), channelName); mapping.setCreationType(ChannelMapping.CreationType.dynamic); em.persist(mapping); } } } private List<BodyTrackHelper.BodyTrackUploadResult> uploadIntradayData(ApiKey apiKey, List<AbstractFacet> deviceFacets, FieldHandler fieldHandler) { List<BodyTrackHelper.BodyTrackUploadResult> results = new ArrayList<BodyTrackHelper.BodyTrackUploadResult>(); for (AbstractFacet deviceFacet : deviceFacets) { List<BodyTrackHelper.BodyTrackUploadResult> facetResults = fieldHandler.handleField(apiKey, deviceFacet); if (facetResults != null) results.addAll(facetResults); } return results; } private FieldHandler getFieldHandler(String fieldHandlerName) { if (fieldHandlers.get(fieldHandlerName)==null) { FieldHandler fieldHandler; fieldHandler = (FieldHandler)beanFactory.getBean(fieldHandlerName); fieldHandlers.put(fieldHandlerName, fieldHandler); } return fieldHandlers.get(fieldHandlerName); } private List<String> getDatastoreChannelNames(String facetName) { String[] channelNamesMappings = env.bodytrackProperties.getString(facetName + ".channel_names").split(","); // this is to account for a very strange eisenbug where bodytrackProperties.getString() would only return the first item before the comma String[] stringArray = env.bodytrackProperties.getStringArray(facetName + ".channel_names"); if (stringArray.length>channelNamesMappings.length) channelNamesMappings = stringArray; List<String> channelNames = new ArrayList<String>(); for (String mapping : channelNamesMappings) { String[] terms = StringUtils.split(mapping, ":"); if (terms[1].startsWith("#")) continue; channelNames.add(terms[0].trim()); } return channelNames; } private List<String> getFacetColumnNames(String facetName) { String[] channelNamesMappings = env.bodytrackProperties.getString(facetName + ".channel_names").split(","); // this is to account for a very strange eisenbug where bodytrackProperties.getString() would only return the first item before the comma String[] stringArray = env.bodytrackProperties.getStringArray(facetName + ".channel_names"); if (stringArray.length>channelNamesMappings.length) channelNamesMappings = stringArray; List<String> channelNames = new ArrayList<String>(); for (String mapping : channelNamesMappings) { String[] terms = StringUtils.split(mapping, ":"); if (terms[1].startsWith("#")) continue; channelNames.add(terms[1].trim()); } return channelNames; } private List<FieldHandler> getFieldHandlers(String facetName) { String[] channelNamesMappings = env.bodytrackProperties.getString(facetName + ".channel_names").split(","); // this is to account for a very strange eisenbug where bodytrackProperties.getString() would only return the first item before the comma String[] stringArray = env.bodytrackProperties.getStringArray(facetName + ".channel_names"); if (stringArray.length>channelNamesMappings.length) channelNamesMappings = stringArray; List<FieldHandler> fieldHandlers = new ArrayList<FieldHandler>(); for (String mapping : channelNamesMappings) { String[] terms = StringUtils.split(mapping, ":"); if (terms[1].startsWith("#")) { String handlerName = terms[1].substring(1); if (handlerName.equalsIgnoreCase("NOOP")||handlerName.equalsIgnoreCase("OOP")) continue; FieldHandler fieldHandler = getFieldHandler(handlerName.substring(1)); fieldHandlers.add(fieldHandler); } } return fieldHandlers; } private List<List<Object>> getDailyDataChannelValues( List<AbstractFacet> deviceFacets, List<String> dailyDataChannelNames) { List<List<Object>> channelValues = new ArrayList<List<Object>>(); for (AbstractFacet deviceFacet : deviceFacets) { Iterator<String> eachFieldName = dailyDataChannelNames.iterator(); List<Object> values = new ArrayList<Object>(); values.add(deviceFacet.start / 1000.0); while (eachFieldName.hasNext()) { String fieldName = eachFieldName.next(); try { Field field; field = deviceFacet.getClass().getField(fieldName); Object channelValue = field.get(deviceFacet); if (channelValue instanceof java.util.Date) { values.add(((java.util.Date)channelValue).getTime()); } else { values.add(channelValue); } } catch (Exception e) { throw new RuntimeException("No such Field: " + fieldName); } } channelValues.add(values); } return channelValues; } private Map<String, List<AbstractFacet>> sortFacetsByFacetName(List<? extends AbstractFacet> facets) { Map<String, List<AbstractFacet>> facetsByDeviceNickname = new HashMap<String, List<AbstractFacet>>(); for (AbstractFacet facet : facets) { Connector connector = Connector.fromValue(facet.api); String connectorAndObjectType = connector.getName(); if (connector.objectTypes()!=null&&connector.objectTypes().length>0) { ObjectType objectType = ObjectType.getObjectType(connector, facet.objectType); if(objectType !=null) { connectorAndObjectType += "." + objectType.getName(); } } String deviceNickname = getDeviceNickname(connectorAndObjectType); if (deviceNickname==null) { // logger.info("No Device Nickname for " + connectorAndObjectType); continue; } if (facetsByDeviceNickname.get(connectorAndObjectType)==null) facetsByDeviceNickname.put(connectorAndObjectType, new ArrayList<AbstractFacet>()); facetsByDeviceNickname.get(connectorAndObjectType).add(facet); } return facetsByDeviceNickname; } private String getDeviceNickname(String connectorAndObjectType) { Iterator<String> keys = env.bodytrackProperties.getKeys(); while (keys.hasNext()) { String key = keys.next(); if (key.startsWith(connectorAndObjectType)) { if (key.endsWith("dev_nickname")) return (String) env.bodytrackProperties.getProperty(key); } } return null; } @Override public void storeInitialHistory(ApiKey apiKey) { logger.info("module=updateQueue component=bodytrackStorageService action=storeInitialHistory" + " guestId=" + apiKey.getGuestId() + " connector=" + apiKey.getConnector().getName()); TimeInterval timeInterval = new SimpleTimeInterval(0, System.currentTimeMillis(), TimeUnit.ARBITRARY, TimeZone.getDefault()); List<AbstractFacet> facets = apiDataService.getApiDataFacets(apiKey, null, timeInterval, null); storeApiData(apiKey, facets); } @Override public void storeInitialHistory(ApiKey apiKey, int objectTypes) { logger.info("module=updateQueue component=bodytrackStorageService action=storeInitialHistory" + " objectTypes=" + objectTypes + " guestId=" + apiKey.getGuestId() + " connector=" + apiKey.getConnector().getName()); TimeInterval timeInterval = new SimpleTimeInterval(0, System.currentTimeMillis(), TimeUnit.ARBITRARY, TimeZone.getDefault()); final ObjectType[] objectTypesForValue = apiKey.getConnector().getObjectTypesForValue(objectTypes); for (ObjectType objectType : objectTypesForValue) { List<AbstractFacet> facets = apiDataService.getApiDataFacets(apiKey, objectType, timeInterval, null); storeApiData(apiKey, facets); } } }