http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/SQL92JDBCDataModel.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/SQL92JDBCDataModel.java b/integration/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/SQL92JDBCDataModel.java deleted file mode 100644 index 39de620..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/impl/model/jdbc/SQL92JDBCDataModel.java +++ /dev/null @@ -1,248 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.model.jdbc; - -import com.google.common.base.Preconditions; -import org.apache.mahout.cf.taste.common.TasteException; -import org.apache.mahout.cf.taste.impl.common.jdbc.AbstractJDBCComponent; -import org.apache.mahout.common.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -/** - * <p> - * A {@link org.apache.mahout.cf.taste.model.JDBCDataModel} backed by a SQL92 compatible database and - * accessed via JDBC. It should work with most JDBC databases, although not optimized for performance. - * By default, this class assumes that there is a {@link javax.sql.DataSource} available under the JNDI name - * "jdbc/taste", which gives access to a database with a "taste_preferences" table with the following schema: - * </p> - * - * <p> - * - * <pre> - * CREATE TABLE taste_preferences ( - * user_id BIGINT NOT NULL, - * item_id BIGINT NOT NULL, - * preference REAL NOT NULL, - * PRIMARY KEY (user_id, item_id) - * ) - * CREATE INDEX taste_preferences_user_id_index ON taste_preferences (user_id); - * CREATE INDEX taste_preferences_item_id_index ON taste_preferences (item_id); - * </pre> - * - * </p> - * - * @see SQL92BooleanPrefJDBCDataModel - */ -public class SQL92JDBCDataModel extends AbstractJDBCDataModel { - - private static final Logger log = LoggerFactory.getLogger(SQL92JDBCDataModel.class); - - private final String updatePreferenceSQL; - private final String verifyPreferenceSQL; - - /** - * <p> - * Creates a using the default {@link javax.sql.DataSource} (named - * {@link #DEFAULT_DATASOURCE_NAME} and default table/column names. - * </p> - * - * @throws org.apache.mahout.cf.taste.common.TasteException - * if {@link javax.sql.DataSource} can't be found - */ - public SQL92JDBCDataModel() throws TasteException { - this(DEFAULT_DATASOURCE_NAME); - } - - /** - * <p> - * Creates a using the default {@link javax.sql.DataSource} found under the given name, and - * using default table/column names. - * </p> - * - * @param dataSourceName - * name of {@link javax.sql.DataSource} to look up - * @throws org.apache.mahout.cf.taste.common.TasteException - * if {@link javax.sql.DataSource} can't be found - */ - public SQL92JDBCDataModel(String dataSourceName) throws TasteException { - this(AbstractJDBCComponent.lookupDataSource(dataSourceName), - DEFAULT_PREFERENCE_TABLE, - DEFAULT_USER_ID_COLUMN, - DEFAULT_ITEM_ID_COLUMN, - DEFAULT_PREFERENCE_COLUMN, - DEFAULT_PREFERENCE_TIME_COLUMN); - } - - /** - * <p> - * Creates a using the given {@link javax.sql.DataSource} and default table/column names. - * </p> - * - * @param dataSource - * {@link javax.sql.DataSource} to use - */ - public SQL92JDBCDataModel(DataSource dataSource) { - this(dataSource, - DEFAULT_PREFERENCE_TABLE, - DEFAULT_USER_ID_COLUMN, - DEFAULT_ITEM_ID_COLUMN, - DEFAULT_PREFERENCE_COLUMN, - DEFAULT_PREFERENCE_TIME_COLUMN); - } - - /** - * <p> - * Creates a using the given {@link javax.sql.DataSource} and default table/column names. - * </p> - * - * @param dataSource - * {@link javax.sql.DataSource} to use - * @param preferenceTable - * name of table containing preference data - * @param userIDColumn - * user ID column name - * @param itemIDColumn - * item ID column name - * @param preferenceColumn - * preference column name - * @param timestampColumn timestamp column name (may be null) - */ - public SQL92JDBCDataModel(DataSource dataSource, - String preferenceTable, - String userIDColumn, - String itemIDColumn, - String preferenceColumn, - String timestampColumn) { - super(dataSource, preferenceTable, userIDColumn, itemIDColumn, preferenceColumn, - // getPreferenceSQL - "SELECT " + preferenceColumn + " FROM " + preferenceTable + " WHERE " + userIDColumn + "=? AND " - + itemIDColumn + "=?", - // getPreferenceTimeSQL - "SELECT " + timestampColumn + " FROM " + preferenceTable + " WHERE " + userIDColumn + "=? AND " - + itemIDColumn + "=?", - // getUserSQL - "SELECT DISTINCT " + userIDColumn + ", " + itemIDColumn + ", " + preferenceColumn + " FROM " + preferenceTable - + " WHERE " + userIDColumn + "=? ORDER BY " + itemIDColumn, - // getAllUsersSQL - "SELECT DISTINCT " + userIDColumn + ", " + itemIDColumn + ", " + preferenceColumn + " FROM " + preferenceTable - + " ORDER BY " + userIDColumn + ", " + itemIDColumn, - // getNumItemsSQL - "SELECT COUNT(DISTINCT " + itemIDColumn + ") FROM " + preferenceTable, - // getNumUsersSQL - "SELECT COUNT(DISTINCT " + userIDColumn + ") FROM " + preferenceTable, - // setPreferenceSQL - "INSERT INTO " + preferenceTable + '(' + userIDColumn + ',' + itemIDColumn + ',' + preferenceColumn - + ") VALUES (?,?,?)", - // removePreference SQL - "DELETE FROM " + preferenceTable + " WHERE " + userIDColumn + "=? AND " + itemIDColumn + "=?", - // getUsersSQL - "SELECT DISTINCT " + userIDColumn + " FROM " + preferenceTable + " ORDER BY " + userIDColumn, - // getItemsSQL - "SELECT DISTINCT " + itemIDColumn + " FROM " + preferenceTable + " ORDER BY " + itemIDColumn, - // getPrefsForItemSQL - "SELECT DISTINCT " + userIDColumn + ", " + itemIDColumn + ", " + preferenceColumn + " FROM " + preferenceTable - + " WHERE " + itemIDColumn + "=? ORDER BY " + userIDColumn, - // getNumPreferenceForItemSQL - "SELECT COUNT(1) FROM " + preferenceTable + " WHERE " + itemIDColumn + "=?", - // getNumPreferenceForItemsSQL - "SELECT COUNT(1) FROM " + preferenceTable + " tp1 JOIN " + preferenceTable + " tp2 " + "USING (" - + userIDColumn + ") WHERE tp1." + itemIDColumn + "=? and tp2." + itemIDColumn + "=?", - // getMaxPreferenceSQL - "SELECT MAX(" + preferenceColumn + ") FROM " + preferenceTable, - // getMinPreferenceSQL - "SELECT MIN(" + preferenceColumn + ") FROM " + preferenceTable); - - updatePreferenceSQL = "UPDATE " + preferenceTable + " SET " + preferenceColumn + "=? WHERE " + userIDColumn - + "=? AND " + itemIDColumn + "=?"; - verifyPreferenceSQL = "SELECT " + preferenceColumn + " FROM " + preferenceTable + " WHERE " + userIDColumn - + "=? AND " + itemIDColumn + "=?"; - } - - protected String getUpdatePreferenceSQL() { - return updatePreferenceSQL; - } - - protected String getVerifyPreferenceSQL() { - return verifyPreferenceSQL; - } - - /** - * Override since SQL92 doesn't have the same non-standard capability that MySQL has, to optionally - * insert or update in one statement. - */ - @Override - public void setPreference(long userID, long itemID, float value) throws TasteException { - Preconditions.checkArgument(!Float.isNaN(value), "NaN value"); - log.debug("Setting preference for user {}, item {}", userID, itemID); - - String setPreferenceSQL = getSetPreferenceSQL(); - - Connection conn = null; - PreparedStatement stmt1 = null; - PreparedStatement stmt2 = null; - PreparedStatement stmt3 = null; - ResultSet rs = null; - try { - conn = getDataSource().getConnection(); - - stmt1 = conn.prepareStatement(verifyPreferenceSQL, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - setLongParameter(stmt1, 1, userID); - setLongParameter(stmt1, 2, itemID); - rs = stmt1.executeQuery(); - - // test if the record exists already. - if (rs.first()) { - // then we update the record. - stmt2 = conn.prepareStatement(updatePreferenceSQL); - stmt2.setDouble(1, value); - setLongParameter(stmt2, 2, userID); - setLongParameter(stmt2, 3, itemID); - - log.debug("Executing SQL update: {}", updatePreferenceSQL); - stmt2.executeUpdate(); - - } else { - // we'll insert the record - stmt3 = conn.prepareStatement(setPreferenceSQL); - setLongParameter(stmt3, 1, userID); - setLongParameter(stmt3, 2, itemID); - stmt3.setDouble(3, value); - - log.debug("Executing SQL update: {}", setPreferenceSQL); - stmt3.executeUpdate(); - } - } catch (SQLException sqle) { - log.warn("Exception while setting preference", sqle); - throw new TasteException(sqle); - } finally { - IOUtils.quietClose(rs); - IOUtils.quietClose(stmt1); - IOUtils.quietClose(stmt2); - IOUtils.quietClose(stmt3); - IOUtils.quietClose(conn); - } - } - -}
http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/impl/model/mongodb/MongoDBDataModel.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/impl/model/mongodb/MongoDBDataModel.java b/integration/src/main/java/org/apache/mahout/cf/taste/impl/model/mongodb/MongoDBDataModel.java deleted file mode 100644 index 92a4019..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/impl/model/mongodb/MongoDBDataModel.java +++ /dev/null @@ -1,873 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.model.mongodb; - -import com.google.common.base.Preconditions; -import com.mongodb.BasicDBObject; -import com.mongodb.DB; -import com.mongodb.DBCollection; -import com.mongodb.DBCursor; -import com.mongodb.DBObject; -import com.mongodb.Mongo; -import org.apache.mahout.cf.taste.common.NoSuchItemException; -import org.apache.mahout.cf.taste.common.NoSuchUserException; -import org.apache.mahout.cf.taste.common.Refreshable; -import org.apache.mahout.cf.taste.common.TasteException; -import org.apache.mahout.cf.taste.impl.common.FastByIDMap; -import org.apache.mahout.cf.taste.impl.common.FastIDSet; -import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator; -import org.apache.mahout.cf.taste.impl.model.GenericDataModel; -import org.apache.mahout.cf.taste.impl.model.GenericPreference; -import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray; -import org.apache.mahout.cf.taste.model.DataModel; -import org.apache.mahout.cf.taste.model.Preference; -import org.apache.mahout.cf.taste.model.PreferenceArray; -import org.bson.types.ObjectId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.UnknownHostException; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; -import java.util.regex.Pattern; - -/** - * <p>A {@link DataModel} backed by a MongoDB database. This class expects a - * collection in the database which contains a user ID ({@code long} or - * {@link ObjectId}), item ID ({@code long} or - * {@link ObjectId}), preference value (optional) and timestamps - * ("created_at", "deleted_at").</p> - * - * <p>An example of a document in MongoDB:</p> - * - * <p>{@code { "_id" : ObjectId("4d7627bf6c7d47ade9fc7780"), - * "user_id" : ObjectId("4c2209fef3924d31102bd84b"), - * "item_id" : ObjectId(4c2209fef3924d31202bd853), - * "preference" : 0.5, - * "created_at" : "Tue Mar 23 2010 20:48:43 GMT-0400 (EDT)" } - * }</p> - * - * <p>Preference value is optional to accommodate applications that have no notion - * of a preference value (that is, the user simply expresses a preference for - * an item, but no degree of preference).</p> - * - * <p>The preference value is assumed to be parseable as a {@code double}.</p> - * - * <p>The user IDs and item IDs are assumed to be parseable as {@code long}s - * or {@link ObjectId}s. In case of {@link ObjectId}s, the - * model creates a {@code Map<ObjectId>}, {@code long}> - * (collection "mongo_data_model_map") inside the MongoDB database. This - * conversion is needed since Mahout uses the long datatype to feed the - * recommender, and MongoDB uses 12 bytes to create its identifiers.</p> - * - * <p>The timestamps ("created_at", "deleted_at"), if present, are assumed to be - * parseable as a {@code long} or {@link Date}. To express - * timestamps as {@link Date}s, a {@link DateFormat} - * must be provided in the class constructor. The default Date format is - * {@code "EE MMM dd yyyy HH:mm:ss 'GMT'Z (zzz)"}. If this parameter - * is set to null, timestamps are assumed to be parseable as {@code long}s. - * </p> - * - * <p>It is also acceptable for the documents to contain additional fields. - * Those fields will be ignored.</p> - * - * <p>This class will reload data from the MondoDB database when - * {@link #refresh(Collection)} is called. MongoDBDataModel keeps the - * timestamp of the last update. This variable and the fields "created_at" - * and "deleted_at" help the model to determine if the triple - * (user, item, preference) must be added or deleted.</p> - */ -public final class MongoDBDataModel implements DataModel { - - private static final Logger log = LoggerFactory.getLogger(MongoDBDataModel.class); - - /** Default MongoDB host. Default: localhost */ - private static final String DEFAULT_MONGO_HOST = "localhost"; - - /** Default MongoDB port. Default: 27017 */ - private static final int DEFAULT_MONGO_PORT = 27017; - - /** Default MongoDB database. Default: recommender */ - private static final String DEFAULT_MONGO_DB = "recommender"; - - /** - * Default MongoDB authentication flag. - * Default: false (authentication is not required) - */ - private static final boolean DEFAULT_MONGO_AUTH = false; - - /** Default MongoDB user. Default: recommender */ - private static final String DEFAULT_MONGO_USERNAME = "recommender"; - - /** Default MongoDB password. Default: recommender */ - private static final String DEFAULT_MONGO_PASSWORD = "recommender"; - - /** Default MongoDB table/collection. Default: items */ - private static final String DEFAULT_MONGO_COLLECTION = "items"; - - /** - * Default MongoDB update flag. When this flag is activated, the - * DataModel updates both model and database. Default: true - */ - private static final boolean DEFAULT_MONGO_MANAGE = true; - - /** Default MongoDB user ID field. Default: user_id */ - private static final String DEFAULT_MONGO_USER_ID = "user_id"; - - /** Default MongoDB item ID field. Default: item_id */ - private static final String DEFAULT_MONGO_ITEM_ID = "item_id"; - - /** Default MongoDB preference value field. Default: preference */ - private static final String DEFAULT_MONGO_PREFERENCE = "preference"; - - /** Default MongoDB final remove flag. Default: false */ - private static final boolean DEFAULT_MONGO_FINAL_REMOVE = false; - - /** - * Default MongoDB date format. - * Default: "EE MMM dd yyyy HH:mm:ss 'GMT'Z (zzz)" - */ - private static final DateFormat DEFAULT_DATE_FORMAT = - new SimpleDateFormat("EE MMM dd yyyy HH:mm:ss 'GMT'Z (zzz)", Locale.ENGLISH); - - public static final String DEFAULT_MONGO_MAP_COLLECTION = "mongo_data_model_map"; - - private static final Pattern ID_PATTERN = Pattern.compile("[a-f0-9]{24}"); - - /** MongoDB host */ - private String mongoHost = DEFAULT_MONGO_HOST; - /** MongoDB port */ - private int mongoPort = DEFAULT_MONGO_PORT; - /** MongoDB database */ - private String mongoDB = DEFAULT_MONGO_DB; - /** - * MongoDB authentication flag. If this flag is set to false, - * authentication is not required. - */ - private boolean mongoAuth = DEFAULT_MONGO_AUTH; - /** MongoDB user */ - private String mongoUsername = DEFAULT_MONGO_USERNAME; - /** MongoDB pass */ - private String mongoPassword = DEFAULT_MONGO_PASSWORD; - /** MongoDB table/collection */ - private String mongoCollection = DEFAULT_MONGO_COLLECTION; - /** MongoDB mapping table/collection */ - private String mongoMapCollection = DEFAULT_MONGO_MAP_COLLECTION; - /** - * MongoDB update flag. When this flag is activated, the - * DataModel updates both model and database - */ - private boolean mongoManage = DEFAULT_MONGO_MANAGE; - /** MongoDB user ID field */ - private String mongoUserID = DEFAULT_MONGO_USER_ID; - /** MongoDB item ID field */ - private String mongoItemID = DEFAULT_MONGO_ITEM_ID; - /** MongoDB preference value field */ - private String mongoPreference = DEFAULT_MONGO_PREFERENCE; - /** MongoDB final remove flag. Default: false */ - private boolean mongoFinalRemove = DEFAULT_MONGO_FINAL_REMOVE; - /** MongoDB date format */ - private DateFormat dateFormat = DEFAULT_DATE_FORMAT; - private DBCollection collection; - private DBCollection collectionMap; - private Date mongoTimestamp; - private final ReentrantLock reloadLock; - private DataModel delegate; - private boolean userIsObject; - private boolean itemIsObject; - private boolean preferenceIsString; - private long idCounter; - - /** - * Creates a new MongoDBDataModel - */ - public MongoDBDataModel() throws UnknownHostException { - this.reloadLock = new ReentrantLock(); - buildModel(); - } - - /** - * Creates a new MongoDBDataModel with MongoDB basic configuration - * (without authentication) - * - * @param host MongoDB host. - * @param port MongoDB port. Default: 27017 - * @param database MongoDB database - * @param collection MongoDB collection/table - * @param manage If true, the model adds and removes users and items - * from MongoDB database when the model is refreshed. - * @param finalRemove If true, the model removes the user/item completely - * from the MongoDB database. If false, the model adds the "deleted_at" - * field with the current date to the "deleted" user/item. - * @param format MongoDB date format. If null, the model uses timestamps. - * @throws UnknownHostException if the database host cannot be resolved - */ - public MongoDBDataModel(String host, - int port, - String database, - String collection, - boolean manage, - boolean finalRemove, - DateFormat format) throws UnknownHostException { - mongoHost = host; - mongoPort = port; - mongoDB = database; - mongoCollection = collection; - mongoManage = manage; - mongoFinalRemove = finalRemove; - dateFormat = format; - this.reloadLock = new ReentrantLock(); - buildModel(); - } - - /** - * Creates a new MongoDBDataModel with MongoDB advanced configuration - * (without authentication) - * - * @param userIDField Mongo user ID field - * @param itemIDField Mongo item ID field - * @param preferenceField Mongo preference value field - * @throws UnknownHostException if the database host cannot be resolved - * @see #MongoDBDataModel(String, int, String, String, boolean, boolean, DateFormat) - */ - public MongoDBDataModel(String host, - int port, - String database, - String collection, - boolean manage, - boolean finalRemove, - DateFormat format, - String userIDField, - String itemIDField, - String preferenceField, - String mappingCollection) throws UnknownHostException { - mongoHost = host; - mongoPort = port; - mongoDB = database; - mongoCollection = collection; - mongoManage = manage; - mongoFinalRemove = finalRemove; - dateFormat = format; - mongoUserID = userIDField; - mongoItemID = itemIDField; - mongoPreference = preferenceField; - mongoMapCollection = mappingCollection; - this.reloadLock = new ReentrantLock(); - buildModel(); - } - - /** - * Creates a new MongoDBDataModel with MongoDB basic configuration - * (with authentication) - * - * @param user Mongo username (authentication) - * @param password Mongo password (authentication) - * @throws UnknownHostException if the database host cannot be resolved - * @see #MongoDBDataModel(String, int, String, String, boolean, boolean, DateFormat) - */ - public MongoDBDataModel(String host, - int port, - String database, - String collection, - boolean manage, - boolean finalRemove, - DateFormat format, - String user, - String password) throws UnknownHostException { - mongoHost = host; - mongoPort = port; - mongoDB = database; - mongoCollection = collection; - mongoManage = manage; - mongoFinalRemove = finalRemove; - dateFormat = format; - mongoAuth = true; - mongoUsername = user; - mongoPassword = password; - this.reloadLock = new ReentrantLock(); - buildModel(); - } - - /** - * Creates a new MongoDBDataModel with MongoDB advanced configuration - * (with authentication) - * - * @throws UnknownHostException if the database host cannot be resolved - * @see #MongoDBDataModel(String, int, String, String, boolean, boolean, DateFormat, String, String) - */ - public MongoDBDataModel(String host, - int port, - String database, - String collection, - boolean manage, - boolean finalRemove, - DateFormat format, - String user, - String password, - String userIDField, - String itemIDField, - String preferenceField, - String mappingCollection) throws UnknownHostException { - mongoHost = host; - mongoPort = port; - mongoDB = database; - mongoCollection = collection; - mongoManage = manage; - mongoFinalRemove = finalRemove; - dateFormat = format; - mongoAuth = true; - mongoUsername = user; - mongoPassword = password; - mongoUserID = userIDField; - mongoItemID = itemIDField; - mongoPreference = preferenceField; - mongoMapCollection = mappingCollection; - this.reloadLock = new ReentrantLock(); - buildModel(); - } - - /** - * <p> - * Adds/removes (user, item) pairs to/from the model. - * </p> - * - * @param userID MongoDB user identifier - * @param items List of pairs (item, preference) which want to be added or - * deleted - * @param add If true, this flag indicates that the pairs (user, item) - * must be added to the model. If false, it indicates deletion. - * @see #refresh(Collection) - */ - public void refreshData(String userID, - Iterable<List<String>> items, - boolean add) throws NoSuchUserException, NoSuchItemException { - checkData(userID, items, add); - long id = Long.parseLong(fromIdToLong(userID, true)); - for (List<String> item : items) { - item.set(0, fromIdToLong(item.get(0), false)); - } - if (reloadLock.tryLock()) { - try { - if (add) { - delegate = addUserItem(id, items); - } else { - delegate = removeUserItem(id, items); - } - } finally { - reloadLock.unlock(); - } - } - } - - - /** - * <p> - * Triggers "refresh" -- whatever that means -- of the implementation. - * The general contract is that any should always leave itself in a - * consistent, operational state, and that the refresh atomically updates - * internal state from old to new. - * </p> - * - * @param alreadyRefreshed s that are known to have already been refreshed as - * a result of an initial call to a method on some object. This ensures - * that objects in a refresh dependency graph aren't refreshed twice - * needlessly. - * @see #refreshData(String, Iterable, boolean) - */ - @Override - public void refresh(Collection<Refreshable> alreadyRefreshed) { - BasicDBObject query = new BasicDBObject(); - query.put("deleted_at", new BasicDBObject("$gt", mongoTimestamp)); - DBCursor cursor = collection.find(query); - Date ts = new Date(0); - while (cursor.hasNext()) { - Map<String,Object> user = (Map<String,Object>) cursor.next().toMap(); - String userID = getID(user.get(mongoUserID), true); - Collection<List<String>> items = new ArrayList<>(); - List<String> item = new ArrayList<>(); - item.add(getID(user.get(mongoItemID), false)); - item.add(Float.toString(getPreference(user.get(mongoPreference)))); - items.add(item); - try { - refreshData(userID, items, false); - } catch (NoSuchUserException e) { - log.warn("No such user ID: {}", userID); - } catch (NoSuchItemException e) { - log.warn("No such items: {}", items); - } - if (ts.compareTo(getDate(user.get("created_at"))) < 0) { - ts = getDate(user.get("created_at")); - } - } - query = new BasicDBObject(); - query.put("created_at", new BasicDBObject("$gt", mongoTimestamp)); - cursor = collection.find(query); - while (cursor.hasNext()) { - Map<String,Object> user = (Map<String,Object>) cursor.next().toMap(); - if (!user.containsKey("deleted_at")) { - String userID = getID(user.get(mongoUserID), true); - Collection<List<String>> items = new ArrayList<>(); - List<String> item = new ArrayList<>(); - item.add(getID(user.get(mongoItemID), false)); - item.add(Float.toString(getPreference(user.get(mongoPreference)))); - items.add(item); - try { - refreshData(userID, items, true); - } catch (NoSuchUserException e) { - log.warn("No such user ID: {}", userID); - } catch (NoSuchItemException e) { - log.warn("No such items: {}", items); - } - if (ts.compareTo(getDate(user.get("created_at"))) < 0) { - ts = getDate(user.get("created_at")); - } - } - } - if (mongoTimestamp.compareTo(ts) < 0) { - mongoTimestamp = ts; - } - } - - /** - * <p> - * Translates the MongoDB identifier to Mahout/MongoDBDataModel's internal - * identifier, if required. - * </p> - * <p> - * If MongoDB identifiers are long datatypes, it returns the id. - * </p> - * <p> - * This conversion is needed since Mahout uses the long datatype to feed the - * recommender, and MongoDB uses 12 bytes to create its identifiers. - * </p> - * - * @param id MongoDB identifier - * @param isUser - * @return String containing the translation of the external MongoDB ID to - * internal long ID (mapping). - * @see #fromLongToId(long) - * @see <a href="http://www.mongodb.org/display/DOCS/Object%20IDs"> - * Mongo Object IDs</a> - */ - public String fromIdToLong(String id, boolean isUser) { - DBObject objectIdLong = collectionMap.findOne(new BasicDBObject("element_id", id)); - if (objectIdLong != null) { - Map<String,Object> idLong = (Map<String,Object>) objectIdLong.toMap(); - Object value = idLong.get("long_value"); - return value == null ? null : value.toString(); - } else { - objectIdLong = new BasicDBObject(); - String longValue = Long.toString(idCounter++); - objectIdLong.put("element_id", id); - objectIdLong.put("long_value", longValue); - collectionMap.insert(objectIdLong); - log.info("Adding Translation {}: {} long_value: {}", - isUser ? "User ID" : "Item ID", id, longValue); - return longValue; - } - } - - /** - * <p> - * Translates the Mahout/MongoDBDataModel's internal identifier to MongoDB - * identifier, if required. - * </p> - * <p> - * If MongoDB identifiers are long datatypes, it returns the id in String - * format. - * </p> - * <p> - * This conversion is needed since Mahout uses the long datatype to feed the - * recommender, and MongoDB uses 12 bytes to create its identifiers. - * </p> - * - * @param id Mahout's internal identifier - * @return String containing the translation of the internal long ID to - * external MongoDB ID (mapping). - * @see #fromIdToLong(String, boolean) - * @see <a href="http://www.mongodb.org/display/DOCS/Object%20IDs"> - * Mongo Object IDs</a> - */ - public String fromLongToId(long id) { - DBObject objectIdLong = collectionMap.findOne(new BasicDBObject("long_value", Long.toString(id))); - Map<String,Object> idLong = (Map<String,Object>) objectIdLong.toMap(); - Object value = idLong.get("element_id"); - return value == null ? null : value.toString(); - } - - /** - * <p> - * Checks if an ID is currently in the model. - * </p> - * - * @param ID user or item ID - * @return true: if ID is into the model; false: if it's not. - */ - public boolean isIDInModel(String ID) { - DBObject objectIdLong = collectionMap.findOne(new BasicDBObject("element_id", ID)); - return objectIdLong != null; - } - - /** - * <p> - * Date of the latest update of the model. - * </p> - * - * @return Date with the latest update of the model. - */ - public Date mongoUpdateDate() { - return mongoTimestamp; - } - - private void buildModel() throws UnknownHostException { - userIsObject = false; - itemIsObject = false; - idCounter = 0; - preferenceIsString = true; - Mongo mongoDDBB = new Mongo(mongoHost, mongoPort); - DB db = mongoDDBB.getDB(mongoDB); - mongoTimestamp = new Date(0); - FastByIDMap<Collection<Preference>> userIDPrefMap = new FastByIDMap<>(); - if (!mongoAuth || db.authenticate(mongoUsername, mongoPassword.toCharArray())) { - collection = db.getCollection(mongoCollection); - collectionMap = db.getCollection(mongoMapCollection); - DBObject indexObj = new BasicDBObject(); - indexObj.put("element_id", 1); - collectionMap.ensureIndex(indexObj); - indexObj = new BasicDBObject(); - indexObj.put("long_value", 1); - collectionMap.ensureIndex(indexObj); - collectionMap.remove(new BasicDBObject()); - DBCursor cursor = collection.find(); - while (cursor.hasNext()) { - Map<String,Object> user = (Map<String,Object>) cursor.next().toMap(); - if (!user.containsKey("deleted_at")) { - long userID = Long.parseLong(fromIdToLong(getID(user.get(mongoUserID), true), true)); - long itemID = Long.parseLong(fromIdToLong(getID(user.get(mongoItemID), false), false)); - float ratingValue = getPreference(user.get(mongoPreference)); - Collection<Preference> userPrefs = userIDPrefMap.get(userID); - if (userPrefs == null) { - userPrefs = new ArrayList<>(2); - userIDPrefMap.put(userID, userPrefs); - } - userPrefs.add(new GenericPreference(userID, itemID, ratingValue)); - if (user.containsKey("created_at") - && mongoTimestamp.compareTo(getDate(user.get("created_at"))) < 0) { - mongoTimestamp = getDate(user.get("created_at")); - } - } - } - } - delegate = new GenericDataModel(GenericDataModel.toDataMap(userIDPrefMap, true)); - } - - private void removeMongoUserItem(String userID, String itemID) { - String userId = fromLongToId(Long.parseLong(userID)); - String itemId = fromLongToId(Long.parseLong(itemID)); - if (isUserItemInDB(userId, itemId)) { - mongoTimestamp = new Date(); - BasicDBObject query = new BasicDBObject(); - query.put(mongoUserID, userIsObject ? new ObjectId(userId) : userId); - query.put(mongoItemID, itemIsObject ? new ObjectId(itemId) : itemId); - if (mongoFinalRemove) { - log.info(collection.remove(query).toString()); - } else { - BasicDBObject update = new BasicDBObject(); - update.put("$set", new BasicDBObject("deleted_at", mongoTimestamp)); - log.info(collection.update(query, update).toString()); - } - log.info("Removing userID: {} itemID: {}", userID, itemId); - } - } - - private void addMongoUserItem(String userID, String itemID, String preferenceValue) { - String userId = fromLongToId(Long.parseLong(userID)); - String itemId = fromLongToId(Long.parseLong(itemID)); - if (!isUserItemInDB(userId, itemId)) { - mongoTimestamp = new Date(); - BasicDBObject user = new BasicDBObject(); - Object userIdObject = userIsObject ? new ObjectId(userId) : userId; - Object itemIdObject = itemIsObject ? new ObjectId(itemId) : itemId; - user.put(mongoUserID, userIdObject); - user.put(mongoItemID, itemIdObject); - user.put(mongoPreference, preferenceIsString ? preferenceValue : Double.parseDouble(preferenceValue)); - user.put("created_at", mongoTimestamp); - collection.insert(user); - log.info("Adding userID: {} itemID: {} preferenceValue: {}", userID, itemID, preferenceValue); - } - } - - private boolean isUserItemInDB(String userID, String itemID) { - BasicDBObject query = new BasicDBObject(); - Object userId = userIsObject ? new ObjectId(userID) : userID; - Object itemId = itemIsObject ? new ObjectId(itemID) : itemID; - query.put(mongoUserID, userId); - query.put(mongoItemID, itemId); - return collection.findOne(query) != null; - } - - private DataModel removeUserItem(long userID, Iterable<List<String>> items) { - FastByIDMap<PreferenceArray> rawData = ((GenericDataModel) delegate).getRawUserData(); - for (List<String> item : items) { - PreferenceArray prefs = rawData.get(userID); - long itemID = Long.parseLong(item.get(0)); - if (prefs != null) { - boolean exists = false; - int length = prefs.length(); - for (int i = 0; i < length; i++) { - if (prefs.getItemID(i) == itemID) { - exists = true; - break; - } - } - if (exists) { - rawData.remove(userID); - if (length > 1) { - PreferenceArray newPrefs = new GenericUserPreferenceArray(length - 1); - for (int i = 0, j = 0; i < length; i++, j++) { - if (prefs.getItemID(i) == itemID) { - j--; - } else { - newPrefs.set(j, prefs.get(i)); - } - } - rawData.put(userID, newPrefs); - } - log.info("Removing userID: {} itemID: {}", userID, itemID); - if (mongoManage) { - removeMongoUserItem(Long.toString(userID), Long.toString(itemID)); - } - } - } - } - return new GenericDataModel(rawData); - } - - private DataModel addUserItem(long userID, Iterable<List<String>> items) { - FastByIDMap<PreferenceArray> rawData = ((GenericDataModel) delegate).getRawUserData(); - PreferenceArray prefs = rawData.get(userID); - for (List<String> item : items) { - long itemID = Long.parseLong(item.get(0)); - float preferenceValue = Float.parseFloat(item.get(1)); - boolean exists = false; - if (prefs != null) { - for (int i = 0; i < prefs.length(); i++) { - if (prefs.getItemID(i) == itemID) { - exists = true; - prefs.setValue(i, preferenceValue); - break; - } - } - } - if (!exists) { - if (prefs == null) { - prefs = new GenericUserPreferenceArray(1); - } else { - PreferenceArray newPrefs = new GenericUserPreferenceArray(prefs.length() + 1); - for (int i = 0, j = 1; i < prefs.length(); i++, j++) { - newPrefs.set(j, prefs.get(i)); - } - prefs = newPrefs; - } - prefs.setUserID(0, userID); - prefs.setItemID(0, itemID); - prefs.setValue(0, preferenceValue); - log.info("Adding userID: {} itemID: {} preferenceValue: {}", userID, itemID, preferenceValue); - rawData.put(userID, prefs); - if (mongoManage) { - addMongoUserItem(Long.toString(userID), - Long.toString(itemID), - Float.toString(preferenceValue)); - } - } - } - return new GenericDataModel(rawData); - } - - private Date getDate(Object date) { - if (date.getClass().getName().contains("Date")) { - return (Date) date; - } - if (date.getClass().getName().contains("String")) { - try { - synchronized (dateFormat) { - return dateFormat.parse(date.toString()); - } - } catch (ParseException ioe) { - log.warn("Error parsing timestamp", ioe); - } - } - return new Date(0); - } - - private float getPreference(Object value) { - if (value != null) { - if (value.getClass().getName().contains("String")) { - preferenceIsString = true; - return Float.parseFloat(value.toString()); - } else { - preferenceIsString = false; - return Double.valueOf(value.toString()).floatValue(); - } - } else { - return 0.5f; - } - } - - private String getID(Object id, boolean isUser) { - if (id.getClass().getName().contains("ObjectId")) { - if (isUser) { - userIsObject = true; - } else { - itemIsObject = true; - } - return ((ObjectId) id).toStringMongod(); - } else { - return id.toString(); - } - } - - private void checkData(String userID, - Iterable<List<String>> items, - boolean add) throws NoSuchUserException, NoSuchItemException { - Preconditions.checkNotNull(userID); - Preconditions.checkNotNull(items); - Preconditions.checkArgument(!userID.isEmpty(), "userID is empty"); - for (List<String> item : items) { - Preconditions.checkNotNull(item.get(0)); - Preconditions.checkArgument(!item.get(0).isEmpty(), "item is empty"); - } - if (userIsObject && !ID_PATTERN.matcher(userID).matches()) { - throw new IllegalArgumentException(); - } - for (List<String> item : items) { - if (itemIsObject && !ID_PATTERN.matcher(item.get(0)).matches()) { - throw new IllegalArgumentException(); - } - } - if (!add && !isIDInModel(userID)) { - throw new NoSuchUserException(); - } - for (List<String> item : items) { - if (!add && !isIDInModel(item.get(0))) { - throw new NoSuchItemException(); - } - } - } - - /** - * Cleanup mapping collection. - */ - public void cleanupMappingCollection() { - collectionMap.drop(); - } - - @Override - public LongPrimitiveIterator getUserIDs() throws TasteException { - return delegate.getUserIDs(); - } - - @Override - public PreferenceArray getPreferencesFromUser(long id) throws TasteException { - return delegate.getPreferencesFromUser(id); - } - - @Override - public FastIDSet getItemIDsFromUser(long userID) throws TasteException { - return delegate.getItemIDsFromUser(userID); - } - - @Override - public LongPrimitiveIterator getItemIDs() throws TasteException { - return delegate.getItemIDs(); - } - - @Override - public PreferenceArray getPreferencesForItem(long itemID) throws TasteException { - return delegate.getPreferencesForItem(itemID); - } - - @Override - public Float getPreferenceValue(long userID, long itemID) throws TasteException { - return delegate.getPreferenceValue(userID, itemID); - } - - @Override - public Long getPreferenceTime(long userID, long itemID) throws TasteException { - return delegate.getPreferenceTime(userID, itemID); - } - - @Override - public int getNumItems() throws TasteException { - return delegate.getNumItems(); - } - - @Override - public int getNumUsers() throws TasteException { - return delegate.getNumUsers(); - } - - @Override - public int getNumUsersWithPreferenceFor(long itemID) throws TasteException { - return delegate.getNumUsersWithPreferenceFor(itemID); - } - - @Override - public int getNumUsersWithPreferenceFor(long itemID1, long itemID2) throws TasteException { - return delegate.getNumUsersWithPreferenceFor(itemID1, itemID2); - } - - @Override - public void setPreference(long userID, long itemID, float value) { - throw new UnsupportedOperationException(); - } - - @Override - public void removePreference(long userID, long itemID) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasPreferenceValues() { - return delegate.hasPreferenceValues(); - } - - @Override - public float getMaxPreference() { - return delegate.getMaxPreference(); - } - - @Override - public float getMinPreference() { - return delegate.getMinPreference(); - } - - @Override - public String toString() { - return "MongoDBDataModel"; - } - -} http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/AbstractJDBCInMemoryItemSimilarity.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/AbstractJDBCInMemoryItemSimilarity.java b/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/AbstractJDBCInMemoryItemSimilarity.java deleted file mode 100644 index 3ae9990..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/AbstractJDBCInMemoryItemSimilarity.java +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.similarity.jdbc; - -import org.apache.mahout.cf.taste.common.Refreshable; -import org.apache.mahout.cf.taste.common.TasteException; -import org.apache.mahout.cf.taste.impl.common.jdbc.AbstractJDBCComponent; -import org.apache.mahout.cf.taste.impl.common.jdbc.ResultSetIterator; -import org.apache.mahout.cf.taste.impl.model.jdbc.ConnectionPoolDataSource; -import org.apache.mahout.cf.taste.impl.similarity.GenericItemSimilarity; -import org.apache.mahout.cf.taste.similarity.ItemSimilarity; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.sql.DataSource; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collection; -import java.util.Iterator; -import java.util.concurrent.locks.ReentrantLock; - -/** - * loads all similarities from the database into RAM - */ -abstract class AbstractJDBCInMemoryItemSimilarity extends AbstractJDBCComponent implements ItemSimilarity { - - private ItemSimilarity delegate; - - private final DataSource dataSource; - private final String getAllItemSimilaritiesSQL; - private final ReentrantLock reloadLock; - - private static final Logger log = LoggerFactory.getLogger(AbstractJDBCInMemoryItemSimilarity.class); - - AbstractJDBCInMemoryItemSimilarity(DataSource dataSource, String getAllItemSimilaritiesSQL) { - - AbstractJDBCComponent.checkNotNullAndLog("getAllItemSimilaritiesSQL", getAllItemSimilaritiesSQL); - - if (!(dataSource instanceof ConnectionPoolDataSource)) { - log.warn("You are not using ConnectionPoolDataSource. Make sure your DataSource pools connections " - + "to the database itself, or database performance will be severely reduced."); - } - - this.dataSource = dataSource; - this.getAllItemSimilaritiesSQL = getAllItemSimilaritiesSQL; - this.reloadLock = new ReentrantLock(); - - reload(); - } - - @Override - public double itemSimilarity(long itemID1, long itemID2) throws TasteException { - return delegate.itemSimilarity(itemID1, itemID2); - } - - @Override - public double[] itemSimilarities(long itemID1, long[] itemID2s) throws TasteException { - return delegate.itemSimilarities(itemID1, itemID2s); - } - - @Override - public long[] allSimilarItemIDs(long itemID) throws TasteException { - return delegate.allSimilarItemIDs(itemID); - } - - @Override - public void refresh(Collection<Refreshable> alreadyRefreshed) { - log.debug("Reloading..."); - reload(); - } - - protected void reload() { - if (reloadLock.tryLock()) { - try { - delegate = new GenericItemSimilarity(new JDBCSimilaritiesIterable(dataSource, getAllItemSimilaritiesSQL)); - } finally { - reloadLock.unlock(); - } - } - } - - private static final class JDBCSimilaritiesIterable implements Iterable<GenericItemSimilarity.ItemItemSimilarity> { - - private final DataSource dataSource; - private final String getAllItemSimilaritiesSQL; - - private JDBCSimilaritiesIterable(DataSource dataSource, String getAllItemSimilaritiesSQL) { - this.dataSource = dataSource; - this.getAllItemSimilaritiesSQL = getAllItemSimilaritiesSQL; - } - - @Override - public Iterator<GenericItemSimilarity.ItemItemSimilarity> iterator() { - try { - return new JDBCSimilaritiesIterator(dataSource, getAllItemSimilaritiesSQL); - } catch (SQLException sqle) { - throw new IllegalStateException(sqle); - } - } - } - - private static final class JDBCSimilaritiesIterator - extends ResultSetIterator<GenericItemSimilarity.ItemItemSimilarity> { - - private JDBCSimilaritiesIterator(DataSource dataSource, String getAllItemSimilaritiesSQL) throws SQLException { - super(dataSource, getAllItemSimilaritiesSQL); - } - - @Override - protected GenericItemSimilarity.ItemItemSimilarity parseElement(ResultSet resultSet) throws SQLException { - return new GenericItemSimilarity.ItemItemSimilarity(resultSet.getLong(1), - resultSet.getLong(2), - resultSet.getDouble(3)); - } - } - -} http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/AbstractJDBCItemSimilarity.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/AbstractJDBCItemSimilarity.java b/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/AbstractJDBCItemSimilarity.java deleted file mode 100644 index 1b8d109..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/AbstractJDBCItemSimilarity.java +++ /dev/null @@ -1,213 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.similarity.jdbc; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collection; - -import javax.sql.DataSource; - -import org.apache.mahout.cf.taste.common.Refreshable; -import org.apache.mahout.cf.taste.common.TasteException; -import org.apache.mahout.cf.taste.impl.common.FastIDSet; -import org.apache.mahout.cf.taste.impl.common.jdbc.AbstractJDBCComponent; -import org.apache.mahout.cf.taste.impl.model.jdbc.ConnectionPoolDataSource; -import org.apache.mahout.cf.taste.similarity.ItemSimilarity; -import org.apache.mahout.common.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * An {@link ItemSimilarity} which draws pre-computed item-item similarities from a database table via JDBC. - */ -public abstract class AbstractJDBCItemSimilarity extends AbstractJDBCComponent implements ItemSimilarity { - - private static final Logger log = LoggerFactory.getLogger(AbstractJDBCItemSimilarity.class); - - static final String DEFAULT_SIMILARITY_TABLE = "taste_item_similarity"; - static final String DEFAULT_ITEM_A_ID_COLUMN = "item_id_a"; - static final String DEFAULT_ITEM_B_ID_COLUMN = "item_id_b"; - static final String DEFAULT_SIMILARITY_COLUMN = "similarity"; - - private final DataSource dataSource; - private final String similarityTable; - private final String itemAIDColumn; - private final String itemBIDColumn; - private final String similarityColumn; - private final String getItemItemSimilaritySQL; - private final String getAllSimilarItemIDsSQL; - - protected AbstractJDBCItemSimilarity(DataSource dataSource, - String getItemItemSimilaritySQL, - String getAllSimilarItemIDsSQL) { - this(dataSource, - DEFAULT_SIMILARITY_TABLE, - DEFAULT_ITEM_A_ID_COLUMN, - DEFAULT_ITEM_B_ID_COLUMN, - DEFAULT_SIMILARITY_COLUMN, - getItemItemSimilaritySQL, - getAllSimilarItemIDsSQL); - } - - protected AbstractJDBCItemSimilarity(DataSource dataSource, - String similarityTable, - String itemAIDColumn, - String itemBIDColumn, - String similarityColumn, - String getItemItemSimilaritySQL, - String getAllSimilarItemIDsSQL) { - AbstractJDBCComponent.checkNotNullAndLog("similarityTable", similarityTable); - AbstractJDBCComponent.checkNotNullAndLog("itemAIDColumn", itemAIDColumn); - AbstractJDBCComponent.checkNotNullAndLog("itemBIDColumn", itemBIDColumn); - AbstractJDBCComponent.checkNotNullAndLog("similarityColumn", similarityColumn); - - AbstractJDBCComponent.checkNotNullAndLog("getItemItemSimilaritySQL", getItemItemSimilaritySQL); - AbstractJDBCComponent.checkNotNullAndLog("getAllSimilarItemIDsSQL", getAllSimilarItemIDsSQL); - - if (!(dataSource instanceof ConnectionPoolDataSource)) { - log.warn("You are not using ConnectionPoolDataSource. Make sure your DataSource pools connections " - + "to the database itself, or database performance will be severely reduced."); - } - - this.dataSource = dataSource; - this.similarityTable = similarityTable; - this.itemAIDColumn = itemAIDColumn; - this.itemBIDColumn = itemBIDColumn; - this.similarityColumn = similarityColumn; - this.getItemItemSimilaritySQL = getItemItemSimilaritySQL; - this.getAllSimilarItemIDsSQL = getAllSimilarItemIDsSQL; - } - - protected String getSimilarityTable() { - return similarityTable; - } - - protected String getItemAIDColumn() { - return itemAIDColumn; - } - - protected String getItemBIDColumn() { - return itemBIDColumn; - } - - protected String getSimilarityColumn() { - return similarityColumn; - } - - @Override - public double itemSimilarity(long itemID1, long itemID2) throws TasteException { - if (itemID1 == itemID2) { - return 1.0; - } - Connection conn = null; - PreparedStatement stmt = null; - try { - conn = dataSource.getConnection(); - stmt = conn.prepareStatement(getItemItemSimilaritySQL, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - stmt.setFetchDirection(ResultSet.FETCH_FORWARD); - stmt.setFetchSize(getFetchSize()); - return doItemSimilarity(stmt, itemID1, itemID2); - } catch (SQLException sqle) { - log.warn("Exception while retrieving similarity", sqle); - throw new TasteException(sqle); - } finally { - IOUtils.quietClose(null, stmt, conn); - } - } - - @Override - public double[] itemSimilarities(long itemID1, long[] itemID2s) throws TasteException { - double[] result = new double[itemID2s.length]; - Connection conn = null; - PreparedStatement stmt = null; - try { - conn = dataSource.getConnection(); - stmt = conn.prepareStatement(getItemItemSimilaritySQL, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - stmt.setFetchDirection(ResultSet.FETCH_FORWARD); - stmt.setFetchSize(getFetchSize()); - for (int i = 0; i < itemID2s.length; i++) { - result[i] = doItemSimilarity(stmt, itemID1, itemID2s[i]); - } - } catch (SQLException sqle) { - log.warn("Exception while retrieving item similarities", sqle); - throw new TasteException(sqle); - } finally { - IOUtils.quietClose(null, stmt, conn); - } - return result; - } - - @Override - public long[] allSimilarItemIDs(long itemID) throws TasteException { - FastIDSet allSimilarItemIDs = new FastIDSet(); - Connection conn = null; - PreparedStatement stmt = null; - ResultSet rs = null; - try { - conn = dataSource.getConnection(); - stmt = conn.prepareStatement(getAllSimilarItemIDsSQL, ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY); - stmt.setFetchDirection(ResultSet.FETCH_FORWARD); - stmt.setFetchSize(getFetchSize()); - stmt.setLong(1, itemID); - stmt.setLong(2, itemID); - rs = stmt.executeQuery(); - while (rs.next()) { - allSimilarItemIDs.add(rs.getLong(1)); - allSimilarItemIDs.add(rs.getLong(2)); - } - } catch (SQLException sqle) { - log.warn("Exception while retrieving all similar itemIDs", sqle); - throw new TasteException(sqle); - } finally { - IOUtils.quietClose(rs, stmt, conn); - } - allSimilarItemIDs.remove(itemID); - return allSimilarItemIDs.toArray(); - } - - @Override - public void refresh(Collection<Refreshable> alreadyRefreshed) { - // do nothing - } - - private double doItemSimilarity(PreparedStatement stmt, long itemID1, long itemID2) throws SQLException { - // Order as smaller - larger - if (itemID1 > itemID2) { - long temp = itemID1; - itemID1 = itemID2; - itemID2 = temp; - } - stmt.setLong(1, itemID1); - stmt.setLong(2, itemID2); - log.debug("Executing SQL query: {}", getItemItemSimilaritySQL); - ResultSet rs = null; - try { - rs = stmt.executeQuery(); - // If not found, perhaps the items exist but have no presence in the table, - // so NaN is appropriate - return rs.next() ? rs.getDouble(1) : Double.NaN; - } finally { - IOUtils.quietClose(rs); - } - } - -} http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/MySQLJDBCInMemoryItemSimilarity.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/MySQLJDBCInMemoryItemSimilarity.java b/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/MySQLJDBCInMemoryItemSimilarity.java deleted file mode 100644 index cc831d9..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/MySQLJDBCInMemoryItemSimilarity.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.similarity.jdbc; - -import org.apache.mahout.cf.taste.common.TasteException; - -import javax.sql.DataSource; - -public class MySQLJDBCInMemoryItemSimilarity extends SQL92JDBCInMemoryItemSimilarity { - - public MySQLJDBCInMemoryItemSimilarity() throws TasteException { - } - - public MySQLJDBCInMemoryItemSimilarity(String dataSourceName) throws TasteException { - super(dataSourceName); - } - - public MySQLJDBCInMemoryItemSimilarity(DataSource dataSource) { - super(dataSource); - } - - public MySQLJDBCInMemoryItemSimilarity(DataSource dataSource, String getAllItemSimilaritiesSQL) { - super(dataSource, getAllItemSimilaritiesSQL); - } - - @Override - protected int getFetchSize() { - // Need to return this for MySQL Connector/J to make it use streaming mode - return Integer.MIN_VALUE; - } - -} http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/MySQLJDBCItemSimilarity.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/MySQLJDBCItemSimilarity.java b/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/MySQLJDBCItemSimilarity.java deleted file mode 100644 index af0742e..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/MySQLJDBCItemSimilarity.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.similarity.jdbc; - -import javax.sql.DataSource; - -import org.apache.mahout.cf.taste.common.TasteException; - -/** - * <p> - * An {@link org.apache.mahout.cf.taste.similarity.ItemSimilarity} backed by a MySQL database - * and accessed via JDBC. It may work with other JDBC - * databases. By default, this class assumes that there is a {@link DataSource} available under the JNDI name - * "jdbc/taste", which gives access to a database with a "taste_item_similarity" table with the following - * schema: - * </p> - * - * <table> - * <tr> - * <th>item_id_a</th> - * <th>item_id_b</th> - * <th>similarity</th> - * </tr> - * <tr> - * <td>ABC</td> - * <td>DEF</td> - * <td>0.9</td> - * </tr> - * <tr> - * <td>DEF</td> - * <td>EFG</td> - * <td>0.1</td> - * </tr> - * </table> - * - * <p> - * For example, the following command sets up a suitable table in MySQL, complete with primary key and - * indexes: - * </p> - * - * <p> - * - * <pre> - * CREATE TABLE taste_item_similarity ( - * item_id_a BIGINT NOT NULL, - * item_id_b BIGINT NOT NULL, - * similarity FLOAT NOT NULL, - * PRIMARY KEY (item_id_a, item_id_b), - * ) - * </pre> - * - * </p> - * - * <p> - * Note that for each row, item_id_a should be less than item_id_b. It is redundant to store it both ways, - * so the pair is always stored as a pair with the lesser one first. - * - * @see org.apache.mahout.cf.taste.impl.model.jdbc.MySQLJDBCDataModel - */ -public class MySQLJDBCItemSimilarity extends SQL92JDBCItemSimilarity { - - public MySQLJDBCItemSimilarity() throws TasteException { - } - - public MySQLJDBCItemSimilarity(String dataSourceName) throws TasteException { - super(dataSourceName); - } - - public MySQLJDBCItemSimilarity(DataSource dataSource) { - super(dataSource); - } - - public MySQLJDBCItemSimilarity(DataSource dataSource, - String similarityTable, - String itemAIDColumn, - String itemBIDColumn, - String similarityColumn) { - super(dataSource, similarityTable, itemAIDColumn, itemBIDColumn, similarityColumn); - } - - @Override - protected int getFetchSize() { - // Need to return this for MySQL Connector/J to make it use streaming mode - return Integer.MIN_VALUE; - } - -} - http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/SQL92JDBCInMemoryItemSimilarity.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/SQL92JDBCInMemoryItemSimilarity.java b/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/SQL92JDBCInMemoryItemSimilarity.java deleted file mode 100644 index b311a5e..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/SQL92JDBCInMemoryItemSimilarity.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.similarity.jdbc; - -import org.apache.mahout.cf.taste.common.TasteException; -import org.apache.mahout.cf.taste.impl.common.jdbc.AbstractJDBCComponent; - -import javax.sql.DataSource; - -public class SQL92JDBCInMemoryItemSimilarity extends AbstractJDBCInMemoryItemSimilarity { - - static final String DEFAULT_GET_ALL_ITEMSIMILARITIES_SQL = - "SELECT " + AbstractJDBCItemSimilarity.DEFAULT_ITEM_A_ID_COLUMN + ", " - + AbstractJDBCItemSimilarity.DEFAULT_ITEM_B_ID_COLUMN + ", " - + AbstractJDBCItemSimilarity.DEFAULT_SIMILARITY_COLUMN + " FROM " - + AbstractJDBCItemSimilarity.DEFAULT_SIMILARITY_TABLE; - - - public SQL92JDBCInMemoryItemSimilarity() throws TasteException { - this(AbstractJDBCComponent.lookupDataSource(AbstractJDBCComponent.DEFAULT_DATASOURCE_NAME), - DEFAULT_GET_ALL_ITEMSIMILARITIES_SQL); - } - - public SQL92JDBCInMemoryItemSimilarity(String dataSourceName) throws TasteException { - this(AbstractJDBCComponent.lookupDataSource(dataSourceName), DEFAULT_GET_ALL_ITEMSIMILARITIES_SQL); - } - - public SQL92JDBCInMemoryItemSimilarity(DataSource dataSource) { - this(dataSource, DEFAULT_GET_ALL_ITEMSIMILARITIES_SQL); - } - - public SQL92JDBCInMemoryItemSimilarity(DataSource dataSource, String getAllItemSimilaritiesSQL) { - super(dataSource, getAllItemSimilaritiesSQL); - } - -} http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/SQL92JDBCItemSimilarity.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/SQL92JDBCItemSimilarity.java b/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/SQL92JDBCItemSimilarity.java deleted file mode 100644 index f449561..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/impl/similarity/jdbc/SQL92JDBCItemSimilarity.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.similarity.jdbc; - -import org.apache.mahout.cf.taste.common.TasteException; - -import javax.sql.DataSource; - -public class SQL92JDBCItemSimilarity extends AbstractJDBCItemSimilarity { - - public SQL92JDBCItemSimilarity() throws TasteException { - this(DEFAULT_DATASOURCE_NAME); - } - - public SQL92JDBCItemSimilarity(String dataSourceName) throws TasteException { - this(lookupDataSource(dataSourceName)); - } - - public SQL92JDBCItemSimilarity(DataSource dataSource) { - this(dataSource, - DEFAULT_SIMILARITY_TABLE, - DEFAULT_ITEM_A_ID_COLUMN, - DEFAULT_ITEM_B_ID_COLUMN, - DEFAULT_SIMILARITY_COLUMN); - } - - public SQL92JDBCItemSimilarity(DataSource dataSource, - String similarityTable, - String itemAIDColumn, - String itemBIDColumn, - String similarityColumn) { - super(dataSource, - similarityTable, - itemAIDColumn, - itemBIDColumn, similarityColumn, - "SELECT " + similarityColumn + " FROM " + similarityTable + " WHERE " - + itemAIDColumn + "=? AND " + itemBIDColumn + "=?", - "SELECT " + itemAIDColumn + ", " + itemBIDColumn + " FROM " + similarityTable + " WHERE " - + itemAIDColumn + "=? OR " + itemBIDColumn + "=?"); - } - -} http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderServlet.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderServlet.java b/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderServlet.java deleted file mode 100644 index a5a89c6..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderServlet.java +++ /dev/null @@ -1,215 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.web; - -import org.apache.mahout.cf.taste.common.TasteException; -import org.apache.mahout.cf.taste.model.DataModel; -import org.apache.mahout.cf.taste.model.Preference; -import org.apache.mahout.cf.taste.model.PreferenceArray; -import org.apache.mahout.cf.taste.recommender.RecommendedItem; -import org.apache.mahout.cf.taste.recommender.Recommender; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.List; - -/** - * <p>A servlet which returns recommendations, as its name implies. The servlet accepts GET and POST - * HTTP requests, and looks for two parameters:</p> - * - * <ul> - * <li><em>userID</em>: the user ID for which to produce recommendations</li> - * <li><em>howMany</em>: the number of recommendations to produce</li> - * <li><em>debug</em>: (optional) output a lot of information that is useful in debugging. - * Defaults to false, of course.</li> - * </ul> - * - * <p>The response is text, and contains a list of the IDs of recommended items, in descending - * order of relevance, one per line.</p> - * - * <p>For example, you can get 10 recommendations for user 123 from the following URL (assuming - * you are running taste in a web application running locally on port 8080):<br/> - * {@code http://localhost:8080/taste/RecommenderServlet?userID=123&howMany=10}</p> - * - * <p>This servlet requires one {@code init-param} in {@code web.xml}: it must find - * a parameter named "recommender-class" which is the name of a class that implements - * {@link Recommender} and has a no-arg constructor. The servlet will instantiate and use - * this {@link Recommender} to produce recommendations.</p> - */ -public final class RecommenderServlet extends HttpServlet { - - private static final int NUM_TOP_PREFERENCES = 20; - private static final int DEFAULT_HOW_MANY = 20; - - private Recommender recommender; - - @Override - public void init(ServletConfig config) throws ServletException { - super.init(config); - String recommenderClassName = config.getInitParameter("recommender-class"); - if (recommenderClassName == null) { - throw new ServletException("Servlet init-param \"recommender-class\" is not defined"); - } - RecommenderSingleton.initializeIfNeeded(recommenderClassName); - recommender = RecommenderSingleton.getInstance().getRecommender(); - } - - @Override - public void doGet(HttpServletRequest request, - HttpServletResponse response) throws ServletException { - - String userIDString = request.getParameter("userID"); - if (userIDString == null) { - throw new ServletException("userID was not specified"); - } - long userID = Long.parseLong(userIDString); - String howManyString = request.getParameter("howMany"); - int howMany = howManyString == null ? DEFAULT_HOW_MANY : Integer.parseInt(howManyString); - boolean debug = Boolean.parseBoolean(request.getParameter("debug")); - String format = request.getParameter("format"); - if (format == null) { - format = "text"; - } - - try { - List<RecommendedItem> items = recommender.recommend(userID, howMany); - if ("text".equals(format)) { - writePlainText(response, userID, debug, items); - } else if ("xml".equals(format)) { - writeXML(response, items); - } else if ("json".equals(format)) { - writeJSON(response, items); - } else { - throw new ServletException("Bad format parameter: " + format); - } - } catch (TasteException | IOException te) { - throw new ServletException(te); - } - - } - - private static void writeXML(HttpServletResponse response, Iterable<RecommendedItem> items) throws IOException { - response.setContentType("application/xml"); - response.setCharacterEncoding("UTF-8"); - response.setHeader("Cache-Control", "no-cache"); - PrintWriter writer = response.getWriter(); - writer.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?><recommendedItems>"); - for (RecommendedItem recommendedItem : items) { - writer.print("<item><value>"); - writer.print(recommendedItem.getValue()); - writer.print("</value><id>"); - writer.print(recommendedItem.getItemID()); - writer.print("</id></item>"); - } - writer.println("</recommendedItems>"); - } - - private static void writeJSON(HttpServletResponse response, Iterable<RecommendedItem> items) throws IOException { - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - response.setHeader("Cache-Control", "no-cache"); - PrintWriter writer = response.getWriter(); - writer.print("{\"recommendedItems\":{\"item\":["); - boolean first = true; - for (RecommendedItem recommendedItem : items) { - if (first) { - first = false; - } else { - writer.print(','); - } - writer.print("{\"value\":\""); - writer.print(recommendedItem.getValue()); - writer.print("\",\"id\":\""); - writer.print(recommendedItem.getItemID()); - writer.print("\"}"); - } - writer.println("]}}"); - } - - private void writePlainText(HttpServletResponse response, - long userID, - boolean debug, - Iterable<RecommendedItem> items) throws IOException, TasteException { - response.setContentType("text/plain"); - response.setCharacterEncoding("UTF-8"); - response.setHeader("Cache-Control", "no-cache"); - PrintWriter writer = response.getWriter(); - if (debug) { - writeDebugRecommendations(userID, items, writer); - } else { - writeRecommendations(items, writer); - } - } - - private static void writeRecommendations(Iterable<RecommendedItem> items, PrintWriter writer) { - for (RecommendedItem recommendedItem : items) { - writer.print(recommendedItem.getValue()); - writer.print('\t'); - writer.println(recommendedItem.getItemID()); - } - } - - private void writeDebugRecommendations(long userID, Iterable<RecommendedItem> items, PrintWriter writer) - throws TasteException { - DataModel dataModel = recommender.getDataModel(); - writer.print("User:"); - writer.println(userID); - writer.print("Recommender: "); - writer.println(recommender); - writer.println(); - writer.print("Top "); - writer.print(NUM_TOP_PREFERENCES); - writer.println(" Preferences:"); - PreferenceArray rawPrefs = dataModel.getPreferencesFromUser(userID); - int length = rawPrefs.length(); - PreferenceArray sortedPrefs = rawPrefs.clone(); - sortedPrefs.sortByValueReversed(); - // Cap this at NUM_TOP_PREFERENCES just to be brief - int max = Math.min(NUM_TOP_PREFERENCES, length); - for (int i = 0; i < max; i++) { - Preference pref = sortedPrefs.get(i); - writer.print(pref.getValue()); - writer.print('\t'); - writer.println(pref.getItemID()); - } - writer.println(); - writer.println("Recommendations:"); - for (RecommendedItem recommendedItem : items) { - writer.print(recommendedItem.getValue()); - writer.print('\t'); - writer.println(recommendedItem.getItemID()); - } - } - - @Override - public void doPost(HttpServletRequest request, - HttpServletResponse response) throws ServletException { - doGet(request, response); - } - - @Override - public String toString() { - return "RecommenderServlet[recommender:" + recommender + ']'; - } - -} http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderSingleton.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderSingleton.java b/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderSingleton.java deleted file mode 100644 index 265d7c0..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderSingleton.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.web; - -import org.apache.mahout.cf.taste.recommender.Recommender; -import org.apache.mahout.common.ClassUtils; - -/** - * <p>A singleton which holds an instance of a {@link Recommender}. This is used to share - * a {@link Recommender} between {@link RecommenderServlet} and {@code RecommenderService.jws}.</p> - */ -public final class RecommenderSingleton { - - private final Recommender recommender; - - private static RecommenderSingleton instance; - - public static synchronized RecommenderSingleton getInstance() { - if (instance == null) { - throw new IllegalStateException("Not initialized"); - } - return instance; - } - - public static synchronized void initializeIfNeeded(String recommenderClassName) { - if (instance == null) { - instance = new RecommenderSingleton(recommenderClassName); - } - } - - private RecommenderSingleton(String recommenderClassName) { - if (recommenderClassName == null) { - throw new IllegalArgumentException("Recommender class name is null"); - } - recommender = ClassUtils.instantiateAs(recommenderClassName, Recommender.class); - } - - public Recommender getRecommender() { - return recommender; - } - -} http://git-wip-us.apache.org/repos/asf/mahout/blob/99a5358f/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderWrapper.java ---------------------------------------------------------------------- diff --git a/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderWrapper.java b/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderWrapper.java deleted file mode 100644 index e927098..0000000 --- a/integration/src/main/java/org/apache/mahout/cf/taste/web/RecommenderWrapper.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.web; - -import com.google.common.io.Files; -import com.google.common.io.InputSupplier; -import com.google.common.io.Resources; -import org.apache.mahout.cf.taste.common.Refreshable; -import org.apache.mahout.cf.taste.common.TasteException; -import org.apache.mahout.cf.taste.model.DataModel; -import org.apache.mahout.cf.taste.recommender.IDRescorer; -import org.apache.mahout.cf.taste.recommender.RecommendedItem; -import org.apache.mahout.cf.taste.recommender.Recommender; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Collection; -import java.util.List; - -/** - * Users of the packaging and deployment mechanism in this module need - * to produce a {@link Recommender} implementation with a no-arg constructor, - * which will internally build the desired {@link Recommender} and delegate - * to it. This wrapper simplifies that process. Simply extend this class and - * implement {@link #buildRecommender()}. - */ -public abstract class RecommenderWrapper implements Recommender { - - private static final Logger log = LoggerFactory.getLogger(RecommenderWrapper.class); - - private final Recommender delegate; - - protected RecommenderWrapper() throws TasteException, IOException { - this.delegate = buildRecommender(); - } - - /** - * @return the {@link Recommender} which should be used to produce recommendations - * by this wrapper implementation - */ - protected abstract Recommender buildRecommender() throws IOException, TasteException; - - @Override - public List<RecommendedItem> recommend(long userID, int howMany) throws TasteException { - return delegate.recommend(userID, howMany); - } - - @Override - public List<RecommendedItem> recommend(long userID, int howMany, IDRescorer rescorer) throws TasteException { - return delegate.recommend(userID, howMany, rescorer); - } - - @Override - public float estimatePreference(long userID, long itemID) throws TasteException { - return delegate.estimatePreference(userID, itemID); - } - - @Override - public void setPreference(long userID, long itemID, float value) throws TasteException { - delegate.setPreference(userID, itemID, value); - } - - @Override - public void removePreference(long userID, long itemID) throws TasteException { - delegate.removePreference(userID, itemID); - } - - @Override - public DataModel getDataModel() { - return delegate.getDataModel(); - } - - @Override - public void refresh(Collection<Refreshable> alreadyRefreshed) { - delegate.refresh(alreadyRefreshed); - } - - /** - * Reads the given resource into a temporary file. This is intended to be used - * to read data files which are stored as a resource available on the classpath, - * such as in a JAR file. However for convenience the resource name will also - * be interpreted as a relative path to a local file, if no such resource is - * found. This facilitates testing. - * - * @param resourceName name of resource in classpath, or relative path to file - * @return temporary {@link File} with resource data - * @throws IOException if an error occurs while reading or writing data - */ - public static File readResourceToTempFile(String resourceName) throws IOException { - String absoluteResource = resourceName.startsWith("/") ? resourceName : '/' + resourceName; - log.info("Loading resource {}", absoluteResource); - InputSupplier<? extends InputStream> inSupplier; - try { - URL resourceURL = Resources.getResource(RecommenderWrapper.class, absoluteResource); - inSupplier = Resources.newInputStreamSupplier(resourceURL); - } catch (IllegalArgumentException iae) { - File resourceFile = new File(resourceName); - log.info("Falling back to load file {}", resourceFile.getAbsolutePath()); - inSupplier = Files.newInputStreamSupplier(resourceFile); - } - File tempFile = File.createTempFile("taste", null); - tempFile.deleteOnExit(); - Files.copy(inSupplier, tempFile); - return tempFile; - } - -}
