/*
* 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.ClientIdCacheStore;
import io.seldon.api.resource.ConsumerBean;
import io.seldon.api.resource.DemographicBean;
import io.seldon.api.resource.ListBean;
import io.seldon.api.resource.UserBean;
import io.seldon.general.User;
import io.seldon.memcache.MemCacheKeys;
import io.seldon.memcache.MemCachePeer;
import java.util.Collection;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author claudio
*/
@Service
public class UserService {
@Autowired
private ClientIdCacheStore idCache;
private static Logger logger = Logger.getLogger(UserService.class.getName());
public static final int USERNAME_MAX_LENGTH = 25;
public static UserBean getUser(ConsumerBean c, String uid, boolean full) throws APIException {
UserBean bean = getUserSafe(c, uid, full);
if(bean==null) throw new APIException(APIException.USER_NOT_FOUND);
return bean;
}
public static UserBean getUserSafe(ConsumerBean c, String uid, boolean full) {
String userBeanKey = MemCacheKeys.getUserBeanKey(c.getShort_name(), uid, full);
UserBean bean = (UserBean) MemCachePeer.get(userBeanKey);
if(bean == null) {
User u = Util.getUserPeer(c).getUser(uid);
if(u==null) { return null;}
bean = new UserBean(u,full,c);
if(Constants.CACHING) MemCachePeer.put(userBeanKey, bean,Constants.USERBEAN_CACHING_TIME);
}
return bean;
}
//Does not retrieve inactive users
public static ListBean getUsers(ConsumerBean c, int limit, boolean full) throws APIException {
ListBean bean = (ListBean) MemCachePeer.get(MemCacheKeys.getUsersBeanKey(c.getShort_name(), full));
bean = Util.getLimitedBean(bean, limit);
if(bean == null) {
bean = new ListBean();
Collection<User> res = Util.getUserPeer(c).getRecentUsers(limit);
for(User u : res) { bean.addBean(new UserBean(u,full,c)); }
if(Constants.CACHING) MemCachePeer.put(MemCacheKeys.getUsersBeanKey(c.getShort_name(),full), bean,Constants.CACHING_TIME);
}
return bean;
}
//Does not retrieve inactive users
public static ListBean getUsersByName(ConsumerBean c, int limit, boolean full, String name) throws APIException {
ListBean bean = (ListBean) MemCachePeer.get(MemCacheKeys.getUsersBeanKey(c.getShort_name(), full, name));
bean = Util.getLimitedBean(bean, limit);
if(bean == null) {
bean = new ListBean();
Collection<User> res = Util.getUserPeer(c).getUserByName(name,limit);
for(User u : res) { bean.addBean(new UserBean(u,full,c)); }
if(Constants.CACHING) MemCachePeer.put(MemCacheKeys.getUsersBeanKey(c.getShort_name(),full, name), bean,Constants.CACHING_TIME);
}
return bean;
}
public Long getInternalUserId(ConsumerBean c, String id) throws APIException {
return getInternalUserId(c.getShort_name(), id);
}
public Long getInternalUserId(String consumerShortName, String id) throws APIException {
Long res = null;
res = getInternalUserIdInternal(consumerShortName, id);
if (res == null)
{
logger.info("getInternalUserId(" + id + "): USER NOT FOUND");
throw new APIException(APIException.USER_NOT_FOUND);
}
return res;
}
public Long getInternalUserIdInternal(String consumerShortName, String id) {
Long res = null;
res = idCache.getInternalUserId(consumerShortName, id);
if (res == null)
{
String userInternalId = MemCacheKeys.getUserInternalId(consumerShortName, id);
res = (Long)MemCachePeer.get(userInternalId);
if(res==null) {
User u = Util.getUserPeer(consumerShortName).getUser(id);
if(u!=null) {
res = u.getUserId();
idCache.putUserId(consumerShortName, id, res);
//MemCachePeer.put(userInternalId, res);
cacheInternalUserId(consumerShortName, id, res);
}
}
}
return res;
}
/**
* Cache a user's internal ID, keyed by client ID
* @param consumerBean -
* @param clientId the client user ID (cache key)
* @param internalId the internal user ID (cache value)
*/
public static void cacheInternalUserId(String consumerBean, String clientId, Long internalId) {
String clientIdKey = MemCacheKeys.getUserInternalId(consumerBean, clientId);
MemCachePeer.put(clientIdKey, internalId,Constants.CACHING_TIME);
}
public String getClientUserId(String consumer, Long id){
if(id == null) { throw new APIException(APIException.USER_NOT_FOUND); }
String res = null;
res = idCache.getExternalUserId(consumer, id);
if (res == null)
res = (String)MemCachePeer.get(MemCacheKeys.getUserClientId(consumer, id));
if(res==null) {
User u = Util.getUserPeer(consumer).getUser(id);
if(u!=null) {
res = u.getClientUserId();
// users may be inferred, hence have no client id
if ( res != null ) {
idCache.putUserId(consumer, res, id);
cacheClientUserId(consumer, id, res);
}
}
else {
logger.info("getClientUserId(" + id + "): USER NOT FOUND");
throw new APIException(APIException.USER_NOT_FOUND);
}
}
return res;
}
public String getClientUserId(ConsumerBean c, Long id) throws APIException {
return getClientUserId(c.getShort_name(), id);
}
/**
* Cache a user's client item ID keyed by internal user ID
* @param consumerBean -
* @param internalId the internal user ID (cache key)
* @param clientId the client user ID (cache value)
*/
public static void cacheClientUserId(ConsumerBean consumerBean, Long internalId, String clientId) {
cacheClientUserId(consumerBean.getShort_name(), internalId, clientId);
}
public static void cacheClientUserId(String consumer, Long internalId, String clientId){
final String internalIdKey = MemCacheKeys.getUserClientId(consumer, internalId);
MemCachePeer.put(internalIdKey, clientId,Constants.CACHING_TIME);
}
/**
* Bidirectionally cache the supplied user
* (see {@link UserService#cacheClientUserId(io.seldon.api.resource.ConsumerBean, Long, String)}
* and {@link UserService#cacheInternalUserId(String, String, Long)}).
* @param consumerBean -
* @param internalId user ID
* @param clientId client ID
*/
public static void cacheUser(ConsumerBean consumerBean, Long internalId, String clientId) {
cacheClientUserId(consumerBean, internalId, clientId);
cacheInternalUserId(consumerBean.getShort_name(), clientId, internalId);
}
public User addUser(ConsumerBean c,UserBean bean) {
//check if the user is already in the system
if(bean.getId()!=null) {
Long id = getInternalUserIdInternal(c.getShort_name(),bean.getId());
if(id!=null) throw new APIException(APIException.USER_DUPLICATED);
}
User u = bean.createUser(c);
truncateUsername(u);
Util.getUserPeer(c).persistUser(u);
long userId = u.getUserId();
//attributes
if(bean.getAttributesName() != null && bean.getAttributesName().size()>0) {
Util.getUserPeer(c).addUserAttributeNames(userId,bean.getType(),bean.getAttributesName(),c);
} else if(bean.getAttributes() != null && bean.getAttributes().size()>0) {
Util.getUserPeer(c).addUserAttribute(userId, bean.getType(), bean.getAttributes(),c);
}
cacheUser(c, bean, userId);
return u;
}
private void cacheUser(ConsumerBean c, UserBean bean, long userId) {
idCache.putUserId(c.getShort_name(), bean.getId(), userId);
cacheInternalUserId(c.getShort_name(), bean.getId(), userId);
MemCachePeer.put(MemCacheKeys.getUserBeanKey(c.getShort_name(),
bean.getId(), bean.getAttributesName() != null), bean, Constants.USERBEAN_CACHING_TIME);
}
public User updateUser(ConsumerBean c,UserBean bean,boolean async) {
UserBean user = null;
Long userId;
boolean fullUpdate = (bean.getAttributes()!=null && bean.getAttributes().size() > 0)
|| (bean.getAttributesName()!=null && bean.getAttributesName().size() > 0);
userId = getInternalUserIdInternal(c.getShort_name(), bean.getId());
if(userId==null){
try {
return addUser(c, bean);
} catch (APIException additionException) {
if ( additionException.getError_id() == APIException.USER_DUPLICATED) {
throw new APIException(APIException.CONCURRENT_USER_UPDATE);
} else {
throw additionException;
}
}
}
user = locateUser(c, bean.getId(), userId, fullUpdate);
if(user==null){
// something strange has happened
logger.error("Found user id in cache but couldn't find the actual user object in caches or DB");
throw new APIException(APIException.GENERIC_ERROR);
}
UserBean merged = mergeInUpdate(user, bean);
if(merged == null){
return User.fromUserBean(user, userId);
}
// truncateUsername(user);
// return Util.getUserPeer(c).persistUser(user);
if (merged.getAttributesName() != null && merged.getAttributesName().size() > 0) {
Util.getUserPeer(c).addUserAttributeNames(userId, merged.getType(), merged.getAttributesName(), c);
merged.getAttributesName().putAll(merged.getAttributesName());
} else if (merged.getAttributes() != null && merged.getAttributes().size() > 0) {
Util.getUserPeer(c).addUserAttribute(userId, merged.getType(),merged.getAttributes(), c);
}
String userBeanKeyTrue = MemCacheKeys.getUserBeanKey(c.getShort_name(), bean.getId(), true);
String userBeanKeyFalse = MemCacheKeys.getUserBeanKey(c.getShort_name(), bean.getId(), false);
if(fullUpdate) {
MemCachePeer.put(userBeanKeyTrue, merged, Constants.CACHING_TIME);
MemCachePeer.delete(userBeanKeyFalse);
} else {
//delete memcache entries
MemCachePeer.delete(userBeanKeyTrue);
MemCachePeer.delete(userBeanKeyFalse);
}
return User.fromUserBean(merged, userId);
}
private static UserBean mergeInUpdate(UserBean previous, UserBean update) {
boolean activeDifferent = previous.isActive() != update.isActive();
boolean typeDifferent = previous.getType() != update.getType();
boolean usernameDifferent = !previous.getUsername().equals(update.getUsername());
Map<Integer,Integer> previousAttributes = previous.getAttributes();
Map<Integer, Integer> updateAttributes = update.getAttributes();
boolean attrsDifferent;
if(previousAttributes==null){
attrsDifferent = updateAttributes!=null;
} else {
attrsDifferent = !previousAttributes.equals(updateAttributes);
}
boolean attrsNameDifferent;
Map<String, String> previousAttributesName = previous.getAttributesName();
Map<String, String> updateAttributesName = update.getAttributesName();
if(previousAttributesName==null){
attrsNameDifferent = updateAttributesName!=null;
} else {
attrsNameDifferent = !previousAttributesName.equals(updateAttributesName);
}
if(activeDifferent || typeDifferent || usernameDifferent || attrsDifferent || attrsNameDifferent){
previous.setActive(update.isActive());
previous.setType(update.getType());
previous.setUsername(update.getUsername());
previous.setAttributes(updateAttributes);
previous.setAttributesName(updateAttributesName);
return previous;
} else return null;
}
private static UserBean locateUser(ConsumerBean c, String clientUserId, Long userId, boolean fullRequired) {
UserBean user = null;
String userBeanKey = MemCacheKeys.getUserBeanKey(c.getShort_name(), clientUserId, true);
user = (UserBean) MemCachePeer.get(userBeanKey);
if(user==null){
if(!fullRequired){
userBeanKey = MemCacheKeys.getUserBeanKey(c.getShort_name(), clientUserId, false);
user = (UserBean) MemCachePeer.get(userBeanKey);
}
if(user==null){
User dbUser = Util.getUserPeer(c).getUser(userId);
if(dbUser==null){
return null;// can't find it anywhere so get out of here
}
user = new UserBean(dbUser, fullRequired, c);
}
}
return user;
}
private static void truncateUsername(User user) {
final String username = user.getUsername();
final String truncated = StringUtils.left(username, USERNAME_MAX_LENGTH);
user.setUsername(truncated);
}
public static DemographicBean getDemographic(ConsumerBean c, int demographic) {;
DemographicBean bean = (DemographicBean)MemCachePeer.get(MemCacheKeys.getDemographicBeanKey(c.getShort_name(), demographic));
if(bean == null) {
bean = new DemographicBean();
bean.setDemoId(demographic);
Integer[] attr = Util.getUserPeer(c).getAttributes(demographic);
String[] attrName = Util.getUserPeer(c).getAttributesNames(demographic);
bean.setAttr(attr[0]);
bean.setVal(attr[1]);
// TODO address this properly:
if ( attrName == null ) {
bean.setAttrName("<all>");
bean.setValName("<all>");
} else {
bean.setAttrName(attrName[0]);
bean.setValName(attrName[1]);
}
MemCachePeer.put(MemCacheKeys.getDemographicBeanKey(c.getShort_name(), demographic),bean,Constants.CACHING_TIME);
}
return bean;
}
public static DemographicBean getDemographic(ConsumerBean c,String attrName,String valName) throws APIException {
DemographicBean bean = (DemographicBean)MemCachePeer.get(MemCacheKeys.getDemographicBeanKey(c.getShort_name(),attrName,valName));
if(bean == null) {
int demoId = Util.getUserPeer(c).getDemographic(attrName,valName);
if(demoId != Constants.DEFAULT_DEMOGRAPHIC) {
bean = new DemographicBean();
bean.setAttrName(attrName);
bean.setValName(valName);
Integer[] attr = Util.getUserPeer(c).getAttributes(demoId);
bean.setDemoId(demoId);
bean.setAttr(attr[0]);
bean.setVal(attr[1]);
MemCachePeer.put(MemCacheKeys.getDemographicBeanKey(c.getShort_name(),attrName,valName),bean,Constants.CACHING_TIME);
}
else return null;
}
return bean;
}
}