/* * gvNIX is an open source tool for rapid application development (RAD). * Copyright (C) 2010 Generalitat Valenciana * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.gvnix.web.menu.roo.addon; import java.io.ByteArrayOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; 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.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.gvnix.support.WebProjectUtils; import org.gvnix.web.menu.roo.addon.listeners.MenuDependencyListener; import org.gvnix.web.menu.roo.addon.util.FilenameUtils; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.springframework.roo.addon.propfiles.PropFileOperations; import org.springframework.roo.addon.web.mvc.jsp.i18n.I18n; import org.springframework.roo.addon.web.mvc.jsp.menu.MenuOperations; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.process.manager.FileManager; import org.springframework.roo.process.manager.MutableFile; import org.springframework.roo.project.Dependency; 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.project.Property; import org.springframework.roo.support.logging.HandlerUtils; import org.springframework.roo.support.util.FileUtils; import org.springframework.roo.support.util.XmlElementBuilder; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Implementation of operations this add-on offers * * @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a * href="http://www.dgti.gva.es">General Directorate for Information * Technologies (DGTI)</a> * @since 0.6 */ @Component // use these Apache Felix annotations to register your commands class in the Roo // container @Service public class MenuEntryOperationsImpl implements MenuEntryOperations { private static Logger logger = HandlerUtils .getLogger(MenuEntryOperationsImpl.class); private static final String ID_MENU_EXP = "//*[@id='_menu']"; private static final String GVNIX_MENU = "gvnix-menu"; private static final String INVALID_XML = "menu.xml hasn't valid XML structure."; private static final String ID_EXP = "//*[@id='"; private static final String MENU_ITEM = "menu-item"; private static final String LABEL_CODE = "labelCode"; private static final String MESSAGE_CODE = "messageCode"; private static final String URL = "url"; private static final String HIDDEN = "hidden"; private static final String NOT_FOUND = "' not found. [No changes done]"; private static final String PAGE = "Page '"; /** * Use ProjectOperations to install new dependencies, plugins, properties, * etc into the project configuration */ private ProjectOperations projectOperations; /** * Use FileManager to modify the underlying disk storage. */ @Reference private FileManager fileManager; /** * Use for property file configuration operations. */ @Reference private PropFileOperations propFileOperations; /** menu.jspx file path */ private String menuFile; /** gvnixsubcategory */ private String menuBootstrapFile; /** menu.xml file path */ private String menuConfigFile; private WebProjectUtils webProjectUtils; /** * Is OSGI Roo menu component already disabled ? In other words, is OSGI * gvNIX menu component already activated ? */ private static boolean isRooMenuDisabled = false; /** * Uses to ensure that dependencyListener will be loaded */ @Reference private MenuDependencyListener dependencyListener; private static final Logger LOGGER = HandlerUtils .getLogger(MenuEntryOperationsImpl.class); // ------------ OSGi component attributes ---------------- private BundleContext context; protected void activate(final ComponentContext context) { this.context = context.getBundleContext(); } // Public operations ----- /** * Inform if roo menu operations must be disabled * * @return */ public static boolean isRooMenuDisabled() { return isRooMenuDisabled; } /** * Set {@link #isRooMenuDisabled} to true */ private static void setRooMenuDisabled() { isRooMenuDisabled = true; } /** * {@inheritDoc} * <p> * Note the project isn't available when you start a new project (emtpy * project dir) because the project metadata doesn't exist yet. */ public boolean isProjectAvailable() { // Check if a project has been created return getProjectOperations().isProjectAvailable( getProjectOperations().getFocusedModuleName()); } /** * {@inheritDoc} * <p> * Do not permit installation unless they have a web project with Spring MVC * Tiles. */ public boolean isSpringMvcTilesProject() { return fileManager.exists(getMvcConfigFile()) && fileManager.exists(getTilesLayoutsFile()); } /** {@inheritDoc} */ public boolean isGvNixMenuAvailable() { return fileManager.exists(getMenuConfigFile()); } // Check if bootstrap is installed @Override public boolean isGvNixMenuBootstrapAvailable() { return fileManager.exists(getMenuBootstrapConfigFile()); } /** {@inheritDoc} */ public boolean isSpringSecurityInstalled() { if (!isProjectAvailable()) { // no project available yet, we cannot check for SS return false; } // create Spring Security dependency entity Dependency dep = new Dependency("org.springframework.security", "spring-security-core", "3.0.5.RELEASE"); // locate Spring Security dependency Set<Dependency> dependencies = getProjectOperations() .getFocusedModule().getDependenciesExcludingVersion(dep); // if didn't find, Spring Security is not installed if (dependencies.isEmpty()) { return false; } return true; } /** {@inheritDoc} */ public boolean isSpringSecurityInstalledOnMenuTag() { if (!isGvNixMenuAvailable()) { return false; } // Getting gvnixitem.tagx String targetPath = getPathResolver().getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/tags/menu/gvnixitem.tagx"); InputStream file = fileManager.getInputStream(targetPath); final Document tagxFile = XmlUtils.readXml(file); final Element root = tagxFile.getDocumentElement(); if (StringUtils.isNotBlank(root.getAttribute("xmlns:sec"))) { return true; } return false; } /** {@inheritDoc} */ public void setup() { // Parse the configuration.xml file Element configuration = XmlUtils.getConfiguration(getClass()); // Add POM properties updatePomProperties(configuration); // Add dependencies to POM updateDependencies(configuration); // disable Roo MenuOperations to receive requests from clients // note we disable Roo MenuOp before start reading menu.jspx to avoid // clients create page whereas we are reading menu.jspx disableRooMenuOperations(); // populate menu.xml from Roo menu.jspx createMenu(); // create project menu entity model createEntityModel("~.web.menu"); // create web layer artifacts createWebArtifacts("~.web.menu"); } /** {@inheritDoc} */ public void updateTags() { // Update web layer artfiacts updateWebArtifacts("~.web.menu"); } @Override public void setupBootstrapMenu() { if (isSpringSecurityInstalled()) { installResourceIfNeeded("/WEB-INF/tags/menu/gvnixitem.tagx", "gvnixitem-bootstrap-sec.tagx", null, new String[] { "<menu:gvnixitem", " xmlns:spring=", " xmlns:sec=" }); } else { installResourceIfNeeded("/WEB-INF/tags/menu/gvnixitem.tagx", "gvnixitem-bootstrap.tagx", null, new String[] { "<menu:gvnixitem" }); } // Adding gvnixsubcategory installResourceIfNeeded("/WEB-INF/tags/menu/gvnixsubcategory.tagx", "gvnixsubcategory.tagx", null, null); // Adding subcategory.tagx installResourceIfNeeded("/WEB-INF/tags/menu/subcategory.tagx", "subcategory.tagx", null, null); // Adding gvNIX Menu styles with Bootstrap installResourceIfNeeded("/styles/menu/dropdown-submenu.css", "dropdown-submenu.css", null, null); // Adding new css to load scripts addStylesToLoadScriptBootstrap(); } /** * Method to update bootstrap menu */ public void updateBootstrapMenu() { if (isSpringSecurityInstalled()) { updateResource("/WEB-INF/tags/menu/gvnixitem.tagx", "gvnixitem-bootstrap-sec.tagx", null, new String[] { "<menu:gvnixitem", " xmlns:spring=", " xmlns:sec=" }); } else { updateResource("/WEB-INF/tags/menu/gvnixitem.tagx", "gvnixitem-bootstrap.tagx", null, new String[] { "<menu:gvnixitem" }); } // Adding gvnixsubcategory updateResource("/WEB-INF/tags/menu/gvnixsubcategory.tagx", "gvnixsubcategory.tagx", null, null); // Adding subcategory.tagx updateResource("/WEB-INF/tags/menu/subcategory.tagx", "subcategory.tagx", null, null); // Adding gvNIX Menu styles with Bootstrap updateResource("/styles/menu/dropdown-submenu.css", "dropdown-submenu.css", null, null); // Adding new css to load scripts addStylesToLoadScriptBootstrap(); } /** {@inheritDoc} */ public void addMenuItem(JavaSymbolName menuCategoryName, JavaSymbolName menuItemId, String globalMessageCode, String link, String idPrefix) { addMenuItem(menuCategoryName, menuItemId, "", globalMessageCode, link, idPrefix, null, false, false); } /** {@inheritDoc} */ public void addMenuItem(JavaSymbolName menuCategoryName, JavaSymbolName menuItemId, String menuItemLabel, String globalMessageCode, String link, String idPrefix) { addMenuItem(menuCategoryName, menuItemId, menuItemLabel, globalMessageCode, link, idPrefix, null, false, true); } /** {@inheritDoc} */ public String addMenuItem(JavaSymbolName menuCategoryName, JavaSymbolName menuItemId, String menuItemLabel, String globalMessageCode, String link, String idPrefix, String roles, boolean hide, boolean writeProps) { Validate.notNull(menuCategoryName, "Menu category name required"); Validate.notNull(menuItemId, "Menu item name required"); // Properties to be written Map<String, String> properties = new HashMap<String, String>(); if (idPrefix == null || idPrefix.length() == 0) { idPrefix = MenuOperations.DEFAULT_MENU_ITEM_PREFIX; } Document document = getMenuDocument(); // 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_EXP, (Element) document.getFirstChild()); if (!rootElement.getNodeName().equals(GVNIX_MENU)) { throw new IllegalArgumentException(INVALID_XML); } // build category name and Id: // - menuCategoryName is a name if it doesn't start with c_: create the // id // - menuCategoryName is an identifier if it starts with c_: create the // name String categoryName = menuCategoryName.getSymbolName(); StringBuilder categoryId = new StringBuilder(); Element category = null; // check for existence of menu category by looking for the // identifier // provided // build category name and Id: // - menuCategoryName is a name if it doesn't start with c_: create // the // id // - menuCategoryName is an identifier if it starts with c_: create // the name // don't create any categoryId if there is already an id prefix if (!categoryName .startsWith(MenuEntryOperations.CATEGORY_MENU_ITEM_PREFIX) && !categoryName.startsWith(idPrefix)) { // create categoryId using the category name categoryId.append(MenuEntryOperations.CATEGORY_MENU_ITEM_PREFIX) .append(categoryName.toLowerCase()); } else { categoryId.append(categoryName.toLowerCase()); // create category name using the category Id categoryName = StringUtils.capitalize(categoryName.substring(2)); } List<Element> givenCategory = XmlUtils.findElements( ID_EXP.concat(categoryId.toString()).concat("']"), rootElement); // if given category not exists, create new one if (givenCategory.isEmpty()) { String categoryLabelCode = "menu_category_".concat( categoryName.toLowerCase()).concat("_label"); category = (Element) rootElement.appendChild(new XmlElementBuilder( MENU_ITEM, document) .addAttribute("id", categoryId.toString()) .addAttribute("name", categoryName) .addAttribute(LABEL_CODE, categoryLabelCode).build()); properties.put(categoryLabelCode, menuCategoryName.getReadableSymbolName()); } else { category = givenCategory.get(0); } // build menu item Id: // - if menu item id starts with 'i_', it is a valid ID but we remove // 'i_' for convenience // - otherwise, have to compose the ID StringBuilder itemId = new StringBuilder(); if (menuItemId.getSymbolName().toLowerCase().startsWith(idPrefix)) { itemId.append(menuItemId.getSymbolName().toLowerCase()); itemId.delete(0, idPrefix.length()); } else { itemId.append(categoryName.toLowerCase()).append("_") .append(menuItemId.getSymbolName().toLowerCase()); } // check for existence of menu item by looking for the identifier // provided // Note that in view files, menu item ID has idPrefix_, but it doesn't // have // at application.properties, so we have to add idPrefix_ to look for // the given menu item but we have to add without idPrefix_ to // application.properties List<Element> menuList = XmlUtils.findElements(ID_EXP.concat(idPrefix) .concat(itemId.toString()).concat("']"), rootElement); String itemLabelCode = "menu_item_".concat(itemId.toString()).concat( "_label"); if (menuList.isEmpty()) { Element menuItem = new XmlElementBuilder(MENU_ITEM, document) .addAttribute("id", idPrefix.concat(itemId.toString())) .addAttribute(LABEL_CODE, itemLabelCode) .addAttribute( MESSAGE_CODE, StringUtils.isNotBlank(globalMessageCode) ? globalMessageCode : "") .addAttribute(URL, StringUtils.isNotBlank(link) ? link : "") .addAttribute(HIDDEN, Boolean.toString(hide)) .addAttribute("roles", StringUtils.isNotBlank(roles) ? roles : "").build(); // TODO: gvnix*.tagx uses destination in spite of url, change to url category.appendChild(menuItem); } if (StringUtils.isNotBlank(menuItemLabel)) { properties.put(itemLabelCode, menuItemLabel); } if (writeProps) { propFileOperations.addProperties( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/i18n/application.properties", properties, true, false); } writeXMLConfigIfNeeded(document); // return the ID assigned to new entry return idPrefix.concat(itemId.toString()); } /** {@inheritDoc} */ public Document getMenuDocument() { Document menuDocument = null; // it could be menu.xml has to be installed if (!fileManager.exists(getMenuConfigFile())) { installResourceIfNeeded("/WEB-INF/views/menu.xml", "menu.xml", null, null); } InputStream menuIs = fileManager.getInputStream(getMenuConfigFile()); menuDocument = org.gvnix.web.menu.roo.addon.util.XmlUtils .parseFile(menuIs); return menuDocument; } /** * {@inheritDoc} * <p> * This method uses MutableFile in combination with FileManager to take * advantage of Roo transactional file handling which offers automatic * rollback if an exception occurs. */ public void writeXMLConfigIfNeeded(Document doc) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); XmlUtils.writeXml(XmlUtils.createIndentingTransformer(), byteArrayOutputStream, doc); // new content String proposed = byteArrayOutputStream.toString(); // If mutableFile becomes non-null, it means we need to use it to write // out the contents of jspContent to the file MutableFile mutableFile = null; if (fileManager.exists(getMenuConfigFile())) { String original = null; try { // Current content to rollback to if an exception occur original = IOUtils .toString(new FileReader(getMenuConfigFile())); } catch (Exception e) { throw new IllegalStateException( "Could not load file: ".concat(getMenuConfigFile())); } if (!proposed.equals(original)) { mutableFile = fileManager.updateFile(getMenuConfigFile()); } else { // contents are equal, nothing to do return; } } else { mutableFile = fileManager.createFile(getMenuConfigFile()); Validate.notNull(mutableFile, "Could not create file '".concat(getMenuConfigFile()) .concat("'")); } try { if (mutableFile != null) { InputStream inputStream = null; OutputStreamWriter outputStream = null; try { inputStream = IOUtils.toInputStream(proposed); outputStream = new OutputStreamWriter( mutableFile.getOutputStream()); IOUtils.copy(inputStream, outputStream); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } } } catch (IOException ioe) { throw new IllegalStateException("Could not output '".concat( mutableFile.getCanonicalPath()).concat("'"), ioe); } } /** * {@inheritDoc} * <p> * Update the entry ID could change entry type because category entry starts * with 'c_' prefix, item entry starts with 'i_' prefix, so to change a * category entry to item entry you have to set a new ID that starts with * 'i_'. */ public void updateEntry(JavaSymbolName pageId, JavaSymbolName nid, String label, String messageCode, String destination, String roles, Boolean hidden, boolean writeProps) { Document document = getMenuDocument(); // Properties to be writen Map<String, String> properties = new HashMap<String, String>(); // 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_EXP, (Element) document.getFirstChild()); if (!rootElement.getNodeName().equals(GVNIX_MENU)) { throw new IllegalArgumentException(INVALID_XML); } // check for existence of menu category by looking for the identifier // provided Element pageElement = XmlUtils .findFirstElement( ID_EXP.concat(pageId.getSymbolName()).concat("']"), rootElement); // exit if menu entry doesn't exist Validate.notNull(pageElement, "Menu entry '".concat(pageId.getSymbolName()).concat(NOT_FOUND)); if (nid != null) { pageElement.setAttribute("id", nid.getSymbolName()); // TODO: if Element has children, children IDs should be // recalculated too // TODO: label code should change too (as addMenuItem does) } if (StringUtils.isNotBlank(label)) { String itemLabelCode = pageElement.getAttribute(LABEL_CODE); properties.put(itemLabelCode, label); } if (writeProps) { propFileOperations.addProperties( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/i18n/application.properties", properties, true, true); } if (StringUtils.isNotBlank(messageCode)) { pageElement.setAttribute(MESSAGE_CODE, messageCode); } if (StringUtils.isNotBlank(destination)) { pageElement.setAttribute(URL, destination); } if (StringUtils.isNotBlank(roles)) { pageElement.setAttribute("roles", roles); } if (hidden != null) { pageElement.setAttribute(HIDDEN, hidden.toString()); } writeXMLConfigIfNeeded(document); } /** {@inheritDoc} */ public String getFormatedInfo(JavaSymbolName pageId, I18n lang) { Validate.notNull(pageId, "Menu entry ID required"); return getFormatedInfo(pageId, true, true, true, lang); } /** {@inheritDoc} */ public String getCompactInfo(JavaSymbolName pageId) { Element rootElement = getMenuRootElement(); // if no entry selected, show the info of all 1st level Elements if (pageId == null) { return getCompactInfo(rootElement.getChildNodes(), 0); } // check for existence of menu category by looking for the identifier // provided Element pageElement = XmlUtils .findFirstElement( ID_EXP.concat(pageId.getSymbolName()).concat("']"), rootElement); // if selected entry doesn't exist, error Validate.notNull(pageElement, PAGE.concat(pageId.getSymbolName()) .concat("' not found [No info found]")); // show the info of selected menu entry return getCompactInfo(pageElement, 0); } /** * Iterates over a list of menu entry Nodes and call * {@link #getCompactInfo(Element, int)} to get the info of all the menu * entry Nodes in the given list. * * @param nodes * @param tabSize * @return */ private String getCompactInfo(NodeList nodes, int tabSize) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); // filter nodes that aren't menuItems if (node.getNodeType() != Node.ELEMENT_NODE) { continue; } String nodeName = node.getNodeName(); if (!nodeName.equals(MENU_ITEM)) { continue; } builder.append(getCompactInfo((Element) node, tabSize)); } return builder.toString(); } /** * Shows menu info in compact mode: <br/> * <code> *   /tribunales [c_tribunales, visible]<br/> *     /tribunales?form [i_tribunales_new, visible]<br/> *     /tribunales?page=1&size=${empty param.size ? 10 : param.size} [i_tribunales_list, hidden]<br/> * </code> * * @param element * @param tabSize * @return */ private String getCompactInfo(Element element, int tabSize) { StringBuilder builder = new StringBuilder(); StringBuilder indent = new StringBuilder(); // tab string to align children for (int i = 0; i < tabSize; i++) { indent.append(" "); } String url = element.getAttribute(URL); if (StringUtils.isNotBlank(url)) { builder.append(indent).append(url).append(" "); } // string containing "[ID, visibility]: " StringBuilder idVisibility = new StringBuilder(); idVisibility.append("[").append(element.getAttribute("id")); String hidden = element.getAttribute(HIDDEN); if (!StringUtils.isNotBlank(hidden)) { hidden = "false"; // visible by default } if (Boolean.valueOf(hidden)) { idVisibility.append(", hidden"); } else { idVisibility.append(", visible"); } if (!StringUtils.isNotBlank(url)) { idVisibility.append(", no-URL"); } idVisibility.append("]"); // build Element info builder.append(idVisibility); // get children info if (element.hasChildNodes()) { builder.append("\n") .append(getCompactInfo(element.getChildNodes(), tabSize + 10)).append("\n"); } else { builder.append("\n"); // empty line } return builder.toString(); } /** {@inheritDoc} */ public String getFormatedInfo(JavaSymbolName pageId, boolean label, boolean messageCode, boolean roles, I18n lang) { Element rootElement = getMenuRootElement(); // if no entry selected, show the info of all 1st level Elements if (pageId == null) { return getFormatedInfo(rootElement.getChildNodes(), label, messageCode, roles, lang, 0); } // check for existence of menu category by looking for the identifier // provided Element pageElement = XmlUtils .findFirstElement( ID_EXP.concat(pageId.getSymbolName()).concat("']"), rootElement); // if selected entry doesn't exist, error Validate.notNull(pageElement, PAGE.concat(pageId.getSymbolName()) .concat("' not found [No info found]")); // show the info of selected menu entry return getFormatedInfo(pageElement, label, messageCode, roles, lang, 0); } /** * Returns the Root element of the menu.xml file * * @return */ private Element getMenuRootElement() { Document document = getMenuDocument(); // 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_EXP, (Element) document.getFirstChild()); if (!rootElement.getNodeName().equals(GVNIX_MENU)) { throw new IllegalArgumentException(INVALID_XML); } return rootElement; } /** {@inheritDoc} */ public void moveBefore(JavaSymbolName pageId, JavaSymbolName beforeId) { Document document = getMenuDocument(); // 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_EXP, (Element) document.getFirstChild()); if (!rootElement.getNodeName().equals(GVNIX_MENU)) { throw new IllegalArgumentException(INVALID_XML); } // check for existence of menu category by looking for the identifier // provided Element pageElement = XmlUtils .findFirstElement( ID_EXP.concat(pageId.getSymbolName()).concat("']"), rootElement); // exit if menu entry doesn't exist Validate.notNull(pageElement, PAGE.concat(pageId.getSymbolName()) .concat(NOT_FOUND)); Element beforeElement = XmlUtils.findFirstElement( ID_EXP.concat(beforeId.getSymbolName()).concat("']"), rootElement); // exit if menu entry doesn't exist Validate.notNull(beforeElement, PAGE.concat(beforeId.getSymbolName()) .concat(NOT_FOUND)); // page parent element where remove menu entry element Element pageParentEl = (Element) pageElement.getParentNode(); pageParentEl.removeChild(pageElement); // before parent element where execute insert before Element beforeParentEl = (Element) beforeElement.getParentNode(); beforeParentEl.insertBefore(pageElement, beforeElement); writeXMLConfigIfNeeded(document); } /** {@inheritDoc} */ public void moveInto(JavaSymbolName pageId, JavaSymbolName intoId) { Document document = getMenuDocument(); // 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_EXP, (Element) document.getFirstChild()); if (!rootElement.getNodeName().equals(GVNIX_MENU)) { throw new IllegalArgumentException(INVALID_XML); } // check for existence of menu category by looking for the identifier // provided Element pageElement = XmlUtils .findFirstElement( ID_EXP.concat(pageId.getSymbolName()).concat("']"), rootElement); // exit if menu entry doesn't exist Validate.notNull(pageElement, PAGE.concat(pageId.getSymbolName()) .concat(NOT_FOUND)); Element intoElement = XmlUtils .findFirstElement( ID_EXP.concat(intoId.getSymbolName()).concat("']"), rootElement); // exit if menu entry doesn't exist Validate.notNull(intoElement, PAGE.concat(intoId.getSymbolName()) .concat(NOT_FOUND)); // parent element where remove menu entry element Element parent = (Element) pageElement.getParentNode(); parent.removeChild(pageElement); // insert intoElement.appendChild(pageElement); writeXMLConfigIfNeeded(document); } /** {@inheritDoc} */ public String getMenuConfigFile() { // resolve path for menu.xml if it hasn't been resolved yet if (menuConfigFile == null) { menuConfigFile = getPathResolver().getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/views/menu.xml"); } return menuConfigFile; } /** {@inheritDoc} */ public String getMenuBootstrapConfigFile() { // resolve path for gvnixsubcategory.tagx if it hasn't been resolved yet if (menuBootstrapFile == null) { menuBootstrapFile = getPathResolver().getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/tags/menu/gvnixsubcategory.tagx"); } return menuBootstrapFile; } /** * Get and initialize the absolute path for the {@code menu.jspx}. * * @return the absolute path to the file (never null) */ public String getMenuFile() { // resolve absolute path for menu.jspx if it hasn't been resolved yet if (menuFile == null) { menuFile = getPathResolver().getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/views/menu.jspx"); } return menuFile; } // Private operations and utils ----- /** * Utility to get {@link PathResolver}. * * @return PathResolver or null if project isn't available yet */ private PathResolver getPathResolver() { // Use PathResolver to resolve between {@link File}, {@link Path} and // canonical path {@link String}s. // See {@link MavenPathResolver} to know location values PathResolver pathResolver = getProjectOperations().getPathResolver(); return pathResolver; } /** * Create Menu model that lets the project works with the underlying menu * structure representation, i.e, menu.xml * * @param targetPackage Java entities will be created inside this package */ protected void createEntityModel(String targetPackage) { installIfNeeded("MenuItem.java", targetPackage); installIfNeeded("Menu.java", targetPackage); installIfNeeded("MenuLoader.java", targetPackage); installIfNeeded("ContextMenuStrategy.java", targetPackage); installIfNeeded("BaseURLContextMenuStrategy.java", targetPackage); installIfNeeded("URLBrothersContextMenuStrategy.java", targetPackage); installIfNeeded("URLChildrenContextMenuStrategy.java", targetPackage); } /** {@inheritDoc} */ public void createWebArtifacts(String classesPackage) { // parameters Map to replace variables in file templates Map<String, String> params = new HashMap<String, String>(); // resolve given classes package String javaPackage = getFullyQualifiedPackageName(classesPackage); // Put variable values in parameters Map params.put( "__TOP_LEVEL_PACKAGE__", getProjectOperations().getTopLevelPackage( getProjectOperations().getFocusedModuleName()) .getFullyQualifiedPackageName()); params.put("__MENU_MODEL_CLASS__", javaPackage.concat(".Menu")); // install tags installResourceIfNeeded("/WEB-INF/tags/menu/gvnixmenu.tagx", "gvnixmenu.tagx", params, new String[] { "<menu:gvnixitem" }); // Check if bootstrap is installed if (getProjectOperations().isFeatureInstalledInFocusedModule( "gvnix-bootstrap")) { if (!isGvNixMenuBootstrapAvailable()) { // Installing gvnixitem and gvnixitem-sec with Bootstrap setupBootstrapMenu(); } } else { // Installing gvnixitem and gvnixitem-sec without Bootstrap if (isSpringSecurityInstalled()) { installResourceIfNeeded("/WEB-INF/tags/menu/gvnixitem.tagx", "gvnixitem-sec.tagx", null, new String[] { "<menu:gvnixitem", " xmlns:spring=", " xmlns:sec=" }); } else { installResourceIfNeeded("/WEB-INF/tags/menu/gvnixitem.tagx", "gvnixitem.tagx", null, new String[] { "<menu:gvnixitem" }); } } // change menu.jspx to use gvnix menu tag that will render menu.xml installResourceIfNeeded("/WEB-INF/views/menu.jspx", "menu.jspx", null, new String[] { "menu:gvnixmenu" }); } /** {@inheritDoc} */ public void updateWebArtifacts(String classesPackage) { // parameters Map to replace variables in file templates Map<String, String> params = new HashMap<String, String>(); // resolve given classes package String javaPackage = getFullyQualifiedPackageName(classesPackage); // Put variable values in parameters Map params.put( "__TOP_LEVEL_PACKAGE__", getProjectOperations().getTopLevelPackage( getProjectOperations().getFocusedModuleName()) .getFullyQualifiedPackageName()); params.put("__MENU_MODEL_CLASS__", javaPackage.concat(".Menu")); // update tags updateResource("/WEB-INF/tags/menu/gvnixmenu.tagx", "gvnixmenu.tagx", params, new String[] { "<menu:gvnixitem" }); // Check if bootstrap is installed if (getProjectOperations().isFeatureInstalledInFocusedModule( "gvnix-bootstrap")) { // Updating gvnixitem and gvnixitem-sec with Bootstrap updateBootstrapMenu(); } else { // Installing gvnixitem and gvnixitem-sec without Bootstrap if (isSpringSecurityInstalled()) { updateResource("/WEB-INF/tags/menu/gvnixitem.tagx", "gvnixitem-sec.tagx", null, new String[] { "<menu:gvnixitem", " xmlns:spring=", " xmlns:sec=" }); } else { updateResource("/WEB-INF/tags/menu/gvnixitem.tagx", "gvnixitem.tagx", null, new String[] { "<menu:gvnixitem" }); } } // change menu.jspx to use gvnix menu tag that will render menu.xml updateResource("/WEB-INF/views/menu.jspx", "menu.jspx", null, new String[] { "menu:gvnixmenu" }); } /** * Install a file template if it doesn't exist * <p> * This method has been copied from Maven addon, maybe it could be * refactored to utility class. * * @param targetFilename File to create. Note this method will create the * file by locating a file template with the same name as target file * but prefixing "-template" to file extension. For example, given * {@code Menu.java} will locate {@code Menu-template.java} */ private void installIfNeeded(String targetFilename, String targetPackage) { String destinationFile = getAbsolutePath(targetPackage, targetFilename); if (!fileManager.exists(destinationFile)) { try { // Read template and insert the user's package String fileName = FilenameUtils.removeExtension(targetFilename); String fileExt = FilenameUtils.getExtension(targetFilename); InputStream templateInputStream = FileUtils.getInputStream( getClass(), fileName.concat("-template").concat(".") .concat(fileExt)); String input = IOUtils.toString(new InputStreamReader( templateInputStream)); // replace template variables input = input.replace( "__TOP_LEVEL_PACKAGE__", getProjectOperations().getTopLevelPackage( getProjectOperations().getFocusedModuleName()) .getFullyQualifiedPackageName()); // Output the file for the user // use MutableFile in combination with FileManager to take // advantage of // Roos transactional file handling which offers automatic // rollback if an // exception occurs MutableFile mutableFile = fileManager .createFile(destinationFile); InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = IOUtils.toInputStream(input); outputStream = mutableFile.getOutputStream(); IOUtils.copy(inputStream, outputStream); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } } catch (IOException ioe) { throw new IllegalStateException("Unable to create '".concat( targetFilename).concat("'"), ioe); } } } /** * Creates or updates the contents for one resource represented by the given * target file path and relative source path. * * @param relativePath path relative to {@link Path.SRC_MAIN_WEBAPP} of * target file * @param resourceName path relative to classpath of file to be copied * (cannot be null) * @param toReplace * @param containsStrings */ private void installResourceIfNeeded(String relativePath, String resourceName, Map<String, String> toReplace, String[] containsStrings) { PathResolver pathResolver = getPathResolver(); String targetPath = pathResolver .getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), relativePath); InputStream resource = getClass().getResourceAsStream(resourceName); String sourceContents; String targetContents = null; // load resource to copy try { sourceContents = IOUtils.toString(new InputStreamReader(resource)); // Replace params if (toReplace != null) { for (Entry<String, String> entry : toReplace.entrySet()) { sourceContents = StringUtils.replace(sourceContents, entry.getKey(), entry.getValue()); } } } catch (IOException e) { throw new IllegalStateException( "Unable to load file to be copied '".concat(resourceName) .concat("'"), e); } finally { try { resource.close(); } catch (IOException e) { e.printStackTrace(); } } // load target contents if exists if (fileManager.exists(targetPath)) { FileReader reader = null; try { reader = new FileReader(targetPath); targetContents = IOUtils.toString(reader); } catch (Exception e) { throw new IllegalStateException("Error reading '".concat( targetPath).concat("'"), e); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { e.printStackTrace(); } } } // prepare mutable file // use MutableFile in combination with FileManager to take advantage of // Roos transactional file handling which offers automatic rollback if // an // exception occurs MutableFile target = null; if (targetContents == null) { target = fileManager.createFile(targetPath); } else { // decide if need to replace target if (containsStrings == null || containsStrings.length == 0) { // No checks to do target = fileManager.updateFile(targetPath); } else { for (String contains : containsStrings) { if (!targetContents.contains(contains)) { target = fileManager.updateFile(targetPath); break; } } } } if (target == null) { return; } try { InputStream inputStream = null; OutputStreamWriter outputStream = null; try { inputStream = IOUtils.toInputStream(sourceContents); outputStream = new OutputStreamWriter(target.getOutputStream()); IOUtils.copy(inputStream, outputStream); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } } catch (IOException e) { throw new IllegalStateException("Unable to create/update '".concat( targetPath).concat("'"), e); } } /** * Updates the contents for one resource represented by the given target * file path and relative source path. * * @param relativePath path relative to {@link Path.SRC_MAIN_WEBAPP} of * target file * @param resourceName path relative to classpath of file to be copied * (cannot be null) * @param toReplace * @param containsStrings */ private void updateResource(String relativePath, String resourceName, Map<String, String> toReplace, String[] containsStrings) { PathResolver pathResolver = getPathResolver(); String targetPath = pathResolver .getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), relativePath); InputStream resource = getClass().getResourceAsStream(resourceName); String sourceContents; String targetContents = null; // load resource to copy try { sourceContents = IOUtils.toString(new InputStreamReader(resource)); // Replace params if (toReplace != null) { for (Entry<String, String> entry : toReplace.entrySet()) { sourceContents = StringUtils.replace(sourceContents, entry.getKey(), entry.getValue()); } } } catch (IOException e) { throw new IllegalStateException( "Unable to load file to be copied '".concat(resourceName) .concat("'"), e); } finally { try { resource.close(); } catch (IOException e) { e.printStackTrace(); } } // load target contents if exists if (fileManager.exists(targetPath)) { FileReader reader = null; try { reader = new FileReader(targetPath); targetContents = IOUtils.toString(reader); } catch (Exception e) { throw new IllegalStateException("Error reading '".concat( targetPath).concat("'"), e); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { e.printStackTrace(); } } } // prepare mutable file // use MutableFile in combination with FileManager to take advantage of // Roos transactional file handling which offers automatic rollback if // an // exception occurs MutableFile target = fileManager.updateFile(targetPath); if (target == null) { return; } try { InputStream inputStream = null; OutputStreamWriter outputStream = null; try { inputStream = IOUtils.toInputStream(sourceContents); outputStream = new OutputStreamWriter(target.getOutputStream()); IOUtils.copy(inputStream, outputStream); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } } catch (IOException e) { throw new IllegalStateException("Unable to create/update '".concat( targetPath).concat("'"), e); } } /** * Get the absolute path to a file name in given package name. * * @param packageName fully qualified package name * @param fileName file to get its absolute path * @return Path.SRC_MAIN_JAVA + packagePath + fileName */ private String getAbsolutePath(String packageName, String fileName) { String fullyQualifPackage = getFullyQualifiedPackageName(packageName); // default package String packagePath = fullyQualifPackage.replace('.', '/'); return getProjectOperations().getPathResolver().getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""), packagePath.concat("/").concat(fileName)); } /** * Convert a package name to a fully qualified package name with full * support for using "~" as denoting the user's top-level package. * * @param packageName * @param prjMetadata * @return */ private String getFullyQualifiedPackageName(String packageName) { if (packageName == null || "".equals(packageName)) { return ""; } // by default return the given packageName String newPackage = packageName.toLowerCase(); // resolve "~" as denoting the user's top-level package if (packageName.startsWith("~")) { String topLevelPath = ""; topLevelPath = getProjectOperations().getTopLevelPackage( getProjectOperations().getFocusedModuleName()) .getFullyQualifiedPackageName(); // analyze char after ~, if it is . do nothing, else concat . // between // topLevelPath and given package name and remove ~ at start if (packageName.length() > 1) { newPackage = (!(packageName.charAt(1) == '.') ? topLevelPath .concat(".") : topLevelPath).concat(packageName .substring(1)); } // when given packageName is ~ else { newPackage = topLevelPath; } } // normalize if (newPackage.endsWith(".")) { newPackage = newPackage.substring(0, newPackage.length() - 1); } return newPackage; } /** * Create menu.xml from menu.jspx. * <p> * Iterates over menu.jspx elements and delegates menu.xml creation to * {@link #addMenuItem(JavaSymbolName, JavaSymbolName, String, String, String, String)} * , the same method used to create menu entries from shell. */ private void createMenu() { InputStream rooMenuInput = fileManager.getInputStream(getMenuFile()); try { Document rooMenuDoc = XmlUtils.getDocumentBuilder().parse( rooMenuInput); Element root = rooMenuDoc.getDocumentElement(); Element menu = XmlUtils.findFirstElement("/div/menu", root); if (menu == null) { // Roo menu exists but is still empty: there is no menu element // into div element already // Avoid error when execute "web mvc setup" and next // "menu setup" return; } // root categories and items NodeList menuElements = menu.getChildNodes(); for (int i = 0; i < menuElements.getLength(); i++) { Node tmpNode = menuElements.item(i); if (tmpNode.getNodeType() != Node.ELEMENT_NODE) { continue; } String nodeName = tmpNode.getNodeName(); // process root category elements if (nodeName.equals("menu:category")) { Element menuCategory = (Element) tmpNode; // We have to recover original categoryName to success // addMenuItem // execution JavaSymbolName categoryId = new JavaSymbolName( menuCategory.getAttribute("id")); JavaSymbolName categoryName = new JavaSymbolName( StringUtils.capitalize(categoryId.getSymbolName() .substring(2))); NodeList menuItems = menuCategory.getChildNodes(); // process category inner item elements for (int j = 0; j < menuItems.getLength(); j++) { tmpNode = menuItems.item(j); if (tmpNode.getNodeType() != Node.ELEMENT_NODE) { continue; } Element menuItem = (Element) tmpNode; // - At menu.jspx there isn't label, no prob because it // was added to // application.properties JavaSymbolName menuItemId = new JavaSymbolName( menuItem.getAttribute("id")); String menuItemPrefix = getMenuItemPrefix(menuItemId .getSymbolName()); addMenuItem(categoryName, menuItemId, null, menuItem.getAttribute(MESSAGE_CODE), menuItem.getAttribute(URL), menuItemPrefix); } } // item elements must be inside a category else if (nodeName.equals("menu:item")) { Element menuItem = (Element) tmpNode; JavaSymbolName menuItemId = new JavaSymbolName( menuItem.getAttribute("id")); logger.log( Level.SEVERE, "Found menu:item '" .concat(menuItemId.getSymbolName()) .concat("' in menu:menu tag. It must be in " .concat("menu:category tag [Ignored]"))); } else { logger.warning("Found unknow element in menu:menu tag: '" .concat(nodeName).concat("' [Ignored]")); } } } catch (Exception ex) { throw new IllegalStateException(ex); } } /** * {@inheritDoc} */ public void disableRooMenuOperations() { logger.fine("Disable Roo MenuOperationsImpl"); setRooMenuDisabled(); } /** * Iterates over a list of menu entry Nodes and call * {@link #getFormatedInfo(Element, boolean, boolean, boolean, boolean, int)} * to get the info of all the menu entry Nodes in the given list. * * @param nodes * @param label * @param messageCode * @param roles * @param tabSize * @return */ private String getFormatedInfo(NodeList nodes, boolean label, boolean messageCode, boolean roles, I18n lang, int tabSize) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); // filter nodes that aren't menuItems if (node.getNodeType() != Node.ELEMENT_NODE) { continue; } String nodeName = node.getNodeName(); if (!nodeName.equals(MENU_ITEM)) { continue; } builder.append(getFormatedInfo((Element) node, label, messageCode, roles, lang, tabSize)); } return builder.toString(); } /** * Gets the selected info about the given menu entry Element. * <p> * TODO: I think compact info better. See section "List menu structure" at * "docs/pd-addon-web-menu.rst". Note, it means we should refactor this * method in 2 methods: on for list command and other for info command * * @param element * @param label * @param messageCode * @param destination * @param roles * @param tabSize * @return */ private String getFormatedInfo(Element element, boolean label, boolean message, boolean roles, I18n lang, int tabSize) { StringBuilder builder = new StringBuilder(); StringBuilder indent = new StringBuilder(); // tab string to align children for (int i = 0; i < tabSize; i++) { indent.append(" "); } // string containing "[ID]: " StringBuilder idInfo = new StringBuilder(); idInfo.append("[").append(element.getAttribute("id")).append("]"); // build Element info builder.append(indent).append(idInfo).append("\n"); String url = element.getAttribute(URL); if (!StringUtils.isNotBlank(url)) { url = "No"; } builder.append(indent).append("URL : ").append(url) .append("\n"); if (label) { String labelCode = element.getAttribute(LABEL_CODE); String labelValue = null; // get label text from application.properties if (StringUtils.isNotBlank(labelCode)) { labelValue = propFileOperations.getProperty( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/i18n/application.properties", labelCode); } builder.append(indent).append("Label Code : ").append(labelCode) .append("\n"); builder.append(indent).append("Label : ") .append(labelValue != null ? labelValue : "").append("\n"); } if (message) { String messageCode = element.getAttribute(MESSAGE_CODE); String messageValue = null; // get message text from messages.properties if (StringUtils.isNotBlank(messageCode)) { if (lang != null) { String messageBundle = "/WEB-INF/i18n/messages_".concat( lang.getLocale().toString()).concat(".properties"); messageValue = propFileOperations.getProperty( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), messageBundle, messageCode); } // if no value for given lang, try default lang if (!StringUtils.isNotBlank(messageValue)) { String messageBundle = "/WEB-INF/i18n/messages.properties"; messageValue = propFileOperations.getProperty( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), messageBundle, messageCode); } } builder.append(indent).append("Message Code : ") .append(element.getAttribute(MESSAGE_CODE)).append("\n"); builder.append(indent).append("Message : ") .append(messageValue != null ? messageValue : "") .append("\n"); } if (roles) { builder.append(indent).append("Roles : ") .append(element.getAttribute("roles")).append("\n"); } String hidden = element.getAttribute(HIDDEN); if (!StringUtils.isNotBlank(hidden)) { hidden = "false"; // visible by default } builder.append(indent).append("Hidden : ").append(hidden) .append("\n"); // get children info if (element.hasChildNodes()) { builder.append(indent).append("Children : ").append("\n"); builder.append(getFormatedInfo(element.getChildNodes(), label, message, roles, lang, tabSize + 15)); // indent // to // the // right // of // "Children // : // " // (length // 15 // chars) } else { builder.append("\n"); // empty line } return builder.toString(); } /** * Install properties defined in external XML file * * @param configuration */ private void updatePomProperties(Element configuration) { List<Element> addonProperties = XmlUtils.findElements( "/configuration/gvnix/menu/properties/*", configuration); for (Element property : addonProperties) { getProjectOperations().addProperty( getProjectOperations().getFocusedModuleName(), new Property(property)); } } /** * Install dependencies defined in external XML file * * @param configuration */ private void updateDependencies(Element configuration) { List<Dependency> dependencies = new ArrayList<Dependency>(); List<Element> securityDependencies = XmlUtils.findElements( "/configuration/gvnix/menu/dependencies/dependency", configuration); for (Element dependencyElement : securityDependencies) { dependencies.add(new Dependency(dependencyElement)); } getProjectOperations().addDependencies( getProjectOperations().getFocusedModuleName(), dependencies); } /** * Gets menu item prefix: {@link MenuOperations#DEFAULT_MENU_ITEM_PREFIX} or * {@link MenuOperations#FINDER_MENU_ITEM_PREFIX} * * @param itemId * @return MenuOperations.FINDER_MENU_ITEM_PREFIX if given itemId starts * with "fi_", otherwise return * MenuOperations.DEFAULT_MENU_ITEM_PREFIX */ private String getMenuItemPrefix(String itemId) { if (itemId.startsWith(MenuOperations.FINDER_MENU_ITEM_PREFIX)) { return MenuOperations.FINDER_MENU_ITEM_PREFIX; } else if (itemId.startsWith("si_")) { return "si_"; } return MenuOperations.DEFAULT_MENU_ITEM_PREFIX; } /** * Get the absolute path for {@code webmvc-config.xml}. * * @return the absolute path to file (never null) */ private String getMvcConfigFile() { // resolve absolute path for menu.jspx if it hasn't been resolved yet return getPathResolver().getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/spring/webmvc-config.xml"); } /** * Get the absolute path for {@code layouts.xml}. * <p> * Note that this file is required for any Tiles project. * * @return the absolute path to file (never null) */ private String getTilesLayoutsFile() { // resolve absolute path for menu.jspx if it hasn't been resolved yet return getPathResolver().getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/layouts/layouts.xml"); } /** * Method to add menu styles to load-scripts-bootstrap.tagx */ private void addStylesToLoadScriptBootstrap() { List<Pair<String, String>> cssList = new ArrayList<Pair<String, String>>(); List<Pair<String, String>> jsList = new ArrayList<Pair<String, String>>(); // Add jquery.datatables.css url resolution cssList.add(new ImmutablePair<String, String>("css_bootstrap_menu_url", "/resources/styles/menu/dropdown-submenu.css")); getWebProjectUtils().addJsAndCssToLoadScriptsTag(cssList, jsList, getProjectOperations(), 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) { projectOperations = (ProjectOperations) this.context .getService(ref); return projectOperations; } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load ProjectOperations on MenuEntryOperationsImpl."); return null; } } else { return projectOperations; } } public WebProjectUtils getWebProjectUtils() { if (webProjectUtils == null) { // Get all Services implement WebProjectUtils interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( WebProjectUtils.class.getName(), null); for (ServiceReference<?> ref : references) { webProjectUtils = (WebProjectUtils) this.context .getService(ref); return webProjectUtils; } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load WebProjectUtils on MenuEntryOperationsImpl."); return null; } } else { return webProjectUtils; } } }