package org.springframework.roo.addon.web.mvc.jsp.menu; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.springframework.roo.addon.propfiles.PropFileOperations; import org.springframework.roo.addon.web.mvc.jsp.roundtrip.XmlRoundTripFileManager; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.process.manager.FileManager; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.project.Path; import org.springframework.roo.project.PathResolver; import org.springframework.roo.project.ProjectOperations; import org.springframework.roo.support.util.FileUtils; import org.springframework.roo.support.util.XmlElementBuilder; import org.springframework.roo.support.util.XmlRoundTripUtils; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.osgi.service.component.ComponentContext; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.springframework.roo.support.logging.HandlerUtils; /** * Generates the jsp menu and allows for management of menu items. * * @author Stefan Schmidt * @since 1.0 */ @Component @Service public class MenuOperationsImpl implements MenuOperations { protected final static Logger LOGGER = HandlerUtils.getLogger(MenuOperationsImpl.class); // ------------ OSGi component attributes ---------------- private BundleContext context; private FileManager fileManager; private ProjectOperations projectOperations; private PropFileOperations propFileOperations; private XmlRoundTripFileManager xmlRoundTripFileManager; protected void activate(final ComponentContext context) { this.context = context.getBundleContext(); } public void addMenuItem(final JavaSymbolName menuCategoryName, final JavaSymbolName menuItemId, final String globalMessageCode, final String link, final String idPrefix, final LogicalPath logicalPath) { addMenuItem(menuCategoryName, menuItemId, "", globalMessageCode, link, idPrefix, false, logicalPath); } private void addMenuItem(final JavaSymbolName menuCategoryName, final JavaSymbolName menuItemId, final String menuItemLabel, final String globalMessageCode, final String link, String idPrefix, final boolean writeProps, final LogicalPath logicalPath) { Validate.notNull(menuCategoryName, "Menu category name required"); Validate.notNull(menuItemId, "Menu item name required"); Validate.notBlank(link, "Link required"); final Map<String, String> properties = new LinkedHashMap<String, String>(); if (StringUtils.isBlank(idPrefix)) { idPrefix = DEFAULT_MENU_ITEM_PREFIX; } final Document document = getMenuDocument(logicalPath); // Make the root element of the menu the one with the menu identifier // allowing for different decorations of menu Element rootElement = XmlUtils.findFirstElement("//*[@id='_menu']", document.getFirstChild()); if (rootElement == null) { final Element rootMenu = new XmlElementBuilder("menu:menu", document).addAttribute("id", "_menu").build(); rootMenu.setAttribute("z", XmlRoundTripUtils.calculateUniqueKeyFor(rootMenu)); rootElement = (Element) document.getDocumentElement().appendChild(rootMenu); } // Check for existence of menu category by looking for the identifier // provided final String lcMenuCategoryName = menuCategoryName.getSymbolName().toLowerCase(); Element category = XmlUtils.findFirstElement("//*[@id='c_" + lcMenuCategoryName + "']", rootElement); // If not exists, create new one if (category == null) { category = (Element) rootElement.appendChild(new XmlElementBuilder("menu:category", document) .addAttribute("id", "c_" + lcMenuCategoryName).build()); category.setAttribute("z", XmlRoundTripUtils.calculateUniqueKeyFor(category)); properties.put("menu_category_" + lcMenuCategoryName + "_label", menuCategoryName.getReadableSymbolName()); } // Check for existence of menu item by looking for the identifier // provided Element menuItem = XmlUtils.findFirstElement("//*[@id='" + idPrefix + lcMenuCategoryName + "_" + menuItemId.getSymbolName().toLowerCase() + "']", rootElement); if (menuItem == null) { menuItem = new XmlElementBuilder("menu:item", document) .addAttribute("id", idPrefix + lcMenuCategoryName + "_" + menuItemId.getSymbolName().toLowerCase()) .addAttribute("messageCode", globalMessageCode).addAttribute("url", link).build(); menuItem.setAttribute("z", XmlRoundTripUtils.calculateUniqueKeyFor(menuItem)); category.appendChild(menuItem); } if (writeProps) { properties.put("menu_item_" + lcMenuCategoryName + "_" + menuItemId.getSymbolName().toLowerCase() + "_label", menuItemLabel); /*getPropFileOperations().addProperties(getProjectOperations() .getPathResolver().getFocusedPath(Path.SRC_MAIN_WEBAPP), "WEB-INF/i18n/application.properties", properties, true, false);*/ } getXmlRoundTripFileManager().writeToDiskIfNecessary(getMenuFileName(logicalPath), document); } public void addMenuItem(final JavaSymbolName menuCategoryName, final JavaSymbolName menuItemId, final String menuItemLabel, final String globalMessageCode, final String link, final String idPrefix, final LogicalPath logicalPath) { addMenuItem(menuCategoryName, menuItemId, menuItemLabel, globalMessageCode, link, idPrefix, true, logicalPath); } public void cleanUpFinderMenuItems(final JavaSymbolName menuCategoryName, final List<String> allowedFinderMenuIds, final LogicalPath logicalPath) { Validate.notNull(menuCategoryName, "Menu category identifier required"); Validate.notNull(allowedFinderMenuIds, "List of allowed menu items required"); final Document document = getMenuDocument(logicalPath); // Find any menu items under this category which have an id that starts // with the menuItemIdPrefix final List<Element> elements = XmlUtils.findElements("//category[@id='c_" + menuCategoryName.getSymbolName().toLowerCase() + "']//item[starts-with(@id, '" + FINDER_MENU_ITEM_PREFIX + "')]", document.getDocumentElement()); if (elements.isEmpty()) { return; } for (final Element element : elements) { if (!allowedFinderMenuIds.contains(element.getAttribute("id")) && isNotUserManaged(element)) { element.getParentNode().removeChild(element); } } getXmlRoundTripFileManager().writeToDiskIfNecessary(getMenuFileName(logicalPath), document); } /** * Attempts to locate a menu item and remove it. * * @param menuCategoryName the identifier for the menu category (required) * @param menuItemName the menu item identifier (required) * @param idPrefix the prefix to be used for this menu item (optional, * MenuOperations.DEFAULT_MENU_ITEM_PREFIX is default) */ public void cleanUpMenuItem(final JavaSymbolName menuCategoryName, final JavaSymbolName menuItemName, String idPrefix, final LogicalPath logicalPath) { Validate.notNull(menuCategoryName, "Menu category identifier required"); Validate.notNull(menuItemName, "Menu item id required"); if (StringUtils.isBlank(idPrefix)) { idPrefix = DEFAULT_MENU_ITEM_PREFIX; } final Document document = getMenuDocument(logicalPath); // Find menu item under this category if exists final Element element = XmlUtils.findFirstElement("//category[@id='c_" + menuCategoryName.getSymbolName().toLowerCase() + "']//item[@id='" + idPrefix + menuCategoryName.getSymbolName().toLowerCase() + "_" + menuItemName.getSymbolName().toLowerCase() + "']", document.getDocumentElement()); if (element == null) { return; } if (isNotUserManaged(element)) { element.getParentNode().removeChild(element); } getXmlRoundTripFileManager().writeToDiskIfNecessary(getMenuFileName(logicalPath), document); } private Document getMenuDocument(final LogicalPath logicalPath) { try { return XmlUtils.readXml(getMenuFileInputStream(logicalPath)); } catch (final Exception e) { throw new IllegalArgumentException("Unable to parse menu.jspx" + (StringUtils.isBlank(e.getMessage()) ? "" : " (" + e.getMessage() + ")"), e); } } private InputStream getMenuFileInputStream(final LogicalPath logicalPath) { final String menuFileName = getMenuFileName(logicalPath); if (!getFileManager().exists(menuFileName)) { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = FileUtils.getInputStream(getClass(), "menu.jspx"); outputStream = getFileManager().createFile(menuFileName).getOutputStream(); IOUtils.copy(inputStream, outputStream); } catch (final IOException e) { throw new IllegalStateException( "Encountered an error during copying of menu.jspx for MVC Menu addon.", e); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } } final PathResolver pathResolver = getProjectOperations().getPathResolver(); final String menuPath = pathResolver.getIdentifier(logicalPath, "WEB-INF/tags/menu/menu.tagx"); if (!getFileManager().exists(menuPath)) { InputStream inputStream = null; try { inputStream = FileUtils.getInputStream(getClass(), "menu.tagx"); getFileManager().createOrUpdateTextFileIfRequired(menuPath, IOUtils.toString(inputStream), false); } catch (final Exception e) { throw new IllegalStateException( "Encountered an error during copying of menu.tagx for MVC Menu addon.", e); } finally { IOUtils.closeQuietly(inputStream); } } final String itemPath = pathResolver.getIdentifier(logicalPath, "WEB-INF/tags/menu/item.tagx"); if (!getFileManager().exists(itemPath)) { InputStream inputStream = null; try { inputStream = FileUtils.getInputStream(getClass(), "item.tagx"); getFileManager().createOrUpdateTextFileIfRequired(menuPath, IOUtils.toString(inputStream), false); } catch (final Exception e) { throw new IllegalStateException( "Encountered an error during copying of item.tagx for MVC Menu addon.", e); } finally { IOUtils.closeQuietly(inputStream); } } final String categoryPath = pathResolver.getIdentifier(logicalPath, "WEB-INF/tags/menu/category.tagx"); if (!getFileManager().exists(categoryPath)) { InputStream inputStream = null; try { inputStream = FileUtils.getInputStream(getClass(), "category.tagx"); getFileManager().createOrUpdateTextFileIfRequired(menuPath, IOUtils.toString(inputStream), false); } catch (final Exception e) { throw new IllegalStateException( "Encountered an error during copying of category.tagx for MVC Menu addon.", e); } finally { IOUtils.closeQuietly(inputStream); } } return getFileManager().getInputStream(menuFileName); } private String getMenuFileName(final LogicalPath logicalPath) { return getProjectOperations().getPathResolver().getIdentifier(logicalPath, "WEB-INF/views/menu.jspx"); } private boolean isNotUserManaged(final Element element) { return "?".equals(element.getAttribute("z")) || XmlRoundTripUtils.calculateUniqueKeyFor(element).equals(element.getAttribute("z")); } public FileManager getFileManager() { if (fileManager == null) { // Get all Services implement FileManager interface try { ServiceReference<?>[] references = this.context.getAllServiceReferences(FileManager.class.getName(), null); for (ServiceReference<?> ref : references) { return (FileManager) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load FileManager on MenuOperationsImpl."); return null; } } else { return fileManager; } } public ProjectOperations getProjectOperations() { if (projectOperations == null) { // Get all Services implement ProjectOperations interface try { ServiceReference<?>[] references = this.context.getAllServiceReferences(ProjectOperations.class.getName(), null); for (ServiceReference<?> ref : references) { return (ProjectOperations) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load ProjectOperations on MenuOperationsImpl."); return null; } } else { return projectOperations; } } public PropFileOperations getPropFileOperations() { if (propFileOperations == null) { // Get all Services implement PropFileOperations interface try { ServiceReference<?>[] references = this.context.getAllServiceReferences(PropFileOperations.class.getName(), null); for (ServiceReference<?> ref : references) { return (PropFileOperations) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load PropFileOperations on MenuOperationsImpl."); return null; } } else { return propFileOperations; } } public XmlRoundTripFileManager getXmlRoundTripFileManager() { if (xmlRoundTripFileManager == null) { // Get all Services implement XmlRoundTripFileManager interface try { ServiceReference<?>[] references = this.context.getAllServiceReferences(XmlRoundTripFileManager.class.getName(), null); for (ServiceReference<?> ref : references) { return (XmlRoundTripFileManager) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load XmlRoundTripFileManager on MenuOperationsImpl."); return null; } } else { return xmlRoundTripFileManager; } } }