/**
* Copyright 2012 plista GmbH (http://www.plista.com/)
*
* 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 org.plista.kornakapi.web.servlets;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
import org.apache.mahout.cf.taste.impl.recommender.svd.Factorization;
import org.apache.mahout.cf.taste.impl.recommender.svd.FilePersistenceStrategy;
import org.apache.mahout.cf.taste.impl.recommender.svd.PersistenceStrategy;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.recommender.CandidateItemsStrategy;
import org.plista.kornakapi.KornakapiRecommender;
import org.plista.kornakapi.core.recommender.CachingAllUnknownItemsCandidateItemsStrategy;
import org.plista.kornakapi.core.recommender.FoldingFactorizationBasedRecommender;
import org.plista.kornakapi.core.storage.CandidateCacheStorageDecorator;
import org.plista.kornakapi.core.storage.MySqlMaxPersistentStorage;
import org.plista.kornakapi.core.storage.MySqlStorage;
import org.plista.kornakapi.core.training.FactorizationbasedInMemoryTrainer;
import org.plista.kornakapi.core.training.Trainer;
import org.plista.kornakapi.core.training.TrainingScheduler;
import org.plista.kornakapi.core.training.preferencechanges.DelegatingPreferenceChangeListenerForLabel;
import org.plista.kornakapi.core.training.preferencechanges.InMemoryPreferenceChangeListener;
import org.plista.kornakapi.web.Components;
import org.plista.kornakapi.core.config.Configuration;
import org.plista.kornakapi.core.config.FactorizationbasedRecommenderConfig;
import org.plista.kornakapi.core.config.RecommenderConfig;
import org.plista.kornakapi.web.InvalidParameterException;
import org.plista.kornakapi.web.MissingParameterException;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;
/** base class for all servlets */
public abstract class BaseServlet extends HttpServlet {
private static final Pattern ITEM_ID_SEPARATOR = Pattern.compile(",");
private static final Logger log = LoggerFactory.getLogger(TrainServlet.class);
private Components getComponents() {
return Components.instance();
}
protected Configuration getConfiguration(){
return getComponents().getConfiguration();
}
protected void setRecommender(String name, KornakapiRecommender recommender){
getComponents().setRecommender(name, recommender);
}
protected BasicDataSource getDataSource(){
return getComponents().getDataSource();
}
protected KornakapiRecommender recommender(String name) {
return getComponents().recommender(name);
}
protected void setTrainer(String name, Trainer trainer){
getComponents().setTrainer(name, trainer);
}
protected boolean containsTrainer(String name){
return getComponents().trainer(name) != null;
}
protected TrainingScheduler scheduler() {
return getComponents().scheduler();
}
protected HashMap<String, CandidateCacheStorageDecorator> storages() {
return getComponents().storages();
}
protected DelegatingPreferenceChangeListenerForLabel preferenceChangeListener() {
return getComponents().preferenceChangeListener();
}
protected boolean hasParameter(HttpServletRequest request, String name) {
return request.getParameter(name) != null;
}
protected String getParameter(HttpServletRequest request, String name, boolean required) {
String param = request.getParameter(name);
if (param == null && required) {
throw new MissingParameterException("Parameter [" + name + "] must be supplied!");
}
return param;
}
protected long getParameterAsLong(HttpServletRequest request, String name, boolean required) {
String param = getParameter(request, name, required);
try {
return Long.parseLong(param);
} catch (NumberFormatException e) {
throw new InvalidParameterException("Unable to parse parameter [" + name + "]", e);
}
}
protected CandidateCacheStorageDecorator getDomainIndependetStorage(){
return getComponents().getDomainIndependetStorage();
}
protected long[] getParameterAsLongArray(HttpServletRequest request, String name) {
String param = getParameter(request, name, false);
String[] tokens = ITEM_ID_SEPARATOR.split(param);
long[] itemIDs = new long[tokens.length];
for (int n = 0; n < itemIDs.length; n++) {
try {
itemIDs[n] = Long.parseLong(tokens[n]);
} catch (NumberFormatException e) {
throw new InvalidParameterException("Unable to parse parameter [" + name + "]", e);
}
}
return itemIDs;
}
protected float getParameterAsFloat(HttpServletRequest request, String name, boolean required) {
String param = getParameter(request, name, required);
try {
return Float.parseFloat(param);
} catch (NumberFormatException e) {
throw new InvalidParameterException("Unable to parse parameter [" + name + "]", e);
}
}
protected int getParameterAsInt(HttpServletRequest request, String name, int defaultValue) {
String param = getParameter(request, name, false);
try {
return param != null ? Integer.parseInt(param) : defaultValue;
} catch (NumberFormatException e) {
throw new InvalidParameterException("Unable to parse parameter [" + name + "]", e);
}
}
protected void createRecommenderForLabel(String label) throws IOException, TasteException{
Configuration conf = getConfiguration();
List<FactorizationbasedRecommenderConfig> factorizationbasedConfs= conf.getFactorizationbasedRecommenders();
FactorizationbasedRecommenderConfig factorizationbasedConf =factorizationbasedConfs.get(0);
String name = factorizationbasedConf.getName() +"_"+ label;
if(conf.getMaxPersistence()){
storages().put(label, new CandidateCacheStorageDecorator(new MySqlMaxPersistentStorage(conf.getStorageConfiguration(), label,getDataSource())));
}else{
storages().put(label, new CandidateCacheStorageDecorator(new MySqlStorage(conf.getStorageConfiguration(), label,getDataSource())));
}
File modelFile = new File(conf.getModelDirectory(), name + ".model");
PersistenceStrategy persistence = new FilePersistenceStrategy(modelFile);
if (!modelFile.exists()) {
createEmptyFactorization(persistence);
}
DataModel persistenData = storages().get(label).recommenderData();
CandidateItemsStrategy allUnknownItemsStrategy =
new CachingAllUnknownItemsCandidateItemsStrategy(persistenData);
FoldingFactorizationBasedRecommender svdRecommender = new FoldingFactorizationBasedRecommender(persistenData,
allUnknownItemsStrategy, persistence);
setRecommender(name, svdRecommender);
setTrainer(name, new FactorizationbasedInMemoryTrainer(factorizationbasedConf));
String cronExpression = factorizationbasedConf.getRetrainCronExpression();
if (cronExpression == null) {
scheduler().addRecommenderTrainingJob(name);
} else {
scheduler().addRecommenderTrainingJobWithCronSchedule(name, cronExpression);
try {
scheduler().immediatelyTrainRecommender(name);
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (factorizationbasedConf.getRetrainAfterPreferenceChanges() !=
RecommenderConfig.DONT_RETRAIN_ON_PREFERENCE_CHANGES) {
((DelegatingPreferenceChangeListenerForLabel)preferenceChangeListener()).addDelegate(new InMemoryPreferenceChangeListener(scheduler(), name,
factorizationbasedConf.getRetrainAfterPreferenceChanges()),label);
}
log.info("Added FactorizationBasedRecommender [{}] using [{}] features and [{}] iterations for label: {}",
new Object[] { name, factorizationbasedConf.getNumberOfFeatures(),
factorizationbasedConf.getNumberOfIterations() , label});
}
private void createEmptyFactorization(PersistenceStrategy strategy) throws IOException {
strategy.maybePersist(new Factorization(new FastByIDMap<Integer>(0), new FastByIDMap<Integer>(0),
new double[0][0], new double[0][0]));
}
/**
* Method maps ids into int range
* @param id
* @return long
*/
protected long idRemapping(long id){
return Math.abs(id % 2147483647);
}
}