├── License.md ├── README.md ├── pom.xml └── src ├── main └── java │ └── ch │ └── sebastianmue │ └── javarank │ └── recommendation │ ├── data │ ├── InputRating.java │ └── RDDHelper.java │ ├── exceptions │ ├── ErrorInDataSourceException.java │ └── ModelNotReadyException.java │ ├── model │ └── RecommendationMlModel.java │ └── service │ └── RecommendationService.java └── test └── java └── ch └── sebastianmue └── javarank ├── data └── RDDHelperTest.java └── service ├── RecommendationServiceModelNotReadyTest.java ├── RecommendationServiceRetrainTest.java └── RecommendationServiceTest.java /License.md: -------------------------------------------------------------------------------- 1 | Copyright 2018 SebastianMue 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaRank 2 | > Recommendation engine in Java. Based on an ALS algorithm (Apache Spark). Including an automatisation of training a new model after N seconds. And an easier interface. 3 | 4 | ## Installation 5 | 6 | Prerelease stage, you have to get your own build to use the dependency. 7 | 8 | ### Requirements 9 | - maven 10 | 11 | Check out the project with your IDE. 12 | Most ide will automatically download your maven dependencies. 13 | After this, the project will be ready to be released/modified. 14 | 15 | 16 | ## Usage example 17 | The following example shows, how to init the ch.javarank.service. After training the model will give prediction, which rating the user will likely give for the product. 18 | ```java 19 | RecommendationService recommendationService = new RecommendationService(() -> dataProvider(), timeBetweenNewModels, initialDelay); 20 | ``` 21 | dataProvider() is a Methode, which returns a Collection. 22 | timeBetweenNewModels is the time in seconds between the renewal of the model 23 | initialDelay is the time in seconds for the first delay 24 | 25 | As soon as the model is ready (see recommendationService.isModelReady()) you can get the prediction like this 26 | ```java 27 | Optional prediction = recommendationService.getPrediction(2, 3); 28 | ``` 29 | 30 | ## Release History 31 | 32 | * Prerelease 33 | * Working prototype 34 | 35 | ## Meta 36 | 37 | SebastianMue 38 | 39 | Distributed under the MIT license. See ``LICENSE`` for more information. 40 | 41 | [https://github.com/SebastianMue] 42 | 43 | ## Contributing 44 | 45 | 1. Fork it () 46 | 2. Create your feature branch (`git checkout -b feature/fooBar`) 47 | 3. Commit your changes (`git commit -am 'Add some fooBar'`) 48 | 4. Push to the branch (`git push origin feature/fooBar`) 49 | 5. Create a new Pull Request 50 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | ch.mue.seb 8 | javarank 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 1.2.61 13 | 14 | 15 | 16 | 17 | 18 | 19 | org.jetbrains.kotlin 20 | kotlin-maven-plugin 21 | ${kotlin.version} 22 | 23 | 24 | compile 25 | compile 26 | 27 | compile 28 | 29 | 30 | 31 | test-compile 32 | test-compile 33 | 34 | test-compile 35 | 36 | 37 | 38 | 39 | 1.11 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-compiler-plugin 45 | 46 | 47 | compile 48 | compile 49 | 50 | compile 51 | 52 | 53 | 54 | testCompile 55 | test-compile 56 | 57 | testCompile 58 | 59 | 60 | 61 | 62 | 11 63 | 11 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.apache.spark 71 | spark-core_2.12 72 | 2.4.3 73 | 74 | 75 | org.apache.spark 76 | spark-sql_2.12 77 | 2.4.3 78 | 79 | 80 | org.apache.spark 81 | spark-mllib_2.12 82 | 2.4.3 83 | 84 | 85 | junit 86 | junit 87 | 4.13.1 88 | test 89 | 90 | 91 | org.hamcrest 92 | hamcrest-all 93 | 1.3 94 | test 95 | 96 | 97 | org.jetbrains.kotlin 98 | kotlin-stdlib-jdk8 99 | ${kotlin.version} 100 | 101 | 102 | org.jetbrains.kotlin 103 | kotlin-test-junit 104 | ${kotlin.version} 105 | test 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/main/java/ch/sebastianmue/javarank/recommendation/data/InputRating.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.recommendation.data; 2 | 3 | public class InputRating { 4 | private int userId; 5 | private int productId; 6 | private double rating; 7 | 8 | public InputRating(int userId, int productId, double rating) { 9 | this.userId = userId; 10 | this.productId = productId; 11 | this.rating = rating; 12 | } 13 | 14 | public int getUserId() { 15 | return userId; 16 | } 17 | 18 | public void setUserId(int userId) { 19 | this.userId = userId; 20 | } 21 | 22 | public int getProductId() { 23 | return productId; 24 | } 25 | 26 | public void setProductId(int productId) { 27 | this.productId = productId; 28 | } 29 | 30 | public double getRating() { 31 | return rating; 32 | } 33 | 34 | public void setRating(double rating) { 35 | this.rating = rating; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/ch/sebastianmue/javarank/recommendation/data/RDDHelper.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.recommendation.data; 2 | 3 | import org.apache.spark.api.java.JavaRDD; 4 | import org.apache.spark.api.java.JavaSparkContext; 5 | 6 | import java.util.Collection; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | 10 | /** 11 | * Used to create create JavaRDDs from collections 12 | */ 13 | public class RDDHelper { 14 | 15 | private static JavaSparkContext jsc; 16 | 17 | public RDDHelper(JavaSparkContext javaSparkContext) { 18 | jsc = javaSparkContext; 19 | } 20 | 21 | /** 22 | * Converts a collection to a JavaRDD 23 | * 24 | * @param collection 25 | * @return The JavaRDD from the same generic type as the provided input 26 | */ 27 | public JavaRDD getRddFromCollection(Collection collection) { 28 | return jsc.parallelize(collection.stream().parallel().collect(Collectors.toList())).cache(); 29 | } 30 | 31 | /** 32 | * Converts a collection(mapped the values before) to a JavaRDD 33 | * 34 | * @param collection 35 | * @param mapper ( i.e. integer -> integer.toString() if you want to get a String RDD from a Integer list) 36 | * @return The JavaRDD from the same generic type as the provided input 37 | */ 38 | public JavaRDD getRddFromCollection(Collection collection, Function mapper) { 39 | return getRddFromCollection(collection.stream().parallel().map(mapper).collect(Collectors.toList())); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/ch/sebastianmue/javarank/recommendation/exceptions/ErrorInDataSourceException.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.recommendation.exceptions; 2 | 3 | /** 4 | * General exception, which is thrown, when there was a problem with the model training. 5 | * There is likely to be a problem with the provided ch.javarank.data 6 | */ 7 | public class ErrorInDataSourceException extends RuntimeException { 8 | public ErrorInDataSourceException(Exception cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/ch/sebastianmue/javarank/recommendation/exceptions/ModelNotReadyException.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.recommendation.exceptions; 2 | 3 | /** 4 | * Exception, which is thrown, when the application want to request a prediction, but the model is not ready 5 | */ 6 | public class ModelNotReadyException extends Exception { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/ch/sebastianmue/javarank/recommendation/model/RecommendationMlModel.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.recommendation.model; 2 | 3 | import ch.sebastianmue.javarank.recommendation.data.InputRating; 4 | import ch.sebastianmue.javarank.recommendation.data.RDDHelper; 5 | import ch.sebastianmue.javarank.recommendation.exceptions.ErrorInDataSourceException; 6 | import ch.sebastianmue.javarank.recommendation.exceptions.ModelNotReadyException; 7 | import org.apache.spark.api.java.JavaRDD; 8 | import org.apache.spark.api.java.JavaSparkContext; 9 | import org.apache.spark.mllib.recommendation.ALS; 10 | import org.apache.spark.mllib.recommendation.MatrixFactorizationModel; 11 | import org.apache.spark.mllib.recommendation.Rating; 12 | 13 | import java.util.Collection; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.concurrent.Callable; 17 | import java.util.concurrent.Executors; 18 | import java.util.concurrent.ScheduledExecutorService; 19 | import java.util.concurrent.TimeUnit; 20 | import java.util.concurrent.atomic.AtomicInteger; 21 | import java.util.concurrent.locks.ReentrantLock; 22 | import java.util.concurrent.locks.ReentrantReadWriteLock; 23 | import java.util.stream.Collectors; 24 | 25 | /** 26 | * Model Class which provides the predictions 27 | */ 28 | public class RecommendationMlModel { 29 | 30 | private static final String SPARK_APP_NAME = "Recommendation Engine"; 31 | private static final String SPARK_MASTER = "local"; 32 | private final ReentrantLock trainingLock = new ReentrantLock(); 33 | private final ReentrantReadWriteLock mutex = new ReentrantReadWriteLock(); 34 | private final JavaSparkContext javaSparkContext = new JavaSparkContext(SPARK_MASTER, SPARK_APP_NAME); 35 | private final ALS als = new ALS(); 36 | private final RDDHelper rddHelper = new RDDHelper(javaSparkContext); 37 | private MatrixFactorizationModel model; 38 | 39 | private volatile AtomicInteger modelNumber = new AtomicInteger(0); 40 | private volatile boolean modelIsReady = false; 41 | 42 | /** 43 | * Constructor, which allows to retrain and replace the model. 44 | * The given Callable will be used to get the ch.javarank.data for the model. 45 | * 46 | * @param inputRatings callable to get the Input ratings for the Model 47 | * @param retrainTime Time to wait before training a new model 48 | * @param initialDelay Time to wait before training the first model 49 | */ 50 | public RecommendationMlModel(Callable> inputRatings, long retrainTime, long initialDelay) { 51 | ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 52 | executor.scheduleAtFixedRate(() -> asyncTrainModel(inputRatings), initialDelay, retrainTime, TimeUnit.SECONDS); 53 | } 54 | 55 | /** 56 | * Constructor, which trains the model once. provide a callable, if you want your model to improve periodically. 57 | * 58 | * @param inputRatings 59 | */ 60 | public RecommendationMlModel(Collection inputRatings) { 61 | asyncTrainModel(inputRatings); 62 | } 63 | 64 | /** 65 | * @return true if a model is ready. Just can be false, if the module was new init and the first model is not ready yet. 66 | */ 67 | public boolean isModelReady() { 68 | return modelIsReady; 69 | } 70 | 71 | /** 72 | * Provides a prediction for the given parameters 73 | * 74 | * @param userId 75 | * @param eventId 76 | * @return the prediction, which rating the used is likely to give. If either the user or the product is unknown, it will return empty 77 | * @throws ModelNotReadyException 78 | */ 79 | public Optional getInterestPrediction(Integer userId, Integer eventId) throws ModelNotReadyException { 80 | if (!modelIsReady) 81 | throw new ModelNotReadyException(); 82 | mutex.readLock().lock(); 83 | Optional prediction; 84 | try { 85 | prediction = Optional.of(model.predict(userId, eventId)); 86 | } catch (IllegalArgumentException e) { 87 | prediction = Optional.empty(); 88 | } finally { 89 | mutex.readLock().unlock(); 90 | } 91 | return prediction; 92 | } 93 | 94 | /** 95 | * Close the sparkContext and set the resources free 96 | */ 97 | public void close() { 98 | javaSparkContext.close(); 99 | } 100 | 101 | private void asyncTrainModel(Callable> inputRatings) { 102 | try { 103 | asyncTrainModel(inputRatings.call()); 104 | } catch (Exception e) { 105 | throw new ErrorInDataSourceException(e); 106 | } 107 | } 108 | 109 | 110 | private void asyncTrainModel(Collection inputRatings) { 111 | Thread thread = new Thread(() -> { 112 | if (trainingLock.isLocked()) 113 | return; 114 | trainingLock.lock(); 115 | trainModel(inputRatings); 116 | trainingLock.unlock(); 117 | }); 118 | thread.start(); 119 | } 120 | 121 | private void trainModel(Collection ratings) { 122 | JavaRDD ratingRDD = rddHelper.getRddFromCollection(createSparkRating(ratings)).cache(); 123 | if (ratingRDD.isEmpty()) 124 | return; 125 | mutex.writeLock().lock(); 126 | model = als.setRank(10).setIterations(10).run(ratingRDD); 127 | mutex.writeLock().unlock(); 128 | modelNumber.incrementAndGet(); 129 | modelIsReady = true; 130 | 131 | } 132 | 133 | 134 | private List createSparkRating(Collection inputRatings) { 135 | return inputRatings 136 | .stream() 137 | .map(ir -> new Rating(ir.getUserId(), ir.getProductId(), ir.getRating())) 138 | .collect(Collectors.toList()); 139 | } 140 | 141 | public Integer getModelNumber() { 142 | return modelNumber.get(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/ch/sebastianmue/javarank/recommendation/service/RecommendationService.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.recommendation.service; 2 | 3 | import ch.sebastianmue.javarank.recommendation.data.InputRating; 4 | import ch.sebastianmue.javarank.recommendation.exceptions.ModelNotReadyException; 5 | import ch.sebastianmue.javarank.recommendation.model.RecommendationMlModel; 6 | 7 | import java.util.Collection; 8 | import java.util.Optional; 9 | import java.util.concurrent.Callable; 10 | 11 | public class RecommendationService { 12 | 13 | private final RecommendationMlModel recommendationMlModel; 14 | 15 | public RecommendationService(Collection ratings) { 16 | recommendationMlModel = new RecommendationMlModel(ratings); 17 | } 18 | 19 | public RecommendationService(Callable> ratings, long retrainTime, long initialDelay) { 20 | recommendationMlModel = new RecommendationMlModel(ratings, retrainTime, initialDelay); 21 | } 22 | 23 | public boolean isModelReady() { 24 | return recommendationMlModel.isModelReady(); 25 | } 26 | 27 | public Optional getPrediction(Integer userId, Integer productId) throws ModelNotReadyException { 28 | return recommendationMlModel.getInterestPrediction(userId, productId); 29 | } 30 | 31 | public void close() { 32 | recommendationMlModel.close(); 33 | } 34 | 35 | public Integer getModelNumber() { 36 | return recommendationMlModel.getModelNumber(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/ch/sebastianmue/javarank/data/RDDHelperTest.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.data; 2 | 3 | import ch.sebastianmue.javarank.recommendation.data.RDDHelper; 4 | import org.apache.commons.collections.CollectionUtils; 5 | import org.apache.spark.api.java.JavaRDD; 6 | import org.apache.spark.api.java.JavaSparkContext; 7 | import org.junit.AfterClass; 8 | import org.junit.Test; 9 | 10 | import java.util.Arrays; 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Set; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.IntStream; 16 | 17 | import static org.hamcrest.MatcherAssert.assertThat; 18 | import static org.hamcrest.core.IsInstanceOf.instanceOf; 19 | import static org.junit.Assert.assertTrue; 20 | 21 | public class RDDHelperTest { 22 | 23 | private static final JavaSparkContext jsc = new JavaSparkContext("local", "testjsc"); 24 | private final RDDHelper rddHelper = new RDDHelper(jsc); 25 | 26 | @AfterClass 27 | public static void close() { 28 | jsc.close(); 29 | } 30 | 31 | @Test 32 | public void shouldCreateRDDFromCollection() { 33 | List intList = IntStream.range(0, 100).boxed().collect(Collectors.toList()); 34 | JavaRDD intRDD = rddHelper.getRddFromCollection(intList); 35 | assertTrue(CollectionUtils.isEqualCollection(intRDD.collect(), intList)); 36 | assertThat(intRDD, instanceOf(JavaRDD.class)); 37 | } 38 | 39 | @Test 40 | public void shouldCreateRDDFromListWithConverter() { 41 | List intList = IntStream.range(0, 3).boxed().collect(Collectors.toList()); 42 | JavaRDD stringRDD = rddHelper.getRddFromCollection(intList, Object::toString); 43 | assertThat(stringRDD, instanceOf(JavaRDD.class)); 44 | assertTrue(CollectionUtils.isEqualCollection(stringRDD.collect(), Arrays.asList("0", "1", "2"))); 45 | } 46 | 47 | @Test 48 | public void shouldCreateRDDFromSetWithConverter() { 49 | Set intList = IntStream.range(0, 3).boxed().collect(Collectors.toSet()); 50 | JavaRDD stringRDD = rddHelper.getRddFromCollection(intList, Object::toString); 51 | assertThat(stringRDD, instanceOf(JavaRDD.class)); 52 | assertTrue(CollectionUtils.isEqualCollection(stringRDD.collect(), Arrays.asList("0", "1", "2"))); 53 | } 54 | 55 | @Test 56 | public void shouldCreateRDDFromSetWithEmptySet() { 57 | Set intList = new HashSet<>(); 58 | JavaRDD stringRDD = rddHelper.getRddFromCollection(intList, Object::toString); 59 | assertThat(stringRDD, instanceOf(JavaRDD.class)); 60 | assertTrue(stringRDD.isEmpty()); 61 | } 62 | 63 | @Test(expected = NullPointerException.class) 64 | public void shouldThrowNullPointerOnNull() { 65 | List intList = null; 66 | JavaRDD stringRDD = rddHelper.getRddFromCollection(intList, Object::toString); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/ch/sebastianmue/javarank/service/RecommendationServiceModelNotReadyTest.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.service; 2 | 3 | import ch.sebastianmue.javarank.recommendation.data.InputRating; 4 | import ch.sebastianmue.javarank.recommendation.exceptions.ModelNotReadyException; 5 | import ch.sebastianmue.javarank.recommendation.service.RecommendationService; 6 | import org.junit.After; 7 | import org.junit.AfterClass; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.util.ArrayList; 12 | 13 | public class RecommendationServiceModelNotReadyTest { 14 | 15 | 16 | private static RecommendationService recommendationService; 17 | 18 | @Before 19 | public void initModel() { 20 | ArrayList inputRatings = new ArrayList<>(); 21 | inputRatings.add(new InputRating(1, 1, 1)); 22 | recommendationService = new RecommendationService(inputRatings); 23 | } 24 | 25 | @After 26 | public void destroyModel() { 27 | recommendationService.close(); 28 | } 29 | 30 | 31 | @AfterClass 32 | public static void close() { 33 | recommendationService.close(); 34 | } 35 | 36 | @Test(expected = ModelNotReadyException.class) 37 | public void shouldNotForgetKnownRating() throws ModelNotReadyException { 38 | recommendationService.getPrediction(1, 1); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/ch/sebastianmue/javarank/service/RecommendationServiceRetrainTest.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.service; 2 | 3 | import ch.sebastianmue.javarank.recommendation.data.InputRating; 4 | import ch.sebastianmue.javarank.recommendation.exceptions.ModelNotReadyException; 5 | import ch.sebastianmue.javarank.recommendation.service.RecommendationService; 6 | import org.junit.AfterClass; 7 | import org.junit.BeforeClass; 8 | import org.junit.Test; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Optional; 12 | 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | import static org.hamcrest.Matchers.lessThan; 15 | import static org.junit.Assert.assertNotEquals; 16 | 17 | public class RecommendationServiceRetrainTest { 18 | 19 | 20 | private static RecommendationService recommendationService; 21 | 22 | private static boolean firstCall = true; 23 | 24 | private static ArrayList dataProvider() { 25 | ArrayList inputRatings = new ArrayList<>(); 26 | inputRatings.add(new InputRating(1, 1, 1)); 27 | inputRatings.add(new InputRating(1, 2, 0)); 28 | inputRatings.add(new InputRating(1, 3, 2)); 29 | inputRatings.add(new InputRating(2, 1, 1)); 30 | if (firstCall) { 31 | inputRatings.add(new InputRating(1, 3, 0)); 32 | firstCall = false; 33 | } 34 | return inputRatings; 35 | } 36 | 37 | @BeforeClass 38 | public static void initModel() { 39 | recommendationService = new RecommendationService(RecommendationServiceRetrainTest::dataProvider, 10, 0); 40 | while (!recommendationService.isModelReady()) { 41 | } 42 | } 43 | 44 | @AfterClass 45 | public static void close() { 46 | recommendationService.close(); 47 | } 48 | 49 | @Test 50 | public void shouldDeliverANewPredictionAfterTraining() throws ModelNotReadyException { 51 | Optional firstPrediction = recommendationService.getPrediction(2, 3); 52 | while (recommendationService.getModelNumber() == 1) { 53 | } 54 | Optional secondPrediction = recommendationService.getPrediction(2, 3); 55 | assertNotEquals(firstPrediction.get(), secondPrediction.get()); 56 | assertThat(firstPrediction.get(), lessThan(secondPrediction.get())); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/ch/sebastianmue/javarank/service/RecommendationServiceTest.java: -------------------------------------------------------------------------------- 1 | package ch.sebastianmue.javarank.service; 2 | 3 | import ch.sebastianmue.javarank.recommendation.data.InputRating; 4 | import ch.sebastianmue.javarank.recommendation.exceptions.ModelNotReadyException; 5 | import ch.sebastianmue.javarank.recommendation.service.RecommendationService; 6 | import org.junit.BeforeClass; 7 | import org.junit.Test; 8 | 9 | import java.util.ArrayList; 10 | 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.Matchers.lessThan; 13 | import static org.junit.Assert.assertNotEquals; 14 | import static org.junit.Assert.assertTrue; 15 | 16 | /** 17 | * The testing was keepn simple, as sparkml is already tested. The testing focus is on the new features. 18 | * This test just verifies, that sparkml was correctly called 19 | */ 20 | public class RecommendationServiceTest { 21 | 22 | 23 | private static RecommendationService recommendationService; 24 | 25 | @BeforeClass 26 | public static void initModel() { 27 | ArrayList inputRatings = new ArrayList<>(); 28 | inputRatings.add(new InputRating(1, 1, 1)); 29 | inputRatings.add(new InputRating(1, 2, 0)); 30 | inputRatings.add(new InputRating(1, 3, 2)); 31 | 32 | inputRatings.add(new InputRating(2, 1, 1)); 33 | recommendationService = new RecommendationService(inputRatings); 34 | while (!recommendationService.isModelReady()) { 35 | } 36 | } 37 | 38 | @Test 39 | public void shouldNotForgetKnownRating() throws ModelNotReadyException { 40 | assertThat(recommendationService.getPrediction(1, 2).get(), lessThan(recommendationService.getPrediction(1, 1).get())); 41 | assertNotEquals(0.0, recommendationService.getPrediction(1, 1)); 42 | } 43 | 44 | @Test 45 | public void shouldPredictFromOtherUser() throws ModelNotReadyException { 46 | assertThat(recommendationService.getPrediction(2, 2).get(), lessThan(recommendationService.getPrediction(1, 3).get())); 47 | } 48 | 49 | @Test 50 | public void shouldHandleUnknownUser() throws ModelNotReadyException { 51 | assertTrue(recommendationService.getPrediction(3, 2).isEmpty()); 52 | } 53 | 54 | @Test 55 | public void shouldHandleUnknownProduct() throws ModelNotReadyException { 56 | assertTrue(recommendationService.getPrediction(2, 4).isEmpty()); 57 | } 58 | 59 | } 60 | --------------------------------------------------------------------------------