Compare commits
	
		
			3 Commits
		
	
	
		
			8bcd8cb9a1
			...
			68462514db
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 68462514db | |||
| 61942f547a | |||
| 9cf0f89e76 | 
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
# Gourmet Recipe Manager - Spring Boot
 | 
			
		||||
# Gourmet Recipe Manager - Spring Boot - Version 2
 | 
			
		||||
 | 
			
		||||
This is a port of Thomas Hinkle (thinkle) Gourmet Recipe Manager.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,4 +85,14 @@ employed when you run this app on your local desktop.
 | 
			
		|||
### Improved graphics support
 | 
			
		||||
 | 
			
		||||
A lot of recipe websites publish images in webp form. Support
 | 
			
		||||
for webp has now been added.
 | 
			
		||||
for webp has now been added.
 | 
			
		||||
 | 
			
		||||
### Better session management
 | 
			
		||||
 | 
			
		||||
JSF tends to depend on session-scope context. Sessions, however
 | 
			
		||||
time out and this has been an annoyance when a recipe is being
 | 
			
		||||
displayed. To minimize this, better timeout mechanisms have been
 | 
			
		||||
installed and the recipe browser keeps last-search and search-type
 | 
			
		||||
values in long-lived cookies on the client. The server will read
 | 
			
		||||
and cache them, but if the server times out, it will automatically
 | 
			
		||||
re-read the cookies on the next request.
 | 
			
		||||
| 
						 | 
				
			
			@ -22,3 +22,5 @@ spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
 | 
			
		|||
# My special properties
 | 
			
		||||
gourmet.password.file=${user.home}/.gourmetpw
 | 
			
		||||
 | 
			
		||||
# This will override aplication.yml
 | 
			
		||||
#server.servlet.context-parameters.primefaces.THEME=le-frog
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -78,6 +78,12 @@
 | 
			
		|||
            <artifactId>all-themes</artifactId>
 | 
			
		||||
            <version>1.0.10</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <!--  Primefaces theme won't work without this! -->
 | 
			
		||||
            <groupId>com.google.code.gson</groupId>
 | 
			
		||||
            <artifactId>gson</artifactId>
 | 
			
		||||
            <scope>runtime</scope>
 | 
			
		||||
        </dependency>        
 | 
			
		||||
        <!--        <dependency>
 | 
			
		||||
            <groupId>javax.enterprise</groupId>
 | 
			
		||||
            <artifactId>cdi-api</artifactId>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package com.mousetech.gourmetj;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
 | 
			
		||||
import jakarta.annotation.PostConstruct;
 | 
			
		||||
import jakarta.faces.event.AjaxBehaviorEvent;
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +15,9 @@ import org.slf4j.Logger;
 | 
			
		|||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import com.mousetech.gourmetj.persistence.model.Recipe;
 | 
			
		||||
import com.mousetech.gourmetj.persistence.service.RecipeService;
 | 
			
		||||
