/*
* Seldon -- open source prediction engine
* =======================================
*
* Copyright 2011-2015 Seldon Technologies Ltd and Rummble Ltd (http://www.seldon.io/)
*
* ********************************************************************************************
*
* Licensed 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 io.seldon.api.resource.service;
import io.seldon.api.APIException;
import io.seldon.api.Constants;
import io.seldon.api.Util;
import io.seldon.api.caching.ActionHistoryProvider;
import io.seldon.api.logging.ActionLogger;
import io.seldon.api.logging.CtrFullLogger;
import io.seldon.api.logging.CtrLogger;
import io.seldon.api.resource.ActionBean;
import io.seldon.api.resource.ActionTypeBean;
import io.seldon.api.resource.ConsumerBean;
import io.seldon.api.resource.ItemBean;
import io.seldon.api.resource.ListBean;
import io.seldon.api.resource.ResourceBean;
import io.seldon.api.resource.UserBean;
import io.seldon.api.resource.service.exception.ActionTypeNotFoundException;
import io.seldon.api.service.async.AsyncActionQueue;
import io.seldon.api.service.async.JdoAsyncActionFactory;
import io.seldon.api.state.ClientAlgorithmStore;
import io.seldon.api.state.options.DefaultOptions;
import io.seldon.api.statsd.StatsdPeer;
import io.seldon.clustering.recommender.ClientClusterTypeService;
import io.seldon.clustering.recommender.CountRecommender;
import io.seldon.clustering.recommender.RecommendationContext;
import io.seldon.clustering.recommender.jdo.JdoCountRecommenderUtils;
import io.seldon.general.Action;
import io.seldon.general.ActionType;
import io.seldon.general.Item;
import io.seldon.general.User;
import io.seldon.memcache.MemCacheKeys;
import io.seldon.memcache.MemCachePeer;
import io.seldon.recommendation.AlgorithmStrategy;
import io.seldon.recommendation.LastRecommendationBean;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author claudio
*/
@Service
public class ActionService {
private static final String BUCKET_CLUSTER_OPTION_NAME = "io.seldon.algorithm.clusters.usebucketcluster";
private static final String FULL_ACTION_STORE_OPTION_NAME = "io.seldon.algorithm.actions.storefullactions";
private static Logger logger = Logger.getLogger(ActionService.class.getName());
@Autowired
private ClientClusterTypeService clusterTypeService;
@Autowired
private ItemService itemService;
@Autowired
private UserService userService;
@Autowired
private ClientAlgorithmStore clientAlgorithmStore;
@Autowired
private DefaultOptions defaultOptions;
@Autowired
private JdoAsyncActionFactory asyncActionFactory;
@Autowired
JdoCountRecommenderUtils cUtils;
@Autowired
ActionHistoryProvider actionProvider;
public ListBean getUserActions(ConsumerBean c, String userId, int limit, boolean full) throws APIException {
ListBean bean = new ListBean();
String clientName = c.getShort_name();
int numActions = limit;
long internalUserId = userService.getInternalUserId(c, userId);
List<Long> recentActionsItemIdList = actionProvider.getRecentActions(clientName, internalUserId, numActions);
for (Long internalItemId : recentActionsItemIdList) {
String clientItemId = itemService.getClientItemId(c, internalItemId);
ActionBean actionBean = new ActionBean(null, userId, clientItemId, 1, null, null, 0);
bean.addBean(actionBean);
}
return bean;
}
public ListBean getItemActions(ConsumerBean c, String itemId, int limit, boolean full) throws APIException {
ListBean bean = (ListBean) MemCachePeer.get(MemCacheKeys.getItemActionsBeanKey(c.getShort_name(), itemId, full, false));
bean = Util.getLimitedBean(bean, limit);
if(bean == null) {
bean = new ListBean();
Collection<Action> res = Util.getActionPeer(c).getItemActions(itemService.getInternalItemId(c, itemId), limit);
for(Action a : res) { bean.addBean(new ActionBean(a,c,full)); }
if(Constants.CACHING) MemCachePeer.put(MemCacheKeys.getItemActionsBeanKey(c.getShort_name(), itemId, full, false),bean,Constants.CACHING_TIME);
}
return bean;
}
public ListBean getActions(ConsumerBean c, String userId, String itemId, int limit, boolean full) throws APIException {
ListBean bean = (ListBean)MemCachePeer.get(MemCacheKeys.getUserItemActionBeanKey(c.getShort_name(), userId, itemId, full, false));
bean = Util.getLimitedBean(bean, limit);
if(bean == null) {
bean = new ListBean();
Collection<Action> res = Util.getActionPeer(c).getUserItemActions(itemService.getInternalItemId(c, itemId), userService.getInternalUserId(c, userId), limit);
for(Action a : res) { bean.addBean(new ActionBean(a,c,full)); }
if(Constants.CACHING) MemCachePeer.put(MemCacheKeys.getUserItemActionBeanKey(c.getShort_name(), userId, itemId, full, false),bean,Constants.CACHING_TIME);
}
return bean;
}
public static ListBean getActions(ConsumerBean c, long userId, long itemId, int limit, boolean full) throws APIException {
ListBean bean = (ListBean)MemCachePeer.get(MemCacheKeys.getInternalUserItemActionBeanKey(c.getShort_name(), userId, itemId, full, false));
bean = Util.getLimitedBean(bean, limit);
if(bean == null) {
bean = new ListBean();
Collection<Action> res = Util.getActionPeer(c).getUserItemActions(itemId, userId, limit);
for(Action a : res) { bean.addBean(new ActionBean(a,c,full)); }
if(Constants.CACHING) MemCachePeer.put(MemCacheKeys.getInternalUserItemActionBeanKey(c.getShort_name(), userId, itemId, full, false),bean,Constants.CACHING_TIME);
}
return bean;
}
// TODO: setup another memcache key for a specific action type?
// public static ListBean getActions(ConsumerBean c, String userId, String itemId, int type, int limit, boolean full) throws APIException {
// ListBean bean = (ListBean)MemCachePeer.get(MemCacheKeys.getUserItemActionBeanKey(c.getShort_name(), userId, itemId, full));
// bean = Util.getLimitedBean(bean, limit);
// if(bean == null) {
// bean = new ListBean();
// Collection<Action> res = Util.getActionPeer(c).getUserItemActions(ItemService.getInternalItemId(c, itemId), UserService.getInternalUserId(c, userId), limit);
// for(Action a : res) { bean.addBean(new ActionBean(a,c,full)); }
// if(Constants.CACHING) MemCachePeer.put(MemCacheKeys.getUserItemActionBeanKey(c.getShort_name(), userId, itemId, full),bean,Constants.CACHING_TIME);
// }
// return bean;
// }
public static ActionBean getAction(ConsumerBean c,long actionId,boolean full) throws APIException {
ActionBean bean = (ActionBean)MemCachePeer.get(MemCacheKeys.getActionBeanKey(c.getShort_name(), actionId, full, false));
if(bean==null) {
Action a = Util.getActionPeer(c).getAction(actionId);
bean = new ActionBean(a,c,full);
if(Constants.CACHING) MemCachePeer.put(MemCacheKeys.getActionBeanKey(c.getShort_name(), actionId, full, false),bean,Constants.CACHING_TIME);
}
return bean;
}
public static ResourceBean getRecentActions(ConsumerBean c,int limit,boolean full) throws APIException {
ListBean bean = new ListBean();
Collection<Action> res = Util.getActionPeer(c).getRecentActions(limit);
for(Action a : res) { bean.addBean(new ActionBean(a,c,full)); }
return bean;
}
public void addAction(ConsumerBean c,ActionBean bean) {
AsyncActionQueue q = null;
if (asyncActionFactory != null)
q = asyncActionFactory.get(c.getShort_name());
boolean doAsyncAction = q != null;
//check if user and item exist
//if not it adds them to the db
Long userId = null;
Long itemId = null;
try
{
itemId = itemService.getInternalItemId(c, bean.getItem());
}
catch(APIException e)
{
if (!doAsyncAction || !JdoAsyncActionFactory.isAsyncItemWrites())
{
if(e.getError_id()==APIException.ITEM_NOT_FOUND)
{
//TODO - addItem can throw an exception if item is now already created - change?
try
{
Item item = itemService.addItem(c, new ItemBean(bean.getItem()));
itemId = item.getItemId();
}
catch (APIException e2)
{
if(e2.getError_id()==APIException.ITEM_DUPLICATED)
itemId = itemService.getInternalItemId(c, bean.getItem());
else
throw e2;
}
}
else
throw e;
}
else
itemId=0L;
};
try
{
userId = userService.getInternalUserId(c, bean.getUser());
}
catch(APIException e)
{
if (!doAsyncAction || !JdoAsyncActionFactory.isAsyncUserWrites())
{
if(e.getError_id()==APIException.USER_NOT_FOUND)
{
//TODO - addUser can throw an exception if user is now already created - change?
try
{
User user = userService.addUser(c, new UserBean(bean.getUser()));
userId = user.getUserId();
}
catch(APIException e2)
{
if(e2.getError_id()==APIException.USER_DUPLICATED)
userId = userService.getInternalUserId(c, bean.getUser());
else
throw e2;
}
}
else
throw e;
}
else
userId=0L;
};
if (userId != null && itemId != null)
{
Action a = bean.createAction(c,userId,itemId);
if (logger.isDebugEnabled())
logger.debug("Action created with Async "+doAsyncAction+" for client "+c.getShort_name()+" userId:"+userId+" itemId:"+itemId+" clientUserId:"+a.getClientUserId()+" clientItemId:"+a.getClientItemId());
try {
ActionLogger.logAsJson(c.getShort_name(), userId, itemId, a.getType(), a.getValue(), a.getClientUserId(), a.getClientItemId(), bean.getRecTag(), bean.getExtraData());
} catch (IOException e) {
final String message = "Unable to log action as json";
logger.error(message, e);
}
if(doAsyncAction)
{
q.put(a);
}
else
{
Util.getActionPeer(c).addAction(a);
}
//Add to count recommender if applicable to client and action cache
if (userId > 0 && itemId > 0)
{
boolean useBucketCluster = false;
boolean storeFullActions = false;
CountRecommender cRec = cUtils.getCountRecommender(c.getShort_name());
for (AlgorithmStrategy strat : clientAlgorithmStore.retrieveStrategy(c.getShort_name()).getAlgorithms(a.getClientUserId(), bean.getRecTag())){
RecommendationContext.OptionsHolder holder = new RecommendationContext.OptionsHolder(defaultOptions, strat.config);
if (holder.getBooleanOption(BUCKET_CLUSTER_OPTION_NAME)) {
useBucketCluster = true;
}
if (holder.getBooleanOption(FULL_ACTION_STORE_OPTION_NAME)) {
storeFullActions = true;
}
}
if (cRec != null)
{
boolean addCount = true;
if (a.getType() != null)
addCount = clusterTypeService.okToClusterCount(c.getShort_name(), a.getType());
if (addCount)
{
Map<Integer,Double> actionWeights = clientAlgorithmStore.retrieveStrategy(c.getShort_name()).getActionsWeights(a.getClientUserId(), bean.getRecTag());
Double actionWeight = actionWeights.get(a.getType());
if (bean.getReferrer() != null)
cRec.setReferrer(bean.getReferrer());
if (a.getDate() != null)
cRec.addCount(userId, itemId,a.getDate().getTime()/1000, useBucketCluster,actionWeight);
else
cRec.addCount(userId, itemId, useBucketCluster,actionWeight);
}
}
actionProvider.addAction(c.getShort_name(),userId, itemId);
if (storeFullActions)
actionProvider.addFullAction(c.getShort_name(), a);
}
}
else {
// logger.error("UserId or ItemId is null when adding action "+bean);
final String message = "UserId or ItemId is null when adding action " + bean;
logger.error(message, new Exception(message));
}
}
public static ActionType getActionType(ConsumerBean c, String name) throws ActionTypeNotFoundException {
String actionTypeKey = MemCacheKeys.getActionTypeByName(c.getShort_name(), name);
ActionType at = (ActionType) MemCachePeer.get(actionTypeKey);
if(at==null) {
at = Util.getActionPeer(c).getActionType(name);
if ( at == null ) {
throw new ActionTypeNotFoundException(c.getShort_name(), name);
}
MemCachePeer.put(actionTypeKey, at,Constants.CACHING_TIME);
}
return at;
}
public static ActionType getActionType(ConsumerBean c, int typeId) {
ActionType at = (ActionType) MemCachePeer.get(MemCacheKeys.getActionTypeById(c.getShort_name(), typeId));
if(at==null) {
at = Util.getActionPeer(c).getActionType(typeId);
MemCachePeer.put(MemCacheKeys.getActionTypeById(c.getShort_name(), typeId), at, Constants.CACHING_TIME);
}
return at;
}
public static ResourceBean getActionTypes(ConsumerBean c) {
ListBean bean = (ListBean)MemCachePeer.get(MemCacheKeys.getActionTypes(c.getShort_name()));
if(bean == null) {
bean = new ListBean();
Collection<ActionType> types = Util.getActionPeer(c).getActionTypes();
for(ActionType t : types) { bean.addBean(new ActionTypeBean(t)); }
MemCachePeer.put(MemCacheKeys.getActionTypes(c.getShort_name()), bean,Constants.CACHING_TIME);
}
return bean;
}
public void logClickAction(ConsumerBean consumerBean, ActionBean actionBean, LastRecommendationBean lastRecs, int clickIndex,
String recTag, String recsCounter) {
StatsdPeer.logClick(consumerBean.getShort_name(), recTag);
String stratName = clientAlgorithmStore.retrieveStrategy(consumerBean.getShort_name()).getName(actionBean.getUser(), recTag);
CtrFullLogger.log(true, consumerBean.getShort_name(), actionBean.getUser(),
actionBean.getItem(), recTag);
String algorithmsString = lastRecs==null? "UNKNOWN": lastRecs.getAlgorithm();
CtrLogger.log(true, consumerBean.getShort_name(), algorithmsString,
clickIndex, actionBean.getUser(), recsCounter,
itemService.getInternalItemId(consumerBean,actionBean.getItem()), 0, "", stratName, recTag);
}
}