/* * Copyright 2010 Research Studios Austria Forschungsgesellschaft mBH * * This file is part of easyrec. * * easyrec is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * easyrec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with easyrec. If not, see <http://www.gnu.org/licenses/>. */ package org.easyrec.plugin.slopeone; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import gnu.trove.iterator.TIntIterator; import gnu.trove.set.TIntSet; import gnu.trove.set.hash.TIntHashSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.easyrec.model.core.TenantVO; import org.easyrec.plugin.model.Version; import org.easyrec.plugin.slopeone.model.*; import org.easyrec.plugin.slopeone.store.dao.ActionDAO; import org.easyrec.plugin.slopeone.store.dao.DeviationDAO; import org.easyrec.plugin.slopeone.store.dao.LogEntryDAO; import org.easyrec.plugin.support.GeneratorPluginSupport; import org.easyrec.service.core.TenantService; import org.easyrec.service.domain.TypeMappingService; import java.net.URI; import java.util.Date; import java.util.List; import java.util.Set; /** * Implementation of the Slope One algorithm [1] as an easyrec-Generator. * <p/> * Utilizes {@link org.easyrec.plugin.slopeone.SlopeOneService}. * <p/> * [1] Lemire and Maclachlan 2005. Slope One Predictors for Online Rating-Base Collaborative Filtering. In SIAM Data * Mining (SDM'05), Newport Beach, California, April 21-23, 2005. */ public class SlopeOneGenerator extends GeneratorPluginSupport<SlopeOneConfiguration, SlopeOneStats> { public static final String DISPLAY_NAME = "SlopeOne"; public static final Version VERSION = new Version("0.98"); public static final URI ID = URI.create("http://www.easyrec.org/plugins/slopeone"); private static final Log logger = LogFactory.getLog(SlopeOneGenerator.class); private SlopeOneService slopeOneService; private TenantService tenantService; private ActionDAO actionDAO; private DeviationDAO deviationDAO; private LogEntryDAO logEntryDAO; private TypeMappingService typeMappingService; public SlopeOneGenerator() { super(DISPLAY_NAME, ID, VERSION, SlopeOneConfiguration.class, SlopeOneStats.class); } @SuppressWarnings({"UnusedDeclaration"}) public void setDeviationDAO(final DeviationDAO deviationDAO) { this.deviationDAO = deviationDAO; } @SuppressWarnings({"UnusedDeclaration"}) public void setLogEntryDAO(final LogEntryDAO logEntryDAO) { this.logEntryDAO = logEntryDAO; } @SuppressWarnings({"UnusedDeclaration"}) public void setSlopeOneService(SlopeOneService slopeOneService) { this.slopeOneService = slopeOneService; } @SuppressWarnings({"UnusedDeclaration"}) public void setTypeMappingService(final TypeMappingService typeMappingService) { this.typeMappingService = typeMappingService; } @Override public String getPluginDescription() { return "This plugin generates item relations based on the Slope One method. It analyzes item ratings and " + "tries to predict how yet unrated items would be rated by the community"; } @Override public SlopeOneConfiguration newConfiguration() { return new SlopeOneConfiguration(); } @Override protected void doInstall() throws Exception { // tables are only created if they don't exist deviationDAO.createTable(); actionDAO.createTable(); logEntryDAO.createTable(); } @Override protected void doUninstall() throws Exception { deviationDAO.dropTable(); actionDAO.dropTable(); logEntryDAO.dropTable(); } @Override protected void doCleanup() throws Exception {} @Override protected void doExecute(final ExecutionControl control, SlopeOneStats stats) throws Exception { control.updateProgress(0, 4, "Started"); SlopeOneConfiguration configuration = getConfiguration(); TenantVO tenant = tenantService.getTenantById(configuration.getTenantId()); LogEntry lastRun = logEntryDAO.getLatestLogEntry(tenant.getId()); Date execution = new Date(); Set<TenantItem> changedItemIds = Sets.newHashSet(); stats.setStartDate(execution); int tenantId = tenant.getId(); List<String> stringItemTypes = configuration.getItemTypes(); if (stringItemTypes.isEmpty()) stringItemTypes = Lists.newArrayList(typeMappingService.getItemTypes(tenantId, true)); TIntSet itemTypes = new TIntHashSet(stringItemTypes.size()); for (String stringItemType : stringItemTypes) { Integer objItemTypeId = typeMappingService.getIdOfItemType(tenantId, stringItemType); objItemTypeId = Preconditions.checkNotNull(objItemTypeId, "configuration value 'itemType'=%stats is " + "invalid.", stringItemType); itemTypes.add(objItemTypeId); } Integer objActionTypeId = typeMappingService.getIdOfActionType(tenantId, configuration.getActionType()); objActionTypeId = Preconditions.checkNotNull(objActionTypeId, "configuration value 'actionType'=%s is invalid.", configuration.getActionType()); Integer objViewTypeId = typeMappingService.getIdOfViewType(tenantId, configuration.getViewType()); objViewTypeId = Preconditions.checkNotNull(objViewTypeId, "configuration value 'viewType'=%s is invalid.", configuration.getViewType()); Integer objAssocTypeId = typeMappingService.getIdOfAssocType(tenantId, configuration.getAssociationType()); objAssocTypeId = Preconditions.checkNotNull(objAssocTypeId, "configuration value 'assocType'=%s is invalid.", configuration.getAssociationType()); Integer objSourceTypeId = typeMappingService.getIdOfSourceType(tenantId, getId().toString()); objSourceTypeId = Preconditions.checkNotNull(objSourceTypeId, "configuration value 'sourceType'=%s is invalid.", getId().toString()); SlopeOneIntegerConfiguration integerConfiguration = new SlopeOneIntegerConfiguration( configuration.getMaxRecsPerItem(), configuration.getMinRatedCount(), configuration.getNonPersonalizedSourceInfo(), objActionTypeId, itemTypes, objViewTypeId, objAssocTypeId, objSourceTypeId, tenantId); LogEntry logEntry = new LogEntry(tenantId, execution, configuration, stats); try { if (control.isAbortRequested()) return; deviationDAO.starting(); control.updateProgress(1, "Generating actions"); slopeOneService.generateActions(integerConfiguration, lastRun, stats); if (logger.isInfoEnabled()) logger.info(String.format("Generated actions in %dms", stats.getActionDuration())); if (control.isAbortRequested()) return; control.updateProgress(2, "Calculating deviations"); slopeOneService.calculateDeviations(integerConfiguration, lastRun.getExecution(), stats, changedItemIds, control); if (logger.isInfoEnabled()) { logger.info(String.format("Calculated deviations in %dms", stats.getDeviationDuration())); logger.info(String.format(" for %d new ratings", stats.getNumberOfActionsConsidered())); logger.info(String.format(" for %d new users", stats.getNoUsers())); logger.info(String.format(" added %d deviations", stats.getNoCreatedDeviations())); logger.info(String.format(" modified %d deviations", stats.getNoModifiedDeviations())); } if (control.isAbortRequested()) return; control.updateProgress(3, "Calculating non-personalized recommendations"); // always generate all item assocs because we have to delete the old assoc values changedItemIds = deviationDAO.getItemIds(tenant.getId(), itemTypes); slopeOneService.nonPersonalizedRecommendations(integerConfiguration, stats, execution, changedItemIds, control); if (logger.isInfoEnabled()) logger.info(String.format("Calculated non-personalized recommendations in %dms", stats.getNonPersonalizedDuration())); control.updateProgress(4, "Finishing ..."); TIntIterator iterator = itemTypes.iterator(); while (iterator.hasNext()) deviationDAO.finished(tenantId, iterator.next()); } catch (Exception e) { stats.setException(e); control.updateProgress(4, "Finishing with error ..."); throw e; } finally { stats.setEndDateToNow(); logEntryDAO.insertLogEntry(logEntry); } } @Override protected void doInitialize() throws Exception { tenantService = getTenantService(); typeMappingService = (TypeMappingService) getTypeMappingService(); } @SuppressWarnings({"UnusedDeclaration"}) public void setSlopeOneActionDAO(final ActionDAO actionDAO) { this.actionDAO = actionDAO; } }