diff --git a/src/main/java/com/mousetech/gourmetj/AdminMainBean.java b/src/main/java/com/mousetech/gourmetj/AdminMainBean.java index 51d14b4..0b8a279 100644 --- a/src/main/java/com/mousetech/gourmetj/AdminMainBean.java +++ b/src/main/java/com/mousetech/gourmetj/AdminMainBean.java @@ -3,7 +3,7 @@ package com.mousetech.gourmetj; import java.io.Serializable; import javax.annotation.PostConstruct; - +import javax.faces.event.AjaxBehaviorEvent; import javax.faces.model.DataModel; import javax.faces.model.ListDataModel; import javax.faces.view.ViewScoped; @@ -129,6 +129,15 @@ public class AdminMainBean implements Serializable { doFind(); } + + /** + * Remove images from recipe + * @param event Notused + */ + public void ajaxUpdateList(AjaxBehaviorEvent event) { + this.doFind(); + } + /** * Finder * diff --git a/src/main/java/com/mousetech/gourmetj/RecipeDetailBean.java b/src/main/java/com/mousetech/gourmetj/RecipeDetailBean.java new file mode 100644 index 0000000..00386d8 --- /dev/null +++ b/src/main/java/com/mousetech/gourmetj/RecipeDetailBean.java @@ -0,0 +1,898 @@ +package com.mousetech.gourmetj; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import javax.annotation.PostConstruct; + +import javax.faces.event.AjaxBehaviorEvent; +import javax.faces.model.DataModel; +import javax.faces.model.ListDataModel; +import javax.faces.view.ViewScoped; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.Part; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mousetech.gourmetj.IngredientUI; +import com.mousetech.gourmetj.UserSession; +import com.mousetech.gourmetj.persistence.model.Category; +import com.mousetech.gourmetj.persistence.model.Ingredient; +import com.mousetech.gourmetj.persistence.model.Recipe; +import com.mousetech.gourmetj.persistence.model.Shopcat; +import com.mousetech.gourmetj.persistence.service.RecipeService; +import com.mousetech.gourmetj.springweb.PictureController; +import com.mousetech.gourmetj.IngredientDigester; + +/** + * Backing bean for display/edit recipe detail + * + * @author timh + * @since Jun 28, 2012 TODO: Cross-reference ingredients to + * keylookup TODO: Cross-reference shopcats + */ + +@Named +@ViewScoped +public class RecipeDetailBean implements Serializable { + + private static final long serialVersionUID = 1L; + + /* Logger */ + + private static final Logger log = + LoggerFactory.getLogger(RecipeDetailBean.class); + + /** + * Default Constructor. + */ + public RecipeDetailBean() { + log.warn("Constructing RecipeDetail " + this); + } + + /** + * Persistency service for Recipes + */ + + @Inject + transient RecipeService recipeService; + + public void setRecipeService(RecipeService service) { + log.debug("INJECT RECIPESERVICE===" + service); + this.recipeService = service; + } + + private String instructions = null; + + @Inject + UserSession userSession; + + /** + * @return the userSession + */ + public UserSession getUserSession() { + return userSession; + } + + /** + * @param userSession the userSession to set + */ + public void setUserSession(UserSession userSession) { + this.userSession = userSession; + } + + /** + * @param instructions the instructions to set + * @see #getInstructions() + */ + public void setInstructions(String instructions) { + this.instructions = instructions; + } + + /** + * @param ingredients the ingredients to set + */ + public void setIngredients( + DataModel ingredients) { + this.ingredients = ingredients; + } + + // ** + private String ingredientText = ""; + + /** + * @return the ingredientText + */ + public String getIngredientText() { + return ingredientText; + } + + /** + * @param ingredientText the ingredientText to set + */ + public void setIngredientText(String ingredientText) { + this.ingredientText = ingredientText; + } + + // ================================== + + private Recipe recipe = new Recipe(); + + public Recipe getRecipe() { + if (this.recipe.getId() == null) { + Long recipeId = userSession.getLastEdit(); + if (recipeId != null) { + loadRecipe(recipeId); + } + } + return this.recipe; + } + + /** + * @param recipe the recipe to set Required by JSF 2.2, but + * DON'T USE! + */ + public void setRecipe(Recipe recipe) { + this.recipe = recipe; + } + + public String getTitle() { + return "ATITLE"; + } + + /**/ + transient DataModel ingredients; + + /**/ + private String category = ""; + + /** + * @return the category as a comma-separated list. + */ + public String getCategory() { + return category; + } + + /** + * @param category the category (list) to set + * @see #getCategory() + */ + public void setCategory(String category) { + this.category = category; + this.setDirty(); + } + + private boolean dirty; + + private String modifications; + + /** + * @return the dirty + */ + public boolean isDirty() { + return dirty; + } + + /** + * @param dirty the dirty to set + */ + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + public void setDirty() { + setDirty(true); + } + + /** + * @return the ingredients + */ + public DataModel getIngredients() { + if (ingredients == null) { + ingredients = new ListDataModel<>(); + ingredients + .setWrappedData(new ArrayList(1)); + } + return ingredients; + } + + /** + * After construction and injection, we obtain the recipe ID + * passed to us, if any and load the recipe. It's also stored + * in @see UserSession for the detail editor and + * + * @see PictureController. + */ + @PostConstruct + private void init() { + this.recipe = userSession.getRecipe(); + + /** + * For "create new, this recipe is a blank constructed + * and passed from main page. For Detail display, it's + * null and ID come in via Flash scope. For Detail Edit, + * it's passed from detail display or "create new". + */ + if (this.recipe == null) { + Long rid = + (Long) JSFUtils.flashScope().get("recipeID"); + if (rid != null) { + this.recipe = loadRecipe(rid); + } else { + // alternative (and probably dead) version of "new + // recipe". + this.recipe = new Recipe(); + return; + } + } + userSession.setRecipe(this.recipe); + + getIngredients().setWrappedData( + buildIngredientFacade(recipe.getIngredientHash())); + } + + /** + * Load recipe from database. @see RecipeDetailBean#init() + * + * @param recipeId ID of recipe to load + * @return loaded recipe or null, if not found. + */ + private Recipe loadRecipe(Long recipeId) { + Recipe recipe = recipeService.findDetails(recipeId); + if ( recipe == null ) { + return null; + } + + Set cList = recipe.getCategories(); + StringBuffer sb = new StringBuffer(35); + boolean first = true; + for (Category cat : cList) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(cat.getCategory()); + } + this.category = sb.toString(); + return recipe; + } + + /** + * Wrap a list of Ingredients in a decorative facade for + * better displayability. + * + * Ingredient group is actually part of Ingredient. Add dummy + * "ingredients" to make them separate lines. + * + * @param ingredients + * @return Wrapped list + */ + private List buildIngredientFacade( + List ingredients) { + final List list = + new ArrayList<>(ingredients.size()); + String currentIngGroup = null; + + for (Ingredient ing : ingredients) { + String ingGroup = ing.getInggroup(); + if (ingGroup != null && ingGroup.isBlank()) { + ingGroup = null; + } + if (!Objects.equals(ingGroup, currentIngGroup)) { + Ingredient dummy = new Ingredient(); + IngredientUI groupIng = new IngredientUI(dummy); + groupIng.setItem(ingGroup); + groupIng.setIngGroup(true); + list.add(groupIng); + currentIngGroup = ingGroup; + } + + IngredientUI ingUi = new IngredientUI(ing); + list.add(ingUi); + // Shopcat is an eager fetch on Ingredient + Shopcat shopCat = ing.getShopCat(); + if (shopCat != null) { + ingUi.setShopCat(shopCat.getShopcategory()); + } + } + return list; + } + + /** + * @return the instructions (cached) + */ + public String getInstructions() { + if (instructions == null) { + instructions = formatInstructions( + getRecipe().getInstructions()); + } + return instructions; + } + + /** + * @return the instructions (cached) + */ + public String getModifications() { + if (this.modifications == null) { + this.modifications = formatInstructions( + getRecipe().getModifications()); + } + return this.modifications; + } + + /** + * Convert instruction plain-text to HTML form + * + * @param instructions + * @return + */ + private String formatInstructions(String instructions) { + if (instructions == null) { + return ""; + } + String s = instructions.replace("\n\n", "

"); + s = s.replace("\n", "
"); + return s; + } + + /** + * Action for "Add Ingredient" button + * + * @return null - stay on page. The Ingredient list table + * will update. + */ + public String doAddIngredient() { + this.addIngredientList(this.getIngredientText()); + setIngredientText(""); // clear for next entry + return null; + } + + // ===== + /** + * Handle entry of a single ingredient line into the input + * form. + * + * @param event Unused??? + */ + public void ajaxAddIngredient(AjaxBehaviorEvent event) { + doAddIngredient(); + } + + public void ajaxMoveUp(AjaxBehaviorEvent event) { + if (!isMoveUpAble()) { + JSFUtils.addErrorMessage("Cannot move up."); + return; + } + + final List rows = getWrappedIngredients(); + final int ingSize = rows.size(); + for (int i = 1; i < ingSize; i++) { + IngredientUI r = rows.get(i); + if (r.isSelected()) { + // swap with preceding row. + rows.remove(i); + rows.add(i - 1, r); + } + } + this.setDirty(); + } + + /** + * Move selected rows down. + * + * @param eventUnused + */ + public void ajaxMoveDown(AjaxBehaviorEvent event) { + if (!isMoveDownAble()) { + JSFUtils.addErrorMessage("Cannot move down."); + return; + } + + final List rows = getWrappedIngredients(); + final int ingSize = rows.size() - 1; + for (int i = ingSize; i > 0; i--) { + IngredientUI r = rows.get(i - 1); + if (r.isSelected()) { + // swap with following row. + rows.remove(i - 1); + rows.add(i, r); + } + } + } + + public void ajaxDeleteItems(AjaxBehaviorEvent event) { + final List rows = getWrappedIngredients(); + List selectedRows = + new ArrayList(); + for (IngredientUI row : rows) { + if (row.isSelected()) { + this.dirty = true; + // Delete row from list. + selectedRows.add(row); + } + } + // 2nd stage to avoid ConcurrentModificationException + for (IngredientUI row : selectedRows) { + rows.remove(row); + } + } + + /** + * Bulk add for ingredients. Looks for long input and if + * found, tries to split it up and add as multiple + * ingredients. + * + * @param ingredientText2 + * @see #addIngredient(String) + */ + private void addIngredientList(String ingredientTextLines) { + if (ingredientTextLines.length() < 40) { + addIngredient(ingredientTextLines); + return; + } + + // Otherwise, try for split. + String[] lineArray = ingredientTextLines.split(" "); + for (String line : lineArray) { + if (line.isBlank()) { + continue; // actually should discard any above + // this + } + if (line.toLowerCase().contains("ingredients")) { + continue; // actually should discard any above + // this + } + addIngredient(line); + } + } + + /** + * Add ingredient text line to recipe + * + * @param ingredientText + * @see #doAddIngredient(), bulk loader + * @see #addIngredientList(String) + */ + public void addIngredient(String ingredientText) { + log.info("Ingredient line: \"" + ingredientText + "\""); + Ingredient ing = + IngredientDigester.digest(ingredientText); + // get ing list size, set ing position, append + List ingredients = getWrappedIngredients(); + int lsize = ingredients.size(); + ing.setPosition(lsize + 1); + ingredients.add(new IngredientUI(ing)); + } + + public boolean isSelectionActive() { + List rows = getWrappedIngredients(); + for (IngredientUI row : rows) { + if (row.isSelected()) { + return true; + } + } + return false; + } + + public void setSelectionActive(boolean value) { + // This is required by JBoss JSF, but the property is + // read-only. + } + + public void setMoveUpAble() { + + } + + public void setMoveDownAble() { + + } + + public boolean isMoveUpAble() { + if (isSelectionActive()) { + List rows = getWrappedIngredients(); + return !rows.get(0).isSelected(); + } + return false; + } + + public boolean isMoveDownAble() { + return true; + } + + // === + + /** + * Convenience method to get ingredientUI list without + * constant whining about unchecked casting. + * + * @return + */ + @SuppressWarnings("unchecked") + private List getWrappedIngredients() { + return (List) this.getIngredients() + .getWrappedData(); + } + + /** + * Save the recipe. + * + * @return Main page if OK, re-display with error if cannot + * save ingredients. + */ + public String doSave() { + if (!saveIngredients()) { + return null; + } + updateRecipeCategories(recipe, category); + // Rebuild ingredients list with groups applied + updateRecipeGroups(getWrappedIngredients()); + recipeService.save(recipe); + userSession.setRecipe(null); + setDirty(false); + return "main"; + } + + /** + * Delete the recipe. This will cause ingredients to be + * deleted by cascade. + * + * @return Main page + */ + public String doDelete() { + recipeService.delete(this.recipe); + this.userSession.setLastEdit(null); // Don't point to me! + return "main"; + } + + /** + * Apply ingredient group IDs (optional) to individual + * ingredients. + * + * @param wrappedIngredients The wrapped ingredient facade. + */ + private void updateRecipeGroups( + List wrappedIngredients) { + String ingGroup = null; + for (IngredientUI ingUI : wrappedIngredients) { + if (ingUI.isIngGroup()) { + ingGroup = ingUI.getItem(); + } else { + ingUI.getIngredient().setInggroup(ingGroup); + } + } + } + + /** + * Update ingredients and shopcat from UI model + * + * @return false if a category has no ingredient key to link + * it. + */ + private boolean saveIngredients() { + List saveIng = getWrappedIngredients(); + List iList = recipe.getIngredientHash(); + iList.clear(); + for (IngredientUI iui : saveIng) { + Ingredient ing = iui.getIngredient(); + ing.setRecipe(recipe); + String ingKey = iui.getIngkey(); + String shopCatName = iui.getShopCat(); + Shopcat scat = ing.getShopCat(); + if (scat == null) { + if ((ingKey != null) && !ingKey.isBlank()) { + scat = new Shopcat(); + scat.setIngkey(ingKey); + scat.setShopcategory(shopCatName); + ing.setShopCat(scat); + } else { + JSFUtils.addErrorMessage( + "Shopping Category requires an Ingredient Key"); + return false; + } + } else { + if ((ingKey == null) || ingKey.isBlank()) { + ing.setShopCat(null); + } else { + ing.getShopCat() + .setShopcategory(shopCatName); + } + } + iList.add(ing); + } + return true; + } + + /** + * Parse out the comma-separated category text control and + * post the results as children of the recipe + * + * @param recipe2 + * @param category2 + */ + private void updateRecipeCategories(Recipe recipe2, + String category2) { + final Set oldList = recipe2.getCategories(); + List newList = new ArrayList(); + String[] cats = this.category.split(","); + for (String s : cats) { + s = s.trim(); + if (!s.isEmpty()) { + Category newCat = new Category(); + newCat.setRecipe(recipe2); + newCat.setCategory(s); + newList.add(newCat); + } + } + for (Category cat : newList) { + // For existing Categories, use existing ID. + Category ocat = searchCategory(oldList, cat); + if (ocat != null) { + cat.setId(ocat.getId()); + } + } + recipe.setCategories(new HashSet(newList)); + } + + private Category searchCategory(Set oldList, + Category cat) { + String catName = cat.getCategory(); + for (Category c : oldList) { + if (catName.equals(c.getCategory())) { + return c; + } + } + return null; + } + + /** + * Partial input sent via AJAX when suggestion box is keyed. + * called each time a character is entered or removed, + * subject (I presume) to the minimum character limit. + * + * Use this to assemble the suggestion list. + */ + private String cuisinePartial; + + /** + * @return the cuisinePartial + */ + public String getCuisinePartial() { + return cuisinePartial; + } + + /** + * @param cuisinePartial the cuisinePartial to set + */ + public void setCuisinePartial(String cuisinePartial) { + this.cuisinePartial = cuisinePartial; + this.cuisineList = null; // trigger construction of new + // list. + } + + private List masterCuisineList = null; + private List cuisineList = null; + + /** + * Load the Cuisine list, which is assembled by scanning + * cuisine fields from all recipes in the database at the + * time we are called. + */ + private List loadCuisineList() { + List cuisines = recipeService.findCuisines(); + return cuisines; + } + + /** + * @return the master cuisineList + */ + public List getMasterCuisineList() { + if (this.masterCuisineList == null) { + this.masterCuisineList = loadCuisineList(); + } + return this.masterCuisineList; + } + + /** + * @return the cuisineList built by matching the master list + * agaist the partial cuisine names. + */ + public List getCuisineList() { + if (this.cuisineList == null) { + this.cuisineList = buildCuisineList(); + } + + return this.cuisineList; + } + + /** + * Using the cuisinePartial, build a subset of the master + * cuisine list. + * + * @return + */ + private List buildCuisineList() { + List list = new ArrayList(); + String partial = this.cuisinePartial; + // Handle cases where we aren't set up, or the partial + // input is blank. + if (partial == null) { + return list; + } + partial = partial.trim(); + if (partial.isEmpty()) { + return list; + } + + List masterList = this.getMasterCuisineList(); + for (String s : masterList) { + if (s.contains(this.cuisinePartial)) { + list.add(s); + } + } + return list; + } + + /** + * @param cuisineList the cuisineList to set + */ + public void setCuisineList(List cuisineList) { + this.cuisineList = cuisineList; + } + + // *** + private String shopcatPartial; + + /** + * @return the shopcatPartial + */ + public String getShopcatPartial() { + return shopcatPartial; + } + + /** + * @param shopcatPartial the shopcatPartial to set + */ + public void setShopcatPartial(String shopcatPartial) { + this.shopcatPartial = shopcatPartial; + } + + private List shopcatList; + + public List getShopcatList() { + if (shopcatList == null) { + shopcatList = recipeService.findShoppingCategories(); + } + return shopcatList; + } + + // *** + + public String editDescription() { + this.setDetailTab(0); + return "detailEdit"; + } + + public String editIngredients() { + this.setDetailTab(1); + return "detailEdit"; + } + + public String editInstructions() { + this.setDetailTab(2); + return "detailEdit"; + } + + public String editNotes() { + this.setDetailTab(3); + return "detailEdit"; + } + + private void setDetailTab(int i) { + this.userSession.setDetailTab(i); + } + + // *** + // *** Category suggestions + private String catToAdd = ""; + private List suggestCategory = null; + + /** + * @return the catToAdd + */ + public String getCatToAdd() { + return catToAdd; + } + + /** + * @param catToAdd the catToAdd to set + */ + public void setCatToAdd(String catToAdd) { + this.catToAdd = catToAdd; + } + + /** + * @return the suggestCategory + */ + public List getSuggestCategory() { + if (suggestCategory == null) { + suggestCategory = loadCategories(); + } + return suggestCategory; + } + + private List loadCategories() { + List catList = + this.recipeService.findCategories(); + return catList; + } + + /** + * @param suggestCategory the suggestCategory to set + */ + public void setSuggestCategory( + List suggestCategory) { + this.suggestCategory = suggestCategory; + } + + public void ajaxSuggestCategory(AjaxBehaviorEvent event) { + if (!this.category.isBlank()) { + this.category += ", "; + } + this.category += catToAdd; + catToAdd = ""; + } + + // *** + + Part imageFile = null; + + /** + * @return the imageFile set by the image upload control + */ + public Part getImageFile() { + return imageFile; + } + + /** + * @param imageFile the imageFile to set + */ + public void setImageFile(Part imageFile) { + this.imageFile = imageFile; + } + + /** + * Load/replace images. Computes thumbnail. + * @param event Notused + */ + public void ajaxUploadImage(AjaxBehaviorEvent event) { + // String fileType = imageFile.getContentType(); + PictureController.importImage(recipe, imageFile); + } + + /** + * Remove images from recipe + * @param event Notused + */ + public void ajaxDeleteImage(AjaxBehaviorEvent event) { + this.recipe.setImage(null); + this.recipe.setThumb(null); + } + + /** + * Return marker for image. Unlike normal JSF, I don't + * care if it gets multiple times and returns different values. + * + * @return "random" string + */ + public String getCurrentTime() { + long now = new java.util.Date().getTime(); + return String.valueOf(now); + } +} diff --git a/src/main/java/com/mousetech/gourmetj/persistence/dao/RecipeRepository.java b/src/main/java/com/mousetech/gourmetj/persistence/dao/RecipeRepository.java index 3c2aefa..f39deed 100644 --- a/src/main/java/com/mousetech/gourmetj/persistence/dao/RecipeRepository.java +++ b/src/main/java/com/mousetech/gourmetj/persistence/dao/RecipeRepository.java @@ -2,6 +2,7 @@ package com.mousetech.gourmetj.persistence.dao; import java.util.List; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @@ -25,6 +26,10 @@ public interface RecipeRepository List findByTitleContaining(String searchText); + @EntityGraph(value="Recipe.findWorkingSet") + public Recipe findDetailsById(Long recipeId); + + // final static String SQL_FIND_CATEGORIES = // "SELECT DISTINCT category from categories" // + " where category is not null and category <> ''" diff --git a/src/main/java/com/mousetech/gourmetj/persistence/model/Recipe.java b/src/main/java/com/mousetech/gourmetj/persistence/model/Recipe.java index f1b58e6..8ba3d75 100644 --- a/src/main/java/com/mousetech/gourmetj/persistence/model/Recipe.java +++ b/src/main/java/com/mousetech/gourmetj/persistence/model/Recipe.java @@ -2,7 +2,9 @@ package com.mousetech.gourmetj.persistence.model; import java.io.Serializable; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.persistence.*; @@ -15,6 +17,10 @@ import javax.persistence.*; @NamedQueries({ @NamedQuery(name = "Recipe.findAll", query = "SELECT r FROM Recipe r"), @NamedQuery(name = "Recipe.findByTitle", query = "SELECT r FROM Recipe r WHERE r.title LIKE concat('%', :searchText,'%')") }) +@NamedEntityGraph(name = "Recipe.findWorkingSet", attributeNodes = { + @NamedAttributeNode(value = "categories"), + @NamedAttributeNode(value = "ingredientHash", subgraph = "subgraph.shopcat") }, subgraphs = { + @NamedSubgraph(name = "subgraph.shopcat", attributeNodes = @NamedAttributeNode(value = "shopCat")) }) public class Recipe implements Serializable { private static final long serialVersionUID = 1L; @@ -40,7 +46,8 @@ public class Recipe implements Serializable { // Actual Ingredient_hash field is VARCHAR(32) - UUID??? @OneToMany(fetch = FetchType.LAZY, mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true) - private List ingredientHash = new ArrayList(); + private List ingredientHash = + new ArrayList(); @Column(name = "instructions") private String instructions; @@ -82,13 +89,13 @@ public class Recipe implements Serializable { private Double yields; @OneToMany(fetch = FetchType.EAGER, mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true) - private List categories = - new ArrayList(); + private Set categories = + new HashSet(); /** * @param categories the categories to set */ - public void setCategories(List categories) { + public void setCategories(Set categories) { this.categories = categories; } @@ -261,7 +268,7 @@ public class Recipe implements Serializable { return "Recipe #" + this.getId() + " " + this.getTitle(); } - public List getCategories() { + public Set getCategories() { return this.categories; } } diff --git a/src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java b/src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java index c4d73f0..d7a49df 100644 --- a/src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java +++ b/src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java @@ -7,13 +7,20 @@ import java.util.Optional; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.inject.Named; +import javax.persistence.NamedEntityGraph; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.transaction.annotation.Transactional; +import com.mousetech.gourmetj.RecipeDetailBean; 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.Ingredient; import com.mousetech.gourmetj.persistence.model.Recipe; +import com.mousetech.gourmetj.persistence.model.Shopcat; @Named @ApplicationScoped @@ -22,6 +29,9 @@ public class RecipeService implements Serializable { private static final long serialVersionUID = 1L; + private static final Logger log = + LoggerFactory.getLogger(RecipeService.class); + @Inject private RecipeRepository recipeRepository; @@ -29,17 +39,42 @@ public class RecipeService implements Serializable { return recipeRepository.findAll(); } + public List findByTitle(String searchText) { + return recipeRepository.findByTitleContaining(searchText); + } + + public Recipe findByPrimaryKey(Long recipeId) { + return recipeRepository.findById(recipeId).orElse(null); + } + + public Recipe findDetails(Long recipeId) { + return recipeRepository.findDetailsById(recipeId); + } + public List findCuisines() { // TODO Auto-generated method stub return null; } - public List findByTitle(String searchText) { - return recipeRepository.findByTitleContaining(searchText); + + public void save(Recipe recipe) { + // TODO Auto-generated method stub + } - public Recipe findByPrimaryKey(Long recipeId) { - return recipeRepository.findById(recipeId).orElse(null); + public void delete(Recipe recipe) { + // TODO Auto-generated method stub + + } + + public List findShoppingCategories() { + // TODO Auto-generated method stub + return null; + } + + public List findCategories() { + // TODO Auto-generated method stub + return null; } // /** diff --git a/src/main/resources/META-INF/resources/main.xhtml b/src/main/resources/META-INF/resources/main.xhtml index bc8c3a2..f8d5627 100644 --- a/src/main/resources/META-INF/resources/main.xhtml +++ b/src/main/resources/META-INF/resources/main.xhtml @@ -3,23 +3,23 @@ xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" - xmlns:p="http://primefaces.org/ui"> + xmlns:p="http://primefaces.org/ui" > - - Gourmet Recipe Manager - - + > + +C - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/resources/recipeDetails.xhtml b/src/main/resources/META-INF/resources/recipeDetails.xhtml new file mode 100644 index 0000000..dc5b1ec --- /dev/null +++ b/src/main/resources/META-INF/resources/recipeDetails.xhtml @@ -0,0 +1,161 @@ + + + Gourmet Recipe Manager + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/resources/recipePrint.xhtml b/src/main/resources/META-INF/resources/recipePrint.xhtml new file mode 100644 index 0000000..7b7fa35 --- /dev/null +++ b/src/main/resources/META-INF/resources/recipePrint.xhtml @@ -0,0 +1,87 @@ + + + + Gourmet Recipe Manager + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file