Skip to content

Commit

Permalink
Release Posters 2.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jowerner committed Jul 8, 2024
2 parents 38f8921 + 0622b92 commit e08357b
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 36 deletions.
12 changes: 11 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.xceptance</groupId>
<artifactId>posters-demo-store</artifactId>
<version>2.3.0</version>
<version>2.4.0</version>
<packaging>jar</packaging>

<name>Posters Demo Store</name>
Expand Down Expand Up @@ -265,6 +265,16 @@
<artifactId>jaxb-impl</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>9.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analysis-common</artifactId>
<version>9.1.0</version>
</dependency>

<!-- Test Dependencies -->
<dependency>
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/conf/Module.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.google.inject.Inject;

import controllers.JobController;
import models.LuceneSearch;
import models.SearchEngine;

/**
* Ninja uses Guice as injection tool. Define your bindings in this class, which you want to use via @{@link Inject} in
Expand All @@ -44,5 +46,7 @@ protected void configure()
bind(JobController.class);
// bind scheduler class
bind(Scheduler.class);
// bind search engine
bind(SearchEngine.class).to(LuceneSearch.class).asEagerSingleton();
}
}
2 changes: 1 addition & 1 deletion src/main/java/conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ ebean.datasource.heartbeatsql = select 1

application.name = demo poster store

application.version = 2.3.0
application.version = 2.4.0

application.cookie.prefix = NINJA

Expand Down
9 changes: 0 additions & 9 deletions src/main/java/conf/default-create.sql
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,6 @@ create table topcategory (
create index ix_billingaddress_customer_id on billingaddress (customer_id);
alter table billingaddress add constraint fk_billingaddress_customer_id foreign key (customer_id) references customer (id) on delete restrict on update restrict;

create index ix_cart_shipping_address_id on cart (shipping_address_id);
alter table cart add constraint fk_cart_shipping_address_id foreign key (shipping_address_id) references shippingaddress (id) on delete restrict on update restrict;

create index ix_cart_billing_address_id on cart (billing_address_id);
alter table cart add constraint fk_cart_billing_address_id foreign key (billing_address_id) references billingaddress (id) on delete restrict on update restrict;

create index ix_cart_credit_card_id on cart (credit_card_id);
alter table cart add constraint fk_cart_credit_card_id foreign key (credit_card_id) references creditcard (id) on delete restrict on update restrict;

create index ix_cartproduct_product_id on cartproduct (product_id);
alter table cartproduct add constraint fk_cartproduct_product_id foreign key (product_id) references product (id) on delete restrict on update restrict;

Expand Down
2 changes: 0 additions & 2 deletions src/main/java/conf/default-drop.sql
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
alter table billingaddress drop constraint if exists fk_billingaddress_customer_id;
drop index if exists ix_billingaddress_customer_id;

alter table cart drop constraint if exists fk_cart_customer_id;

alter table cart drop constraint if exists fk_cart_shipping_address_id;
drop index if exists ix_cart_shipping_address_id;

Expand Down
16 changes: 15 additions & 1 deletion src/main/java/controllers/CatalogController.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,22 @@ public Result productDetail(@Param("productId") final int productId, final Conte
{
final Map<String, Object> data = new HashMap<String, Object>();
WebShopController.setCommonData(data, context, xcpConf);

//Use the ID to acquire corresponding product
Product obtainedProduct = Product.getProductById(productId);

// Check if productId acquired a product
if (obtainedProduct !=null)
{
//Logger.warn("Product with ID {} not found", productId);
data.put("productDetail", Product.getProductById(productId));
}
else
{
// Handle missing productId
return Results.redirect(xcpConf.NOT_FOUND_404);
}
// put product to data map
data.put("productDetail", Product.getProductById(productId));
return Results.html().render(data);
}

Expand Down
33 changes: 16 additions & 17 deletions src/main/java/controllers/SearchController.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import conf.PosterConstants;
import filters.SessionCustomerExistFilter;
import models.Product;
import models.SearchEngine;
import models.TopCategory;
import ninja.Context;
import ninja.FilterWith;
Expand All @@ -53,6 +54,9 @@ public class SearchController
@Inject
PosterConstants xcpConf;

@Inject
SearchEngine searcher;

private final Optional<String> language = Optional.of("en");

/**
Expand Down Expand Up @@ -131,24 +135,18 @@ public Result getProductOfSearch(@Param("searchText") final String searchText, @
* @return A list of products that match the search text.
*/
private List<Product> searchForProducts(final String searchText, final int pageNumber, final Map<String, Object> data) {
// Divide search text by spaces
final String[] searchTerms = searchText.split(" ");

// Create the query
final Query<Product> query = DB.find(Product.class);

// Add search conditions for each term
for (String term : searchTerms) {
String likePattern = "%" + term.toLowerCase() + "%";
query.where()
.or()
.ilike("descriptionDetail", likePattern)
.ilike("name", likePattern)
.endOr();
// Search products with search engine, second param is the limit for returned results
List<Integer> resultIds = searcher.search(searchText, 20);

if (resultIds.isEmpty()) {
return List.of();
}

// Log the generated query
System.out.println("Generated query: " + query.getGeneratedSql());
else{
// Create the query
final Query<Product> query = DB.find(Product.class);

// Add search conditions
query.where().idIn(resultIds);

final int pageSize = xcpConf.PRODUCTS_PER_PAGE;

Expand Down Expand Up @@ -186,6 +184,7 @@ private List<Product> searchForProducts(final String searchText, final int pageN
data.put("currentPage", pageNumber);

return products;
}
}

@FilterWith(SessionCustomerExistFilter.class)
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/models/Cart.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,22 @@ public class Cart
/**
* The {@link ShippingAddress} of the order.
*/
@ManyToOne
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
@DbForeignKey(noConstraint = true)
private ShippingAddress shippingAddress;

/**
* The {@link BillingAddress} of the order.
*/
@ManyToOne
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
@DbForeignKey(noConstraint = true)
private BillingAddress billingAddress;

/**
* The {@link CreditCard}, the order is paid with.
*/
@ManyToOne
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
@DbForeignKey(noConstraint = true)
private CreditCard creditCard;

/**
Expand Down
119 changes: 119 additions & 0 deletions src/main/java/models/LuceneSearch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package models;

import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.QueryBuilder;

import io.ebean.Ebean;
import ninja.lifecycle.Start;
import util.standalone.StemmingAnalyzer;

public class LuceneSearch implements SearchEngine {
// setup analyzer
Analyzer prodAna;
// setup directory - for now in memory, alternative Option might be desirable
final Directory prodIndex = new ByteBuffersDirectory();

// standard constructor that sets up used analyzer as a standard analyzer
public LuceneSearch() {
prodAna = new StemmingAnalyzer();
}

@Start(order = 100)
public void firstIndexing() {
setup();
}

@Override
public void setup() {
indexData();
}

@Override
public List<Integer> search(String searchText, int maxNumberOfHits) {
List<Integer> results = new ArrayList<Integer>();
// setup query builder
QueryBuilder builder = new QueryBuilder(prodAna);
// setup queries
BooleanQuery.Builder fullQuery = new BooleanQuery.Builder();
// check names
Query queryN = builder.createBooleanQuery("name", searchText, BooleanClause.Occur.MUST);
fullQuery.add(queryN, BooleanClause.Occur.SHOULD);
// check short description
Query querySD = builder.createBooleanQuery("overview", searchText, BooleanClause.Occur.MUST);
fullQuery.add(querySD, BooleanClause.Occur.SHOULD);
// check long description
Query queryLD = builder.createBooleanQuery("description", searchText, BooleanClause.Occur.MUST);
fullQuery.add(queryLD, BooleanClause.Occur.SHOULD);
// setup index reader and searcher and perform search
try {
// setup
IndexReader reader = DirectoryReader.open(prodIndex);
IndexSearcher searcher = new IndexSearcher(reader);
// search
TopDocs topDocs = searcher.search(fullQuery.build(), maxNumberOfHits);
ScoreDoc[] hits = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : hits) {
results.add(Integer.parseInt(searcher.doc(scoreDoc.doc).get("id")));
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}

return results;
}

// currently indexes only products
private void indexData() {

// setup index writer + config
IndexWriterConfig config = new IndexWriterConfig(prodAna);
try {
IndexWriter wri = new IndexWriter(prodIndex, config);
// loop through all products to add to the index
List<Product> products = getAllProducts();
for (Product product : products) {
// create a 'document' (= an indexing target)
Document prodDoc = new Document();
// Setup the fields in that document, we store the id so we can use it to retrieve search results
StoredField prodId = new StoredField("id", product.getId());
TextField prodName = new TextField("name", product.getName(), Store.NO);
TextField prodShortDesc = new TextField("overview", product.getDescriptionOverview(), Store.NO);
TextField prodLongDesc = new TextField("description", product.getDescriptionDetail(), Store.NO);
prodDoc.add(prodId);
prodDoc.add(prodName);
prodDoc.add(prodShortDesc);
prodDoc.add(prodLongDesc);
// add the document with the products information to the index writer
wri.addDocument(prodDoc);
}
wri.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}

private List<Product> getAllProducts() {
return Ebean.find(Product.class).findList();
}
}
4 changes: 2 additions & 2 deletions src/main/java/models/Order.java
Original file line number Diff line number Diff line change
Expand Up @@ -688,8 +688,8 @@ public static void deleteOldPendingOrders() {

// Get all orders that are pending and have creation time more than one day ago from the database irrespective of customer.
// Delete those orders.
Ebean.delete(Ebean.find(Order.class)
.where().eq("orderStatus", "Pending").lt("lastUpdate", oneDayAgo).findList());
Ebean.find(Order.class)
.where().eq("orderStatus", "Pending").lt("lastUpdate", oneDayAgo).delete();
}

}
8 changes: 8 additions & 0 deletions src/main/java/models/SearchEngine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package models;

import java.util.List;

public interface SearchEngine {
void setup();
List<Integer> search(String searchText, int maxNumberOfHits);
}
14 changes: 14 additions & 0 deletions src/main/java/util/standalone/StemmingAnalyzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package util.standalone;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.LowerCaseFilter;
import org.apache.lucene.analysis.en.PorterStemFilter;
import org.apache.lucene.analysis.standard.StandardTokenizer;
import org.apache.lucene.analysis.Tokenizer;

public class StemmingAnalyzer extends Analyzer {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer source = new StandardTokenizer();
return new TokenStreamComponents(source, new PorterStemFilter(new LowerCaseFilter(source)));
}
}

0 comments on commit e08357b

Please sign in to comment.