| 
						 | 
				
			
			@ -44,8 +47,11 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
	private static final Logger log =
 | 
			
		||||
			LoggerFactory.getLogger(AdminMainBean.class);
 | 
			
		||||
 | 
			
		||||
	/** Cookie delimiter */
 | 
			
		||||
	private static final String CKDLM = ",";
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Persistency service for Recipes
 | 
			
		||||
	 * Persistency service for Recipes.
 | 
			
		||||
	 */
 | 
			
		||||
 | 
			
		||||
	@Inject
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +62,24 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
		this.recipeService = service;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// **
 | 
			
		||||
	@Inject
 | 
			
		||||
	private CookieBean cookieBean;
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the cookieBean
 | 
			
		||||
	 */
 | 
			
		||||
	public CookieBean getCookieBean() {
 | 
			
		||||
		return cookieBean;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param cookieBean the cookieBean to set
 | 
			
		||||
	 */
 | 
			
		||||
	public void setCookieBean(CookieBean cookieBean) {
 | 
			
		||||
		this.cookieBean = cookieBean;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// **
 | 
			
		||||
	@Inject
 | 
			
		||||
	private UserSession userSession;
 | 
			
		||||
| 
						 | 
				
			
			@ -81,10 +105,10 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
	 * @return the searchText
 | 
			
		||||
	 */
 | 
			
		||||
	public String getSearchText() {
 | 
			
		||||
		if (this.searchResults == null) {
 | 
			
		||||
			// Fake around broken @PostConstruct
 | 
			
		||||
			this.setSearchText(userSession.getLastSearch());
 | 
			
		||||
		}
 | 
			
		||||
//		if (this.searchResults == null) {
 | 
			
		||||
//			this.setSearchText(cookieBean.getSearchText());
 | 
			
		||||
//		}
 | 
			
		||||
		this.searchText = cookieBean.getSearchText();
 | 
			
		||||
		return searchText;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +117,7 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
	 */
 | 
			
		||||
	public void setSearchText(String searchText) {
 | 
			
		||||
		this.searchText = searchText;
 | 
			
		||||
		userSession.setLastSearch(searchText);
 | 
			
		||||
		cookieBean.setSearchText(searchText);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<String> suggestionList = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +128,7 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
 | 
			
		||||
	public List<String> searchSuggestionList(String query) {
 | 
			
		||||
		if (suggestionList == null) {
 | 
			
		||||
			switch (this.userSession.getSearchType()) {
 | 
			
		||||
			switch (searchtypeEnum()) {
 | 
			
		||||
			case rst_BY_CATEGORY:
 | 
			
		||||
				suggestionList = recipeService.findCategories();
 | 
			
		||||
				break;
 | 
			
		||||
| 
						 | 
				
			
			@ -118,6 +142,16 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
		return suggestionList;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private RecipeSearchType searchtypeEnum() {
 | 
			
		||||
		int stn = cookieBean.getSearchType();
 | 
			
		||||
		return searchtypeEnum(stn);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private RecipeSearchType searchtypeEnum(int stn) {
 | 
			
		||||
		RecipeSearchType st = RecipeSearchType.values()[stn];
 | 
			
		||||
		return st;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**/
 | 
			
		||||
	transient DataModel<Recipe> searchResults;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +161,7 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
	public DataModel<Recipe> getSearchResults() {
 | 
			
		||||
		if (searchResults == null) {
 | 
			
		||||
			searchResults = new ListDataModel<Recipe>();
 | 
			
		||||
			init(); // @PostConstruct is broken
 | 
			
		||||
			init(); // @PostConstruct is broken TODO: fixed??
 | 
			
		||||
		}
 | 
			
		||||
		return searchResults;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +181,7 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
	@PostConstruct
 | 
			
		||||
	void init() {
 | 
			
		||||
		log.debug("Initializing AdminMainBean " + this);
 | 
			
		||||
		this.setSearchText(userSession.getLastSearch());
 | 
			
		||||
		this.setSearchText(cookieBean.getSearchText());
 | 
			
		||||
		// Clean up from any previous operations.
 | 
			
		||||
		this.userSession.setRecipe(null);
 | 
			
		||||
		doFind();
 | 
			
		||||
| 
						 | 
				
			
			@ -177,9 +211,20 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
	 */
 | 
			
		||||
	public String doFind() {
 | 
			
		||||
		List<Recipe> recipes = null;
 | 
			
		||||
		if ( searchText == null ) {
 | 
			
		||||
			setSearchText("");
 | 
			
		||||
		}
 | 
			
		||||
		searchText = searchText.trim();
 | 
			
		||||
 | 
			
		||||
		switch (this.getUserSession().getSearchType()) {
 | 
			
		||||
		// Persist current settings
 | 
			
		||||
		try {
 | 
			
		||||
			cookieBean.saveCookies();
 | 
			
		||||
		} catch (UnsupportedEncodingException e) {
 | 
			
		||||
			// Something is really wrong if we can't create UTF-8!
 | 
			
		||||
			log.error("Unable to save cookies!", e);
 | 
			
		||||
		}
 | 
			
		||||
		RecipeSearchType st = searchtypeEnum(); 
 | 
			
		||||
		switch (st) {
 | 
			
		||||
		case rst_BY_NAME:
 | 
			
		||||
			recipes = recipeService.findByTitle(searchText);
 | 
			
		||||
			break;
 | 
			
		||||
| 
						 | 
				
			
			@ -197,12 +242,11 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			log.error("Invalid recipe search type: "
 | 
			
		||||
					+ this.getUserSession().getSearchType());
 | 
			
		||||
					+ st);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		getSearchResults().setWrappedData(recipes);
 | 
			
		||||
		this.userSession.setLastSearch(this.getSearchText());
 | 
			
		||||
		getSearchResults().setWrappedData(recipes);	
 | 
			
		||||
		return null; // Stay on page
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -243,27 +287,4 @@ public class AdminMainBean implements Serializable {
 | 
			
		|||
										// items.
 | 
			
		||||
		return "recipeDetails?faces-redirect=true";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get printable preptime. Database version is in seconds.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @deprecated User {@link UserSession#formatTime(Long)}
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return Formatted time. Called from EL on main page.
 | 
			
		||||
	 */
 | 
			
		||||
	public String formatPreptime(int timesec) {
 | 
			
		||||
		StringBuffer sb = new StringBuffer(20);
 | 
			
		||||
		int preptime = timesec / 60;
 | 
			
		||||
		if (preptime > 60) {
 | 
			
		||||
			int hours = preptime / 60;
 | 
			
		||||
			sb.append(hours);
 | 
			
		||||
			sb.append(" h. ");
 | 
			
		||||
			preptime %= 60;
 | 
			
		||||
		}
 | 
			
		||||
		if (preptime > 0) {
 | 
			
		||||
			sb.append(preptime);
 | 
			
		||||
			sb.append(" min.");
 | 
			
		||||
		}
 | 
			
		||||
		return sb.toString();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										51
									
								
								src/main/java/com/mousetech/gourmetj/AppBean.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/main/java/com/mousetech/gourmetj/AppBean.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
package com.mousetech.gourmetj;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import jakarta.enterprise.context.ApplicationScoped;
 | 
			
		||||
import jakarta.faces.model.SelectItem;
 | 
			
		||||
import jakarta.inject.Named;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Appplication-scope data (mostly constants)
 | 
			
		||||
 * 
 | 
			
		||||
 * @author timh
 | 
			
		||||
 * @since Feb 1, 2024
 | 
			
		||||
 */
 | 
			
		||||
@Named
 | 
			
		||||
@ApplicationScoped
 | 
			
		||||
public class AppBean {
 | 
			
		||||
 | 
			
		||||
	public AppBean() {
 | 
			
		||||
		// TODO Auto-generated constructor stub
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<SelectItem> searchTypeList;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the searchTypeList
 | 
			
		||||
	 * @see RecipeSearchType
 | 
			
		||||
	 * Used by main.xhtml
 | 
			
		||||
	 */
 | 
			
		||||
	public List<SelectItem> getSearchTypeList() {
 | 
			
		||||
		if (searchTypeList == null) {
 | 
			
		||||
			searchTypeList = loadSearchTypeList();
 | 
			
		||||
		}
 | 
			
		||||
		return searchTypeList;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<SelectItem> loadSearchTypeList() {
 | 
			
		||||
		List<SelectItem> list = new ArrayList<SelectItem>(5);
 | 
			
		||||
		list.add(new SelectItem(RecipeSearchType.rst_BY_NAME.ordinal(),
 | 
			
		||||
				"Title"));
 | 
			
		||||
		list.add(new SelectItem(RecipeSearchType.rst_BY_CATEGORY.ordinal(),
 | 
			
		||||
				"Category"));
 | 
			
		||||
		list.add(new SelectItem(RecipeSearchType.rst_BY_CUISINE.ordinal(),
 | 
			
		||||
				"Cuisine"));
 | 
			
		||||
		list.add(
 | 
			
		||||
			new SelectItem(RecipeSearchType.rst_BY_INGREDIENT.ordinal(),
 | 
			
		||||
					"Ingredient"));
 | 
			
		||||
		return list;
 | 
			
		||||
	}	
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								src/main/java/com/mousetech/gourmetj/CookieBean.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/main/java/com/mousetech/gourmetj/CookieBean.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Copyright (C) 2024, Tim Holloway
 | 
			
		||||
 *
 | 
			
		||||
 * Manages app data persisted client-side in cookies.
 | 
			
		||||
 *
 | 
			
		||||
 * Date written: Jan 31, 2024
 | 
			
		||||
 * Author: Tim Holloway <timh@mousetech.com>
 | 
			
		||||
 */
 | 
			
		||||
package com.mousetech.gourmetj;
 | 
			
		||||
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Map.Entry;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import jakarta.annotation.PostConstruct;
 | 
			
		||||
import jakarta.faces.view.ViewScoped;
 | 
			
		||||
import jakarta.inject.Named;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Caching object for cookie data persistence.
 | 
			
		||||
 * 
 | 
			
		||||
 * @author timh
 | 
			
		||||
 * @since Jan 31, 2024
 | 
			
		||||
 */
 | 
			
		||||
@Named
 | 
			
		||||
@ViewScoped
 | 
			
		||||
public class CookieBean {
 | 
			
		||||
 | 
			
		||||
	private static final String KEY_DISPLAY_ROWS = "displayRows";
 | 
			
		||||
 | 
			
		||||
	private static final String KEY_SEARCH_TYPE = "searchType";
 | 
			
		||||
 | 
			
		||||
	private static final String KEY_SEARCH_FOR = "searchFor";
 | 
			
		||||
 | 
			
		||||
	/* Logger */
 | 
			
		||||
 | 
			
		||||
	private static final Logger log =
 | 
			
		||||
			LoggerFactory.getLogger(CookieBean.class);
 | 
			
		||||
 | 
			
		||||
	private Map<String, String> cookieMap;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Constructor.
 | 
			
		||||
	 */
 | 
			
		||||
	public CookieBean() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@PostConstruct
 | 
			
		||||
	public void init() {
 | 
			
		||||
		this.cookieMap = JSFUtils.getCookies();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Persist us to client cookie storage
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @throws UnsupportedEncodingException (which should never
 | 
			
		||||
	 *                                      happen)
 | 
			
		||||
	 */
 | 
			
		||||
	public void saveCookies()
 | 
			
		||||
			throws UnsupportedEncodingException {
 | 
			
		||||
		final Map<String, Object> properties = new HashMap<>();
 | 
			
		||||
		properties.put("maxAge", 31536000);
 | 
			
		||||
		properties.put("path", "/");
 | 
			
		||||
		properties.put("SameSite", "Strict");
 | 
			
		||||
 | 
			
		||||
		for (Entry<String, String> e : cookieMap.entrySet()) {
 | 
			
		||||
			JSFUtils.outputCookie(e.getKey(), e.getValue(),
 | 
			
		||||
				properties);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get Cookie value by name
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param name Name of the cookie
 | 
			
		||||
	 * @return Value stored in the cookie
 | 
			
		||||
	 */
 | 
			
		||||
	public String getCookieValue(String name) {
 | 
			
		||||
		return cookieMap.get(name);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setCookieValue(String name, String value) {
 | 
			
		||||
		cookieMap.put(name, value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ************************
 | 
			
		||||
	// App-specific properties
 | 
			
		||||
	// ************************
 | 
			
		||||
 | 
			
		||||
	public String getSearchText() {
 | 
			
		||||
		return cookieMap.get(KEY_SEARCH_FOR);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setSearchText(String value) {
 | 
			
		||||
		cookieMap.put(KEY_SEARCH_FOR, value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// **
 | 
			
		||||
	public Integer getSearchType() {
 | 
			
		||||
		if (!cookieMap.containsKey(KEY_SEARCH_TYPE)) {
 | 
			
		||||
			cookieMap.put(KEY_SEARCH_TYPE, "0");
 | 
			
		||||
		}
 | 
			
		||||
		String st = cookieMap.get(KEY_SEARCH_TYPE);
 | 
			
		||||
		return Integer.valueOf(String.valueOf(st));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setSearchType(Integer value) {
 | 
			
		||||
		cookieMap.put(KEY_SEARCH_TYPE, String.valueOf(value));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// **
 | 
			
		||||
	public Integer getDisplayListSize() {
 | 
			
		||||
		if (!cookieMap.containsKey(KEY_DISPLAY_ROWS)) {
 | 
			
		||||
			cookieMap.put(KEY_DISPLAY_ROWS, "30");
 | 
			
		||||
		}
 | 
			
		||||
		String st = cookieMap.get(KEY_DISPLAY_ROWS);
 | 
			
		||||
		return Integer.valueOf(String.valueOf(st));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setDisplayListSize(Integer value) {
 | 
			
		||||
		cookieMap.put(KEY_DISPLAY_ROWS, String.valueOf(value));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +1,17 @@
 | 
			
		|||
package com.mousetech.gourmetj;
 | 
			
		||||
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import jakarta.faces.application.FacesMessage;
 | 
			
		||||
import jakarta.faces.context.ExternalContext;
 | 
			
		||||
import jakarta.faces.context.FacesContext;
 | 
			
		||||
import jakarta.faces.context.Flash;
 | 
			
		||||
import jakarta.servlet.http.Cookie;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +61,14 @@ public class JSFUtils {
 | 
			
		|||
			message);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	public static void addWarningMessage(String msgString) {
 | 
			
		||||
		FacesMessage message = new FacesMessage(
 | 
			
		||||
			FacesMessage.SEVERITY_WARN, "WARNING", msgString);
 | 
			
		||||
		FacesContext.getCurrentInstance().addMessage(null,
 | 
			
		||||
			message);
 | 
			
		||||
	}	
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Post an error-level message to the FacesContext where the
 | 
			
		||||
	 * <h:messages> tag can display it.
 | 
			
		||||
| 
						 | 
				
			
			@ -103,4 +117,36 @@ public class JSFUtils {
 | 
			
		|||
	public static void putFlash(String key, Object value) {
 | 
			
		||||
		flashScope().put(key, value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//***********
 | 
			
		||||
	//* COOKIE!!!
 | 
			
		||||
	//***********
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get cookie values.
 | 
			
		||||
	 */
 | 
			
		||||
	public static Map<String, String> getCookies(){
 | 
			
		||||
		Map<String, Object> m0 = getExternalContext().getRequestCookieMap();
 | 
			
		||||
		Map<String, String>m1 = new HashMap<String, String>();
 | 
			
		||||
		m1 = m0.entrySet()
 | 
			
		||||
				.stream()
 | 
			
		||||
				.collect(Collectors.toMap(
 | 
			
		||||
					e -> e.getKey(),
 | 
			
		||||
					e -> ((Cookie)e.getValue()).getValue()));
 | 
			
		||||
		return m1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Set a cookie value in Response.
 | 
			
		||||
	 * @param name Cookie name
 | 
			
		||||
	 * @param value Cookie value
 | 
			
		||||
	 * @param properties Cookie property Map (timeout, <i>etc.</i>)
 | 
			
		||||
	 * @throws UnsupportedEncodingException 
 | 
			
		||||
	 */
 | 
			
		||||
	public static void outputCookie(String name,
 | 
			
		||||
			String value, Map<String, Object> properties) throws UnsupportedEncodingException {
 | 
			
		||||
		getExternalContext().addResponseCookie(name,
 | 
			
		||||
			URLEncoder.encode(value, "UTF-8"),			
 | 
			
		||||
			properties);
 | 
			
		||||
	}		
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -129,32 +129,6 @@ public class UserSession implements Serializable {
 | 
			
		|||
		this.searchType = searchType;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<SelectItem> searchTypeList;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the searchTypeList
 | 
			
		||||
	 */
 | 
			
		||||
	public List<SelectItem> getSearchTypeList() {
 | 
			
		||||
		if (searchTypeList == null) {
 | 
			
		||||
			searchTypeList = loadSearchTypeList();
 | 
			
		||||
		}
 | 
			
		||||
		return searchTypeList;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<SelectItem> loadSearchTypeList() {
 | 
			
		||||
		List<SelectItem> list = new ArrayList<SelectItem>(5);
 | 
			
		||||
		list.add(new SelectItem(RecipeSearchType.rst_BY_NAME,
 | 
			
		||||
				"Title"));
 | 
			
		||||
		list.add(new SelectItem(RecipeSearchType.rst_BY_CATEGORY,
 | 
			
		||||
				"Category"));
 | 
			
		||||
		list.add(new SelectItem(RecipeSearchType.rst_BY_CUISINE,
 | 
			
		||||
				"Cuisine"));
 | 
			
		||||
		list.add(
 | 
			
		||||
			new SelectItem(RecipeSearchType.rst_BY_INGREDIENT,
 | 
			
		||||
					"Ingredient"));
 | 
			
		||||
		return list;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ====
 | 
			
		||||
 | 
			
		||||
	public String formatCategories(Recipe r) {
 | 
			
		||||
| 
						 | 
				
			
			@ -190,16 +164,17 @@ public class UserSession implements Serializable {
 | 
			
		|||
	private List<Recipe> shoppingList = new ArrayList<Recipe>();
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the sessionTimeoutInterval
 | 
			
		||||
	 * @return the sessionTimeoutInterval, msec
 | 
			
		||||
	 */
 | 
			
		||||
	public long getSessionTimeoutInterval() {
 | 
			
		||||
		return sessionTimeoutInterval;
 | 
			
		||||
		return 5000L; //sessionTimeoutInterval;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void sessionIdleListener() {
 | 
			
		||||
		log.warn("Session Idle Listener fired.");
 | 
			
		||||
		PrimeFaces.current()
 | 
			
		||||
			.executeScript("sessionExpiredConfirmation.show()");
 | 
			
		||||
		JSFUtils.addWarningMessage("Timeout approaching. Save your work!");
 | 
			
		||||
//		PrimeFaces.current()
 | 
			
		||||
//			.executeScript("sessionExpiredConfirmation.show()");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public String logoutAction() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,10 +6,9 @@
 | 
			
		|||
    xmlns:ui="http://java.sun.com/jsf/facelets"
 | 
			
		||||
    xmlns:p="http://primefaces.org/ui"
 | 
			
		||||
>
 | 
			
		||||
<head></head>
 | 
			
		||||
<body>
 | 
			
		||||
<h:head></h:head>
 | 
			
		||||
<h:body>
 | 
			
		||||
    <ui:composition>
 | 
			
		||||
        <f:view>
 | 
			
		||||
            <h:head>
 | 
			
		||||
                <title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title>
 | 
			
		||||
                <link rel="icon" type="image/vnd.microsoft.icon"
 | 
			
		||||
| 
						 | 
				
			
			@ -29,50 +28,14 @@
 | 
			
		|||
                </ui:insert>
 | 
			
		||||
                <!--  -->
 | 
			
		||||
                <div id="footer">
 | 
			
		||||
                    (C) 2021 Tim Holloway, Licensed under the <a
 | 
			
		||||
                    (C) 2021, 2024 Tim Holloway, Licensed under the <a
 | 
			
		||||
                        href="http://www.apache.org/licenses/LICENSE-2.0"
 | 
			
		||||
                    >Apache License, Version 2.0</a>.
 | 
			
		||||
                    <p>Based on Gourmet Recipe Manager by T.
 | 
			
		||||
                        Hinkle</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <!--  -->
 | 
			
		||||
                <h:form id="frmTimeout">
 | 
			
		||||
                    <p:idleMonitor
 | 
			
		||||
                        timeout="#{userSession.sessionTimeoutInterval}"
 | 
			
		||||
                        onidle="PF('dlgSessionExpired').show()"
 | 
			
		||||
                    >
 | 
			
		||||
                        <p:ajax event="idle"
 | 
			
		||||
                            listener="#{userSession.sessionIdleListener}"
 | 
			
		||||
                        />
 | 
			
		||||
                    </p:idleMonitor>
 | 
			
		||||
                    <p:confirmDialog closable="false"
 | 
			
		||||
                        id="sessionExpiredDlg"
 | 
			
		||||
                        message="Your session has expired."
 | 
			
		||||
                        header="#{msgs['confirmDialog.initiatingDestroyProcess.label']}"
 | 
			
		||||
                        severity="alert"
 | 
			
		||||
                        widgetVar="dlgSessionExpired"
 | 
			
		||||
                        style="z-index: 25000"
 | 
			
		||||
                    >
 | 
			
		||||
                        <p:commandButton id="cmdExpiredOK"
 | 
			
		||||
                            value="OK" action="/main.jsf"
 | 
			
		||||
                            oncomplete="PF('dlgSessionExpired').hide()"
 | 
			
		||||
                        />
 | 
			
		||||
                    </p:confirmDialog>
 | 
			
		||||
                </h:form>
 | 
			
		||||
                <!--  -->
 | 
			
		||||
                <h:form id="frmOpErr">
 | 
			
		||||
                    <p:confirmDialog
 | 
			
		||||
                        message="Session may have expired."
 | 
			
		||||
                        header="Error"
 | 
			
		||||
                        severity="alert" widgetVar="opError"
 | 
			
		||||
                    >
 | 
			
		||||
                        <p:commandButton value="OK"
 | 
			
		||||
                            oncomplete="PF('opError').hide()"
 | 
			
		||||
                        />
 | 
			
		||||
                    </p:confirmDialog>
 | 
			
		||||
                </h:form>
 | 
			
		||||
            </h:body>
 | 
			
		||||
        </f:view>
 | 
			
		||||
    </ui:composition>
 | 
			
		||||
</body>
 | 
			
		||||
</h:body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -395,6 +395,19 @@
 | 
			
		|||
                    />                    
 | 
			
		||||
                </h:form>
 | 
			
		||||
            </p:panel>
 | 
			
		||||
            <!--  -->
 | 
			
		||||
            <p:growl id="growl" showDetail="true" />
 | 
			
		||||
            <h:form id="frmTimeout">
 | 
			
		||||
               <p:idleMonitor
 | 
			
		||||
                  timeout="#{userSession.sessionTimeoutInterval}"
 | 
			
		||||
               >
 | 
			
		||||
                  <p:ajax id="ajaxIdle" event="idle"
 | 
			
		||||
                    listener="#{userSession.sessionIdleListener}"
 | 
			
		||||
                    update="growl"
 | 
			
		||||
                  />
 | 
			
		||||
               </p:idleMonitor>
 | 
			
		||||
            </h:form>
 | 
			
		||||
            <!--  -->
 | 
			
		||||
            <p:dialog id="addGroupDlg" widgetVar="addGroupDlg">
 | 
			
		||||
                <h:form id="frmAddGroup">
 | 
			
		||||
                    <p:panelGrid columns="1">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,10 +28,10 @@
 | 
			
		|||
                />
 | 
			
		||||
                <p:outputLabel for="@next" value="Search for " />
 | 
			
		||||
                <p:selectOneMenu id="ctlSearchType"
 | 
			
		||||
                    value="#{userSession.searchType}"
 | 
			
		||||
                    value="#{cookieBean.searchType}"
 | 
			
		||||
                >
 | 
			
		||||
                    <f:selectItems
 | 
			
		||||
                        value="#{userSession.searchTypeList}"
 | 
			
		||||
                        value="#{appBean.searchTypeList}"
 | 
			
		||||
                    />
 | 
			
		||||
                    <p:ajax
 | 
			
		||||
                        listener="#{adminMainBean.resetSuggestions}"
 | 
			
		||||
| 
						 | 
				
			
			@ -45,12 +45,12 @@
 | 
			
		|||
                <p:commandButton value="New Recipe"
 | 
			
		||||
                    action="#{adminMainBean.doNewRecipe}"
 | 
			
		||||
                />
 | 
			
		||||
                <p:commandButton value="More..."
 | 
			
		||||
                <p:commandButton value="Shopping..."
 | 
			
		||||
                    action="#{adminMainBean.doMore}"
 | 
			
		||||
                />
 | 
			
		||||
                <h:outputText id="slistSSize"
 | 
			
		||||
                    style="margin-left: 2em"
 | 
			
		||||
                    value="#{userSession.shoppingList.size()}"
 | 
			
		||||
                    value="#{cookieBean.displayListSize}"
 | 
			
		||||
                />
 | 
			
		||||
                <h:outputLabel for="slistSize"
 | 
			
		||||
                    value=" Recipes in Shopping List"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
    xmlns:p="http://primefaces.org/ui"
 | 
			
		||||
    xmlns:c="http://xmlns.jcp.org/jstl"
 | 
			
		||||
>
 | 
			
		||||
    <!-- Tabbed page for the Mainpage "More..." button -->
 | 
			
		||||
    <!-- Tabbed page for the Mainpage "Shopping..." button -->
 | 
			
		||||
    <ui:define name="title">Gourmet Recipe Manager - Shopping</ui:define>
 | 
			
		||||
    <ui:define name="content">
 | 
			
		||||
        <style>
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +34,7 @@
 | 
			
		|||
	
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
        This is the list of recipe items you've selected to shop for.
 | 
			
		||||
        <h:messages />
 | 
			
		||||
        <p:tabView id="tabGroupClient" orientation="left"
 | 
			
		||||
            dynamic="true"
 | 
			
		||||
| 
						 | 
				
			
			@ -95,13 +96,11 @@
 | 
			
		|||
                                    value="Ingredients"
 | 
			
		||||
                                />
 | 
			
		||||
                            </f:facet>
 | 
			
		||||
                            <p:headerRow>
 | 
			
		||||
                                <p:column colspan="4">
 | 
			
		||||
                                    <h:outputText
 | 
			
		||||
                                        value="#{item.shopCat}"
 | 
			
		||||
                                    />
 | 
			
		||||
                                </p:column>
 | 
			
		||||
                            </p:headerRow>
 | 
			
		||||
                            <p:column label="Amt"
 | 
			
		||||
                                style="width: 3em; text-align: right"
 | 
			
		||||
                            >
 | 
			
		||||
| 
						 | 
				
			
			@ -150,6 +149,7 @@
 | 
			
		|||
            </p:tab>
 | 
			
		||||
            <!--  -->
 | 
			
		||||
            <p:tab id="tabPantry" title="Pantry">
 | 
			
		||||
                <h:outputText value="Stuff already in the pantry." />
 | 
			
		||||
                <h:outputText value="For future implementation" />
 | 
			
		||||
            </p:tab>
 | 
			
		||||
            <!--  -->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,11 @@ server:
 | 
			
		|||
    servlet:
 | 
			
		||||
        session:
 | 
			
		||||
            timeout: '30m'
 | 
			
		||||
# Theme here pverrides jinfaces theme            
 | 
			
		||||
#        context-parameters:
 | 
			
		||||
#            primefaces:
 | 
			
		||||
#                THEME: vela
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
gourmet:
 | 
			
		||||
    password:
 | 
			
		||||
| 
						 | 
				
			
			@ -33,4 +38,4 @@ gourmet:
 | 
			
		|||
        
 | 
			
		||||
joinfaces:
 | 
			
		||||
  primefaces:
 | 
			
		||||
    theme: saga
 | 
			
		||||
    theme: casablanca
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user