/* * 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.ActionBean; import io.seldon.api.resource.ConsumerBean; import io.seldon.api.resource.DimensionBean; import io.seldon.api.resource.ItemBean; import io.seldon.api.resource.ItemTypeBean; import io.seldon.api.resource.ListBean; import io.seldon.api.resource.ResourceBean; import io.seldon.general.Dimension; import io.seldon.general.Item; import io.seldon.general.ItemAttr; import io.seldon.general.ItemStorage; import io.seldon.general.ItemType; import io.seldon.general.RecommendationStorage; import io.seldon.memcache.MemCacheKeys; import io.seldon.memcache.MemCachePeer; import io.seldon.recommendation.RecommendationPeer; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; 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 ItemService { private static Logger logger = Logger.getLogger(ItemService.class.getName()); public static final int ITEM_NAME_LENGTH = 255; private static final int ITEMS_CACHING_TIME_SECS = 300; @Autowired private RecommendationPeer recommendationPeer; @Autowired private RecommendationStorage recommendationStorage; @Autowired private ItemStorage itemStorage; @Autowired private ClientIdCacheStore idCache; public ItemBean getItem(final ConsumerBean c, final String iid, final boolean full) throws APIException { String memKey = MemCacheKeys.getItemBeanKey(c.getShort_name(), iid,full); ItemBean bean = (ItemBean) MemCachePeer.get(memKey); if(bean == null) { Item i = Util.getItemPeer(c).getItem(iid); if ( i == null ) { // TODO We should throw a checked exception (using APIException for now; minimal surprises). throw new APIException(APIException.ITEM_NOT_FOUND); } bean = new ItemBean(i,full,c); if(Constants.CACHING) MemCachePeer.put(memKey,bean,Constants.CACHING_TIME); } return bean; } public ItemBean getItemLocalized(final ConsumerBean c, final String iid, final boolean full,String locale) throws APIException { String memKey = MemCacheKeys.getItemBeanKeyWithLocale(c.getShort_name(), iid,full,locale); ItemBean bean = (ItemBean) MemCachePeer.get(memKey); if(bean == null) { Item i = Util.getItemPeer(c).getItem(iid); if ( i == null ) { // TODO We should throw a checked exception (using APIException for now; minimal surprises). throw new APIException(APIException.ITEM_NOT_FOUND); } bean = new ItemBean(i,full,c,locale); if(Constants.CACHING) MemCachePeer.put(memKey,bean,Constants.CACHING_TIME); } return bean; } public static ListBean getItems(ConsumerBean c, int limit, boolean full, String sort,int dimension) throws APIException { ListBean bean = (ListBean)MemCachePeer.get(MemCacheKeys.getItemsBeanKey(c.getShort_name(),full,sort,dimension)); bean = Util.getLimitedBean(bean, limit); if(bean == null) { bean = new ListBean(); bean.setRequested(limit); Collection<Item> res = null; if(sort == null || sort.length() == 0 || sort.toLowerCase().equals(Constants.SORT_ID)) { res = Util.getItemPeer(c).getItems(limit,dimension,c); } else if(sort.toLowerCase().equals(Constants.SORT_NAME)) { res = Util.getItemPeer(c).getAlphabeticItems(limit,dimension,c); } else if(sort.toLowerCase().equals(Constants.SORT_LAST_ACTION)) { res = Util.getItemPeer(c).getRecentItems(limit,dimension,c); } if(res!=null) { for(Item i : res) { bean.addBean(new ItemBean(i,full,c)); }} if(bean != null) bean.setSize(bean.getList().size()); if(Constants.CACHING) MemCachePeer.put(MemCacheKeys.getItemsBeanKey(c.getShort_name(), full, sort,dimension),bean,ITEMS_CACHING_TIME_SECS); } return bean; } public static ListBean getItemsByName(ConsumerBean c, int limit, boolean full, String name, int dimension) throws APIException { ListBean bean = (ListBean)MemCachePeer.get(MemCacheKeys.getItemsBeanKeyByName(c.getShort_name(), full, name,dimension)); bean = Util.getLimitedBean(bean, limit); if(bean == null) { bean = new ListBean(); Collection<Item> res = Util.getItemPeer(c).getItemsByName(name,limit,dimension,c); for(Item i : res) { bean.addBean(new ItemBean(i,full,c)); } bean.setRequested(limit); bean.setSize(res.size()); if(Constants.CACHING) MemCachePeer.put(MemCacheKeys.getItemsBeanKeyByName(c.getShort_name(), full, name,dimension),bean,ITEMS_CACHING_TIME_SECS); } return bean; } public String[] getDimensionName(ConsumerBean c,int dimension) { return Util.getItemPeer(c).getAttributesNames(dimension); } public static DimensionBean getDimension(ConsumerBean c,int dimension) throws APIException { if(dimension == Constants.DEFAULT_DIMENSION) return null; DimensionBean bean = (DimensionBean)MemCachePeer.get(MemCacheKeys.getDimensionBeanKey(c.getShort_name(), dimension)); if(bean == null) { bean = new DimensionBean(); bean.setDimId(dimension); int itemType = Util.getItemPeer(c).getDimensionItemType(dimension); Integer[] attr = Util.getItemPeer(c).getAttributes(dimension); String[] attrName = Util.getItemPeer(c).getAttributesNames(dimension); bean.setItemType(itemType); 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.getDimensionBeanKey(c.getShort_name(), dimension),bean,Constants.CACHING_TIME); } return bean; } public static DimensionBean getDimension(ConsumerBean c,int attr,int val) throws APIException { DimensionBean bean = (DimensionBean)MemCachePeer.get(MemCacheKeys.getDimensionBeanKey(c.getShort_name(),attr,val)); if(bean == null) { bean = new DimensionBean(); bean.setAttr(attr); bean.setVal(val); int dimension = Util.getItemPeer(c).getDimension(attr,val); int itemType = Util.getItemPeer(c).getDimensionItemType(dimension); String[] attrName = Util.getItemPeer(c).getAttributesNames(dimension); bean.setDimId(dimension); bean.setItemType(itemType); bean.setAttrName(attrName[0]); bean.setValName(attrName[1]); MemCachePeer.put(MemCacheKeys.getDimensionBeanKey(c.getShort_name(),attr,val),bean,Constants.CACHING_TIME); } return bean; } public static DimensionBean getDimension(ConsumerBean c,String attrName,String valName) throws APIException { DimensionBean bean = (DimensionBean)MemCachePeer.get(MemCacheKeys.getDimensionBeanKey(c.getShort_name(),attrName,valName)); if(bean == null) { int dimension = Util.getItemPeer(c).getDimension(attrName,valName); if(dimension != Constants.DEFAULT_DIMENSION) { bean = new DimensionBean(); bean.setAttrName(attrName); bean.setValName(valName); int itemType = Util.getItemPeer(c).getDimensionItemType(dimension); Integer[] attr = Util.getItemPeer(c).getAttributes(dimension); bean.setDimId(dimension); bean.setItemType(itemType); bean.setAttr(attr[0]); bean.setVal(attr[1]); MemCachePeer.put(MemCacheKeys.getDimensionBeanKey(c.getShort_name(),attrName,valName),bean,Constants.CACHING_TIME); } else return null; } return bean; } public static ListBean getDimensions(ConsumerBean c) throws APIException { ListBean bean = (ListBean)MemCachePeer.get(MemCacheKeys.getDimensionsBeanKey(c.getShort_name())); if(bean == null) { bean = new ListBean(); Collection<Dimension> dims = Util.getItemPeer(c).getDimensions(); for(Dimension d : dims) { bean.addBean(new DimensionBean(d)); } MemCachePeer.put(MemCacheKeys.getDimensionsBeanKey(c.getShort_name()),bean,Constants.CACHING_TIME); } return bean; } public static Integer getDimensionbyItemType(ConsumerBean c,int itemType) throws APIException { Integer res = (Integer)MemCachePeer.get(MemCacheKeys.getDimensionBeanByItemTypeKey(c.getShort_name(),itemType)); if(res == null) { res = Util.getItemPeer(c).getDimensionByItemType(itemType); if (res != null) MemCachePeer.put(MemCacheKeys.getDimensionBeanByItemTypeKey(c.getShort_name(),itemType),res,Constants.CACHING_TIME); else logger.warn("Can't get dimension for item type "+itemType); } return res; } public Long getInternalItemId(ConsumerBean c, String id) throws APIException { Long res = null; res = idCache.getInternalItemId(c.getShort_name(), id); if (res == null) res = (Long)MemCachePeer.get(MemCacheKeys.getItemInternalId(c.getShort_name(), id)); if(res==null) { Item i = Util.getItemPeer(c).getItem(id); if(i!=null) { res = i.getItemId(); idCache.putItemId(c.getShort_name(), id, res); cacheInternalItemId(c, id, res); } else { logger.info("getInternalItemId(" + id + "): ITEM NOT FOUND"); throw new APIException(APIException.ITEM_NOT_FOUND); } } return res; } /** * Cache an item's internal ID keyed by client ID * @param consumerBean - * @param clientId the client item ID (cache key) * @param internalId the internal item ID (cache value) */ public static void cacheInternalItemId(ConsumerBean consumerBean, String clientId, Long internalId) { final String clientIdKey = MemCacheKeys.getItemInternalId(consumerBean.getShort_name(), clientId); MemCachePeer.put(clientIdKey, internalId,Constants.CACHING_TIME); } public String getClientItemId(ConsumerBean c, Long id) throws APIException { if(id == null) { throw new APIException(APIException.ITEM_NOT_FOUND); } String res = null; res = idCache.getExternalItemId(c.getShort_name(), id); if (res == null) res = (String)MemCachePeer.get(MemCacheKeys.getItemClientId(c.getShort_name(), id)); if(res==null) { Item i = Util.getItemPeer(c).getItem(id); if(i!=null) { res = i.getClientItemId(); idCache.putItemId(c.getShort_name(), res, id); cacheClientItemId(c, id, res); } else { logger.info("getClientItemId(" + id + "): ITEM NOT FOUND"); throw new APIException(APIException.ITEM_NOT_FOUND); } } return res; } /** * Cache an item's client ID keyed by item ID * @param consumerBean - * @param internalId the internal item ID (cache key) * @param clientId the client item ID (cache value) */ public static void cacheClientItemId(ConsumerBean consumerBean, Long internalId, String clientId) { final String internalItemKey = MemCacheKeys.getItemClientId(consumerBean.getShort_name(), internalId); MemCachePeer.put(internalItemKey, clientId,Constants.CACHING_TIME); } /** * Bidirectionally cache the supplied item * (see {@link ItemService#cacheClientItemId(io.seldon.api.resource.ConsumerBean, Long, String)} * and {@link ItemService#cacheInternalItemId(io.seldon.api.resource.ConsumerBean, String, Long)}). * @param consumerBean - * @param internalId user ID * @param clientId client ID */ public static void cacheItem(ConsumerBean consumerBean, Long internalId, String clientId) { cacheClientItemId(consumerBean, internalId, clientId); cacheInternalItemId(consumerBean, clientId, internalId); } public static String getAttrType(ConsumerBean c, int itemType,String attrName) { String res = (String)MemCachePeer.get(MemCacheKeys.getItemAttrType(c.getShort_name(), itemType, attrName)); if(res==null) { ItemAttr a = Util.getItemPeer(c).getItemAttr(itemType,attrName); if ( a != null ) { res = a.getType(); MemCachePeer.put(MemCacheKeys.getItemAttrType(c.getShort_name(), itemType, attrName), res,Constants.CACHING_TIME); } } return res; } private static void truncateItemName(ItemBean itemBean, int newLength) { final String name = itemBean.getName(); final String truncatedName = StringUtils.left(name, newLength); logger.info("Truncating itemBean name '" + name + "' to '" + truncatedName + "'"); itemBean.setName(truncatedName); } public Item addItem(ConsumerBean c,ItemBean bean) { //check if the item is already in the system try { getInternalItemId(c, bean.getId()); throw new APIException(APIException.ITEM_DUPLICATED); } catch(APIException e) { if(e.getError_id() != APIException.ITEM_NOT_FOUND) { throw e; } } truncateItemName(bean, ITEM_NAME_LENGTH); Item i = bean.createItem(c); // double check the type is valid, ItemType type = ItemService.getItemType(c, i.getType()); if ( type == null ) { throw new APIException(APIException.ITEM_TYPE_NOT_FOUND); } i = Util.getItemPeer(c).addItem(i,c); long itemId = i.getItemId(); if(bean.getAttributesName() != null && bean.getAttributesName().size()>0) { Util.getItemPeer(c).addItemAttributeNames(itemId, type.getTypeId(), bean.getAttributesName(), c); } else if(bean.getAttributes() != null && bean.getAttributes().size()>0) { Util.getItemPeer(c).addItemAttribute(itemId, type.getTypeId(), bean.getAttributes(),c); } return i; } public void updateItem(ConsumerBean c,ItemBean bean) { Long itemId = null; truncateItemName(bean, ITEM_NAME_LENGTH); //check if the item is already in the system try { itemId = getInternalItemId(c, bean.getId()); Item i = bean.createItem(c); i.setItemId(itemId); // i = Util.getItemPeer(c).addItem(i,c); ItemType type = ItemService.getItemType(c, i.getType()); if ( type == null ) { throw new APIException(APIException.ITEM_TYPE_NOT_FOUND); } i = Util.getItemPeer(c).saveOrUpdate(i,c); // TODO: if(bean.getAttributesName() != null && bean.getAttributesName().size()>0) { Util.getItemPeer(c).addItemAttributeNames(itemId, bean.getType(), bean.getAttributesName(),c); } else if(bean.getAttributes() != null && bean.getAttributes().size()>0) { Util.getItemPeer(c).addItemAttribute(itemId, bean.getType(), bean.getAttributes(),c); } //invalidate memcache entry (both full and short version) MemCachePeer.delete(MemCacheKeys.getItemBeanKey(c.getShort_name(), bean.getId(),true)); MemCachePeer.delete(MemCacheKeys.getItemBeanKey(c.getShort_name(), bean.getId(),false)); MemCachePeer.delete(MemCacheKeys.getItemDimensions(c.getShort_name(), itemId)); } catch(APIException e) { if(e.getError_id() == APIException.ITEM_NOT_FOUND) { try { addItem(c,bean); } catch (APIException additionException) { if ( additionException.getError_id() == APIException.ITEM_DUPLICATED) { throw new APIException(APIException.CONCURRENT_ITEM_UPDATE); } else { throw additionException; } } } else { throw e; } } } public static Collection<Integer> getItemDimensions(ConsumerBean c, long itemId) { Collection<Integer> res = (Collection<Integer>)MemCachePeer.get(MemCacheKeys.getItemDimensions(c.getShort_name(), itemId)); if(res == null) { res = Util.getItemPeer(c).getItemDimensions(itemId); MemCachePeer.put(MemCacheKeys.getItemDimensions(c.getShort_name(), itemId), res,Constants.CACHING_TIME); } return res; } public static Integer getItemCluster(ConsumerBean c, long itemId) { Integer res = (Integer)MemCachePeer.get(MemCacheKeys.getItemCluster(c.getShort_name(), itemId)); if(res == null) { res = Util.getItemPeer(c).getItemCluster(itemId); MemCachePeer.put(MemCacheKeys.getItemCluster(c.getShort_name(), itemId), res,ITEMS_CACHING_TIME_SECS); } return res; } public static ItemType getItemType(ConsumerBean c, String name) { ItemType res = (ItemType) MemCachePeer.get(MemCacheKeys.getItemTypeByName(c.getShort_name(), name)); if(res==null) { ItemType t = Util.getItemPeer(c).getItemType(name); if ( t == null ) { throw new APIException(APIException.ITEM_TYPE_NOT_FOUND); } MemCachePeer.put(MemCacheKeys.getItemTypeByName(c.getShort_name(), name), t,ITEMS_CACHING_TIME_SECS); res = t; } return res; } public static ItemType getItemType(ConsumerBean c, int typeId) { ItemType res = (ItemType) MemCachePeer.get(MemCacheKeys.getItemTypeById(c.getShort_name(), typeId)); if(res==null) { ItemType t = Util.getItemPeer(c).getItemType(typeId); MemCachePeer.put(MemCacheKeys.getItemTypeById(c.getShort_name(), typeId), t,ITEMS_CACHING_TIME_SECS); res = t; } return res; } public static ResourceBean getItemTypes(ConsumerBean c) { ListBean bean = (ListBean)MemCachePeer.get(MemCacheKeys.getItemTypes(c.getShort_name())); if(bean == null) { bean = new ListBean(); Collection<ItemType> types = Util.getItemPeer(c).getItemTypes(); for(ItemType t : types) { bean.addBean(new ItemTypeBean(t)); } MemCachePeer.put(MemCacheKeys.getItemTypes(c.getShort_name()), bean,ITEMS_CACHING_TIME_SECS); } return bean; } public static List<String> getItemSemanticAttributes(ConsumerBean c, long itemId) { List<String> res = (List<String>)MemCachePeer.get(MemCacheKeys.getItemSemanticAttributes(c.getShort_name(),itemId)); if(res==null) { res = Util.getItemPeer(c).getItemSemanticAttributes(itemId); MemCachePeer.put(MemCacheKeys.getItemSemanticAttributes(c.getShort_name(),itemId), res,Constants.CACHING_TIME); } return res; } public static ItemBean filter(ItemBean itemBean, List<String> attributeList) { if(attributeList != null && attributeList.size()>0) { Map<String,String> oldAttributes = itemBean.getAttributesName(); Map<String,String> newAttributes = new HashMap<>(); for(String attribute : attributeList) { newAttributes.put(attribute, oldAttributes.get(attribute)); } itemBean.setAttributesName(newAttributes); } return itemBean; } public void updateIgnoredItems(ConsumerBean consumerBean, ActionBean actionBean, List<Long> ignoredFromLastRecs) { Set<Long> resultingSet = new HashSet<>(); Set<Long> ignoredItems = itemStorage.retrieveIgnoredItems(consumerBean.getShort_name(), actionBean.getUser()); if(ignoredItems!=null) { resultingSet.addAll(ignoredItems); } resultingSet.addAll(ignoredFromLastRecs); itemStorage.persistIgnoredItems(consumerBean.getShort_name(), actionBean.getUser(), resultingSet); } public Map<String,Integer> getDimensionIdsForItem(ConsumerBean c,long itemId) { final String key = MemCacheKeys.getItemAttrDims(c.getShort_name(), itemId); Map<String,Integer> res = (Map<String,Integer>)MemCachePeer.get(key); if(res==null) { res = Util.getItemPeer(c).getDimensionIdsForItem(itemId); MemCachePeer.put(key, res,Constants.CACHING_TIME); } return res; } }