From 4bcf8b911fddd99f80710d17fa87d58b1ede022a Mon Sep 17 00:00:00 2001 From: Tim Holloway Date: Tue, 28 Dec 2021 15:59:40 -0500 Subject: [PATCH] Trauma averted - beans migrated --- .../com/mousetech/gourmetj/AdminMainBean.java | 208 ++++++++++ .../com/mousetech/gourmetj/CuisineBean.java | 49 +++ .../gourmetj/IngredientDigester.java | 332 ++++++++++++++++ .../com/mousetech/gourmetj/IngredientUI.java | 375 ++++++++++++++++++ .../java/com/mousetech/gourmetj/JSFUtils.java | 86 ++++ .../com/mousetech/gourmetj/UserSession.java | 97 +++++ .../persistence/service/RecipeService.java | 47 +++ .../resources/META-INF/resources/main.xhtml | 67 ++++ .../resources/WEB-INF/layout/layout.xhtml | 28 ++ 9 files changed, 1289 insertions(+) create mode 100644 src/main/java/com/mousetech/gourmetj/AdminMainBean.java create mode 100644 src/main/java/com/mousetech/gourmetj/CuisineBean.java create mode 100644 src/main/java/com/mousetech/gourmetj/IngredientDigester.java create mode 100644 src/main/java/com/mousetech/gourmetj/IngredientUI.java create mode 100644 src/main/java/com/mousetech/gourmetj/JSFUtils.java create mode 100644 src/main/java/com/mousetech/gourmetj/UserSession.java create mode 100644 src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java create mode 100644 src/main/resources/META-INF/resources/main.xhtml create mode 100644 src/main/resources/WEB-INF/layout/layout.xhtml diff --git a/src/main/java/com/mousetech/gourmetj/AdminMainBean.java b/src/main/java/com/mousetech/gourmetj/AdminMainBean.java new file mode 100644 index 0000000..51d14b4 --- /dev/null +++ b/src/main/java/com/mousetech/gourmetj/AdminMainBean.java @@ -0,0 +1,208 @@ +package com.mousetech.gourmetj; + +import java.io.Serializable; + +import javax.annotation.PostConstruct; + +import javax.faces.model.DataModel; +import javax.faces.model.ListDataModel; +import javax.faces.view.ViewScoped; +import javax.inject.Inject; +import javax.inject.Named; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import com.mousetech.gourmetj.UserSession; +import com.mousetech.gourmetj.persistence.model.Category; +import com.mousetech.gourmetj.persistence.model.Recipe; +import com.mousetech.gourmetj.persistence.service.RecipeService; + +/** + * Main control panel backing bean. + * + * @author timh + * @since Jun 28, 2012 + */ + +@Named +@ViewScoped +public class AdminMainBean implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 2L; + + public AdminMainBean() { + + } + + /* Logger */ + + private static final Logger log = LoggerFactory.getLogger(AdminMainBean.class); + + + /** + * Persistency service for Recipes + */ + + @Inject + transient RecipeService recipeService; + + public void setRecipeService(RecipeService service) { + log.debug("INJECT RECIPESERVICE===" + service); + this.recipeService = service; + } + + // ** + @Inject + private UserSession userSession; + + /** + * @return the userSession + */ + public UserSession getUserSession() { + return userSession; + } + + /** + * @param userSession the userSession to set + */ + public void setUserSession(UserSession userSession) { + this.userSession = userSession; + } + + // ** + private String searchText; + + /** + * @return the searchText + */ + public String getSearchText() { + if (this.searchResults == null) { + // Fake around broken @PostConstruct + this.setSearchText(userSession.getLastSearch()); + } + return searchText; + } + + /** + * @param searchText the searchText to set + */ + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + /**/ + transient DataModel searchResults; + + /** + * @return the searchResults + */ + public DataModel getSearchResults() { + if (searchResults == null) { + searchResults = new ListDataModel(); + init(); // @PostConstruct is broken + } + return searchResults; + } + + /** + * @param searchResults the searchResults to set + */ + public void setSearchResults( + DataModel searchResults) { + this.searchResults = searchResults; + } + + /** + * Return to last search, if any + */ + @PostConstruct + void init() { + this.setSearchText(userSession.getLastSearch()); + // Clean up from any previous operations. + this.userSession.setRecipe(null); + doFind(); + } + + /** + * Finder + * + * @return Navigation string + */ + public String doFind() { + List recipes = + recipeService.findByTitle(searchText); + + getSearchResults().setWrappedData(recipes); + this.userSession.setLastSearch(this.getSearchText()); + return null; // Stay on page + } + + /** + * Prep to create a new recipe + * + * @return navigation to detailEdit page + */ + public String doNewRecipe() { + // Clear for new recipe + this.userSession.setLastEdit(null); + // Construct a blank recipe to be created. + this.userSession.setRecipe(new Recipe()); + return "detailEdit?faces-redirect=true"; + } + + /** + * Show selected recipe + * + * @return + */ + public String showRecipe() { + long recipeId = getSearchResults().getRowData().getId(); + JSFUtils.flashScope().put("recipeID", + Long.valueOf(recipeId)); + userSession.setLastEdit(recipeId); + userSession.setRecipe(null); // forces loading of line + // items. + return "recipeDetails?faces-redirect=true"; + } + + /** + * Get printable preptime. Database version is in seconds. + * + * @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(); + } + + public String formatCategories(Recipe r) { + StringBuffer sb = new StringBuffer(30); + boolean first = true; + for (Category cat : r.getCategories()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(cat.getCategory()); + } + return sb.toString(); + } +} diff --git a/src/main/java/com/mousetech/gourmetj/CuisineBean.java b/src/main/java/com/mousetech/gourmetj/CuisineBean.java new file mode 100644 index 0000000..52ee14b --- /dev/null +++ b/src/main/java/com/mousetech/gourmetj/CuisineBean.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2021, Tim Holloway + * + * Date written: Dec 7, 2021 + * Author: Tim Holloway + */ +package com.mousetech.gourmetj; + +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.inject.Named; + +import com.mousetech.gourmetj.persistence.service.RecipeService; + +/** + * @author timh + * @since Dec 7, 2021 + */ +@Named +@ApplicationScoped +public class CuisineBean { + + @Inject + private RecipeService recipeService; + + private List cuisineList; + + /** + * Return list of cuisines currently on file. Create it if + * needed. + */ + public List getCuisineList() { + if (this.cuisineList == null ) { + this.cuisineList = loadCuisineList(); + } + return this.cuisineList; + } + + private synchronized List loadCuisineList() { + List list = this.recipeService.findCuisines(); + return list; + } + + public synchronized void registerCuisine(String cuisineString) { + // search in-memory list. Add if needed. + } +} diff --git a/src/main/java/com/mousetech/gourmetj/IngredientDigester.java b/src/main/java/com/mousetech/gourmetj/IngredientDigester.java new file mode 100644 index 0000000..a3ac20a --- /dev/null +++ b/src/main/java/com/mousetech/gourmetj/IngredientDigester.java @@ -0,0 +1,332 @@ +package com.mousetech.gourmetj; + +import java.text.DecimalFormat; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.mousetech.gourmetj.persistence.model.Ingredient; + +/** + * Service for taking an ingredients line, parsing it and + * returning a populated Ingredients object. Understands fraction + * characters in the amounts fields. + * + * @author timh + * @since Dec 1, 2021 TestedBy @see IngredientDigesterTest + */ +public class IngredientDigester { + + /** + * @author timh + * @since Dec 5, 2021 + */ + public enum IngredientAmountFormat { + IA_DECIMAL, // 5.75 + IA_TEXT, // 5 3/4 + IA_SYMBOLS, // 5¾ + } + + final static String amountPatternTxt = + "([0-9¼⅓½⅔¾⅛⅜⅝⅞][\\s\\-\\.]*[/0-9¼⅓½⅔¾⅛⅜⅝⅞]*)"; + final static Pattern amountPattern = + Pattern.compile(amountPatternTxt); + + final static Pattern fractionPattern = + Pattern.compile("(\\d*)[\\s\\-]*(\\d+)/(\\d+)"); + + /** + * Digest an ingredient text line and construct an Ingredient + * object + * + * @param inputText Input text line + * @return Ingredient + */ + public static Ingredient digest(String inputText) { + inputText = inputText.trim(); + final boolean optional = + inputText.toLowerCase().contains("optional"); + + Ingredient ing = new Ingredient(); + ing.setAmount(0.0); + + // Split for amount, unit and rest of string. + String[] pamt = parseFancyNumber(inputText); + if (pamt == null) { + // Can't parse + ing.setAmount(null); + ing.setUnit(null); + ing.setItem(inputText); + } else { + final Double[] amount = digestAmount(pamt[0]); + ing.setAmount(amount[0]); + ing.setRangeamount(amount[1]); + + String[] unext = pamt[1].split("\\s", 2); + switch (unext.length) { + case 1: + ing.setUnit(null); + ing.setItem(unext[0]); + break; + case 2: + ing.setUnit(unext[0]); + ing.setItem(unext[1]); + break; + } + } + + ing.setPosition(null); + ing.setOptional(optional ? 1 : 0); + // Extract the item type from the item name + // Assume possible prep details ("parsley, chopped") + String istring = ing.getItem(); + String iclass = istring.split("\\s*,",2)[0]; + ing.setIngkey(iclass); + return ing; + } + + /** + * Break down ingredients line into 2 parts. First part is + * numeric text representing amount and optional range, + * second part is text remainder, including units. + * + * @param instring String to parse + * @return 2-dimensional String array with amount and + * remainder or null if instring does + * not parse. + * + */ + static String[] parseFancyNumber(String instring) { + Matcher m = amountPattern.matcher(instring); + if (m.find()) { + // System.out.println("GROUPS=" + m.groupCount()); + String rs = m.group(1); + return new String[] { rs.trim(), + instring.substring(m.end()).trim() }; + } + return null; + } + + // TODO: Remove "optional", if present, including + // brackets/parentheses + + /** + * Convert instring into a number with optional fractions. + * Can take decimals, unicode ratio characters, and n/m form + * with embedded spaces and dashes. + * + * @param instring String to analyze + * @return number equivalent pair. Note that thirds are not + * precise in binary floating-point! First element is + * low range amount. Second element is null unless a + * high range was given as well. + */ + public static Double[] digestAmount(String instring) { + String[] amtParts = instring.split("\\s*-\\s*"); + Double[] result = new Double[2]; + result[0] = parseSingleAmount(amtParts[0]); + if (amtParts.length == 1) { + return result; + } + + // dash separates low, high values OR integer and a + // fraction. + result[1] = parseSingleAmount(amtParts[1]); + if (result[1] < result[0]) { + result[0] += result[1]; + result[1] = 0.0d; + } + return result; + } + + /** + * Parse a single amount. May be integer, decimal number, + * integer + fraction character or [integer] integer / + * integer. + * + * @param instring amount string to parse + * @return parsed value. + * @throws NumberFormatException if bad decimal value given. + */ + static Double parseSingleAmount(String instring) { + double value = 0; + double frac = 0; + + if (instring.contains("/")) { + // Break down a/b fraction + frac = digestFraction(instring); + return frac; + } + + if (instring.contains(".")) { + frac = Double.valueOf(instring); + return frac; + } + + // Look for integer and possibly a symbol; + for (int i = 0; i < instring.length(); i++) { + final Character ch = instring.charAt(i); + switch (ch) { + case '⅛': + frac = 0.125d; + break; + case '⅓': + frac = 1.0 / 3.0; + break; + case '¼': + frac = 0.25d; + break; + case '⅜': + frac = 3.0 * 0.125d; + break; + case '½': + frac = 0.5d; + break; + case '⅝': + frac = 5.0 * 0.125d; + break; + case '⅔': + frac = 2.0 / 3.0; + break; + case '⅞': + frac = 7.0 * 0.125d; + break; + default: + if (Character.isDigit(ch)) { + value = value * 10.0 + (ch - '0'); + } + // Ignore spaces, dashes, etc. + break; + } + } + value += frac; + return value; + } + + /** + * Pull fraction from tail-end of string and evaluate it. + * + * @param instring String in the form dddxxxddd/ddd, where d + * is digit, x is dash or space (optional) + * @return results of evaluation. May need to throw + * Exception??? + */ + static double digestFraction(String instring) { + Matcher m = fractionPattern.matcher(instring); + if (!m.find()) { + return 0.0; + } + + String lead = m.group(1); + String snumerator = m.group(2); + String sdenominator = m.group(3); + + double numerator; + double denominator; + try { + numerator = Double.parseDouble(snumerator); + denominator = Double.parseDouble(sdenominator); + } catch (NumberFormatException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return 0.0; + } + + if (denominator == 0.0d) { + return 0.0d; + } + double result = numerator / denominator; + if (!lead.isEmpty()) { + result += Double.valueOf(lead); // integral part + } + return result; + } + + /** + * Convert floating-point amounts to display-friendly form. + * + * @param format Amount formatting scheme + * @param amt Amount to display + * @param ext amount range high value (can be null) + * @return Displayable String. Zero returns an empty string. + * + */ + public static String displayAmount( + IngredientAmountFormat format, Double amt, + Double ext) { + if (amt == 0.0d) { + return ""; + } + String amountRange = displayAmountPart(format, amt); + if (ext != null) { + amountRange += "-" + displayAmountPart(format, ext); + } + return amountRange; + } + + /** + * @param format + * @param amt Amount to format + * @return Amount formatted in decimal, text, or text/symbol. + * Values that don't conform to the common fractions + * report as decimal values. Returns empty string for + * 0. + */ + static String displayAmountPart( + IngredientAmountFormat format, Double amt) { + if (amt == 0.0d) { + return ""; + } + DecimalFormat df = new DecimalFormat("#.###"); + String fnum = df.format(amt); + if (format == IngredientAmountFormat.IA_DECIMAL) { + return fnum; + } + + String part[] = fnum.split("\\."); + if (part.length == 1) { + return part[0]; // Integer + } + String p2 = part[1]; + String fs = ""; + if (format == IngredientAmountFormat.IA_TEXT) { + if (p2.equals("125")) { + fs = "1/8"; + } else if (p2.equals("333")) { + fs = "1/3"; + } else if (p2.equals("25")) { + fs = "1/4"; + } else if (p2.equals("5")) { + fs = "1/2"; + } else if (p2.equals("667")) { + fs = "2/3"; + } else if (p2.equals("75")) { + fs = "3/4"; + } else { // 3, 5, 7/8 + return fnum; + } + if (!"0".equals(part[0])) { + fs = ' ' + fs; + } + } else { + if (p2.equals("125")) { + fs = "⅛"; + } else if (p2.equals("333")) { + fs = "⅓"; + } else if (p2.equals("25")) { + fs = "¼"; + } else if (p2.equals("5")) { + fs = "½"; + } else if (p2.equals("667")) { + fs = "⅔"; + } else if (p2.equals("75")) { + fs = "¾"; + } else { // 3, 5, 7/8 + return fnum; + } + } + if ("0".equals(part[0])) { + part[0] = ""; + } + return part[0] + fs; + } +} diff --git a/src/main/java/com/mousetech/gourmetj/IngredientUI.java b/src/main/java/com/mousetech/gourmetj/IngredientUI.java new file mode 100644 index 0000000..47a5bb8 --- /dev/null +++ b/src/main/java/com/mousetech/gourmetj/IngredientUI.java @@ -0,0 +1,375 @@ +package com.mousetech.gourmetj; + + +import com.mousetech.gourmetj.persistence.model.Ingredient; +import com.mousetech.gourmetj.persistence.model.IngredientIF; +import com.mousetech.gourmetj.persistence.model.Recipe; +import com.mousetech.gourmetj.IngredientDigester; +import com.mousetech.gourmetj.IngredientDigester.IngredientAmountFormat; + +/** + * JSF-friendly decorator for @see Ingredient. Formats amount + * with fractions and supports checkboxes. Primary use + * is as a JSF TableModel wrapped content in @see RecipeDetailBean. + * + * TestedBy @see IngredientUITest + * + * @author timh + * @since Dec 2, 2021 + */ +public class IngredientUI implements IngredientIF { + private Ingredient ingredient; + + /** + * Constructor. + * @param ingredient + */ + public IngredientUI(Ingredient ingredient) { + this.ingredient = ingredient; + } + + public Ingredient getIngredient() { + return this.ingredient; + } + + private boolean ingGroup; + /** + * @param ingGroup the ingGroup to set + */ + public void setIngGroup(boolean ingGroup) { + this.ingGroup = ingGroup; + } + + /** + * Ingredient groups are rendered visually as synthetic + * rows. Actual group IDs are part of line-item ingredients, + * so when building the model, we create a group row when + * the line item inggroup value changes. + * + * @return true for an Ingredient Group + * header row. + */ + public boolean isIngGroup() { + return this.ingGroup; + } + + /** + * Row selection checkbox (UI only) + */ + + private boolean selected; + + + /** + * @return the selected status + */ + public boolean isSelected() { + return selected; + } + + /** + * @param selected the selected to set + */ + public void setSelected(boolean selected) { + this.selected = selected; + } + + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getAmount() + */ + public Double getAmount() { + return ingredient.getAmount(); + } + + /** + * @param amount + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setAmount(java.lang.Double) + */ + public void setAmount(Double amount) { + ingredient.setAmount(amount); + } + + /** + * Get amount display-friendly + * @see #getAmount + */ + public String getDisplayAmount() { + // TODO + Double amt = ingredient.getAmount(); + if ( amt == null ) { + return ""; + } + String amt1 = IngredientDigester.displayAmount( + IngredientAmountFormat.IA_SYMBOLS, amt, ingredient.getRangeamount()); + return amt1; + } + + /** + * Set amount display-friendly + * @see #setAmount + */ + public void setDisplayAmount(String amount) { + if ( amount.isBlank() ) { + ingredient.setAmount(null); + } + IngredientDigester digester = new IngredientDigester(); + Double[] amt = digester.digestAmount(amount); + ingredient.setAmount(amt[0]); + ingredient.setRangeamount(amt[1]); + } + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getDeleted() + */ + public Integer getDeleted() { + return ingredient.getDeleted(); + } + + /** + * @param deleted + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setDeleted(java.lang.Integer) + */ + public void setDeleted(Integer deleted) { + ingredient.setDeleted(deleted); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getId() + */ + public Long getId() { + return ingredient.getId(); + } + + /** + * @param id + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setId(long) + */ + public void setId(Long id) { + ingredient.setId(id); + } + + /** + * @return + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return ingredient.hashCode(); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getInggroup() + */ + public String getInggroup() { + return ingredient.getInggroup(); + } + + /** + * @param inggroup + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setInggroup(java.lang.String) + */ + public void setInggroup(String inggroup) { + ingredient.setInggroup(inggroup); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getIngkey() + */ + public String getIngkey() { + return ingredient.getIngkey(); + } + + /** + * @param ingkey + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setIngkey(java.lang.String) + */ + public void setIngkey(String ingkey) { + ingredient.setIngkey(ingkey); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getItem() + */ + public String getItem() { + return ingredient.getItem(); + } + + /** + * @param item + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setItem(java.lang.String) + */ + public void setItem(String item) { + ingredient.setItem(item); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getOptional() + */ + public Integer getOptional() { + return ingredient.getOptional(); + } + + /** + * @param optional + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setOptional(java.lang.Integer) + */ + public void setOptional(Integer optional) { + ingredient.setOptional(optional); + } + + /** + * Get optional value in boolean Checkbox friendly form + * @return + */ + public boolean getOptionalCB() { + return ingredient.getOptional() != 0; + } + + public void setOptionalCB(boolean value) { + ingredient.setOptional(value ? 1 : 0); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getPosition() + */ + public Integer getPosition() { + return ingredient.getPosition(); + } + + /** + * @param position + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setPosition(java.lang.Integer) + */ + public void setPosition(Integer position) { + ingredient.setPosition(position); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getRangeamount() + */ + public Double getRangeamount() { + return ingredient.getRangeamount(); + } + + /** + * @param rangeamount + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setRangeamount(java.lang.Double) + */ + public void setRangeamount(Double rangeamount) { + ingredient.setRangeamount(rangeamount); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getRecipe() + */ + public Recipe getRecipe() { + return ingredient.getRecipe(); + } + + /** + * @param recipe + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setRecipe(com.mousetech.gourmetj.persistence.model.Recipe) + */ + public void setRecipe(Recipe recipe) { + ingredient.setRecipe(recipe); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getRefid() + */ + public Long getRefid() { + return ingredient.getRefid(); + } + + /** + * @param refid + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setRefid(java.lang.Long) + */ + public void setRefid(Long refid) { + ingredient.setRefid(refid); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getShopoptional() + */ + public Integer getShopoptional() { + return ingredient.getShopoptional(); + } + + /** + * @param shopoptional + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setShopoptional(java.lang.Integer) + */ + public void setShopoptional(Integer shopoptional) { + ingredient.setShopoptional(shopoptional); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getShopoptional() + */ + public boolean getShopoptionalCB() { + return ingredient.getShopoptional() != 0; + } + + /** + * @param shopoptional + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setShopoptional(java.lang.Integer) + */ + public void setShopoptionalCB(boolean shopoptional) { + ingredient.setShopoptional(shopoptional? 1:0); + } + + /** + * @return + * @see com.mousetech.gourmetj.persistence.model.Ingredient#getUnit() + */ + public String getUnit() { + return ingredient.getUnit(); + } + + /** + * @param unit + * @see com.mousetech.gourmetj.persistence.model.Ingredient#setUnit(java.lang.String) + */ + public void setUnit(String unit) { + ingredient.setUnit(unit); + } + + + // This goes to the shopCats table via ManyToOne at save time. + private String shopCat; + + /** + * @return the shopCat + */ + public String getShopCat() { + return shopCat; + } + + /** + * @param shopCat the shopCat to set + */ + public void setShopCat(String shopCat) { + this.shopCat = shopCat; + } + + /** + * @return + * @see java.lang.Object#toString() + */ + public String toString() { + return ingredient.toString(); + } +} diff --git a/src/main/java/com/mousetech/gourmetj/JSFUtils.java b/src/main/java/com/mousetech/gourmetj/JSFUtils.java new file mode 100644 index 0000000..eaad0fd --- /dev/null +++ b/src/main/java/com/mousetech/gourmetj/JSFUtils.java @@ -0,0 +1,86 @@ +package com.mousetech.gourmetj; + +import java.io.InputStream; + +import javax.faces.application.FacesMessage; +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; +import javax.faces.context.Flash; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JSFUtils { + + /* Logger */ + + private static final Logger log = + LoggerFactory.getLogger(JSFUtils.class); + + private static ExternalContext getExternalContext() { + FacesContext facesContext = + FacesContext.getCurrentInstance(); + ExternalContext externalContext = + facesContext.getExternalContext(); + return externalContext; + } + + /** + * Obtain resource as Stream. The caller must close this + * stream after use. + * + * @param pathName Path of the resource. For example, + * "/WEB-INF/classes/xys.properties". + * @return InputStream or null, if no such + * resource exists. + */ + public static InputStream getResourceStream( + String pathName) { + InputStream response = getExternalContext() + .getResourceAsStream(pathName); + return response; + } + + // === + /** + * Post an info-level message to the FacesContext where the + * <h:messages> tag can display it. + * + * @param string Message text + */ + public static void addInfoMessage(String string) { + FacesMessage message = new FacesMessage( + FacesMessage.SEVERITY_INFO, string, string); + FacesContext.getCurrentInstance().addMessage(null, + message); + } + + /** + * Post an error-level message to the FacesContext where the + * <h:messages> tag can display it. + * + * @param string Message text + */ + public static void addErrorMessage(String string) { + FacesMessage message = new FacesMessage( + FacesMessage.SEVERITY_ERROR, string, string); + FacesContext.getCurrentInstance().addMessage(null, + message); + } + + public static void addErrorMessage(String string, + Exception e) { + if (e instanceof NullPointerException) { + addErrorMessage( + "Internal logic error (NullPointerException)"); + } else { + addErrorMessage(string); + } + log.error(string, e); + } + + public static Flash flashScope() { + return (FacesContext.getCurrentInstance() + .getExternalContext().getFlash()); + } +} diff --git a/src/main/java/com/mousetech/gourmetj/UserSession.java b/src/main/java/com/mousetech/gourmetj/UserSession.java new file mode 100644 index 0000000..0b31f44 --- /dev/null +++ b/src/main/java/com/mousetech/gourmetj/UserSession.java @@ -0,0 +1,97 @@ +package com.mousetech.gourmetj; + +import java.io.Serializable; + +import javax.enterprise.context.SessionScoped; +import javax.inject.Named; + +import com.mousetech.gourmetj.persistence.model.Recipe; + +@Named +@SessionScoped +public class UserSession implements Serializable { + + /** + * Serial version for session save/restore + */ + private static final long serialVersionUID = + 7449440266704831598L; + + private String lastSearch = ""; + + /** + * @return the lastSearch + */ + public String getLastSearch() { + return lastSearch; + } + + /** + * @param lastSearch the lastSearch to set + */ + public void setLastSearch(String lastSearch) { + this.lastSearch = lastSearch; + } + + private Long lastEdit; + + /** + * @return the lastEdit + */ + public Long getLastEdit() { + return lastEdit; + } + + /** + * @param lastEdit the lastEdit to set + */ + public void setLastEdit(Long lastEdit) { + this.lastEdit = lastEdit; + } + + //*** + /** + * Tab index to select when presenting editDetails. + * First tab is 0. + */ + private int detailTab; + + /** + * @return the detailTab + */ + public int getDetailTab() { + return detailTab; + } + + /** + * @param detailTab the detailTab to set + */ + public void setDetailTab(int detailTab) { + this.detailTab = detailTab; + } + + //*** + private Recipe recipe; + + /** + * Recipe is set by the mainpage bean to a blank recipe + * before dispatching to the detailEdit page (new recipe). + * It is also set by the detail view page so that the + * detail view can be edited. + * + * In addition to detail editing, it's also used by the + * @see PictureController. + * + * @return Recipe selected. + */ + public Recipe getRecipe() { + return recipe; + } + + /** + * @param recipe the recipe to set + */ + public void setRecipe(Recipe recipe) { + this.recipe = recipe; + } +} diff --git a/src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java b/src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java new file mode 100644 index 0000000..84fe860 --- /dev/null +++ b/src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java @@ -0,0 +1,47 @@ +package com.mousetech.gourmetj.persistence.service; + +import java.io.Serializable; +import java.util.List; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.inject.Named; + +import org.springframework.transaction.annotation.Transactional; + +import com.mousetech.gourmetj.persistence.dao.CategoryRepository; +import com.mousetech.gourmetj.persistence.dao.RecipeRepository; +import com.mousetech.gourmetj.persistence.model.Category; +import com.mousetech.gourmetj.persistence.model.Recipe; + +@Named +@ApplicationScoped +@Transactional +public class RecipeService implements Serializable { + + private static final long serialVersionUID = 1L; + + @Inject + private RecipeRepository recipeRepository; + + public List findAll() { + return recipeRepository.findAll(); + } + + public List findCuisines() { + // TODO Auto-generated method stub + return null; + } + + public List findByTitle(String searchText) { + // TODO Auto-generated method stub + return null; + } + +// /** +// * +// * @return All Category names that are not null/blank, sorted. +// */ +// public List findCategoryNames() { +// return categoryRepository.findDistinctCategoryNative(); +// } +} diff --git a/src/main/resources/META-INF/resources/main.xhtml b/src/main/resources/META-INF/resources/main.xhtml new file mode 100644 index 0000000..a82927c --- /dev/null +++ b/src/main/resources/META-INF/resources/main.xhtml @@ -0,0 +1,67 @@ + + + Gourmet Recipe Manager + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/WEB-INF/layout/layout.xhtml b/src/main/resources/WEB-INF/layout/layout.xhtml new file mode 100644 index 0000000..315465c --- /dev/null +++ b/src/main/resources/WEB-INF/layout/layout.xhtml @@ -0,0 +1,28 @@ + + + + + <ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert> + + +

+ Gourmet Recipe Manager (web version) +

+ + + + + © 2021 Tim Holloway, Licensed under the Apache License, Version 2.0. +

Based on Gourmet Recipe Manager by T. Hinkle

+
+
+
+
\ No newline at end of file