/* Copyright (2007-2012) Schibsted ASA * This file is part of Possom. * * Possom is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Possom 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Possom. If not, see <http://www.gnu.org/licenses/>. */ package no.sesat.search.view.navigation; import java.io.Serializable; import static no.sesat.search.site.config.AbstractDocumentFactory.*; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Iterator; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import no.sesat.search.mode.config.SearchConfiguration; import no.sesat.search.view.config.SearchTab; /** * This is a command to help generating navigation urls in the view. I got tired of all * the URL handling velocity code. * <p/> * This should be a multiResult resulthandler, but right now this just a waiting searchCommand. * Usually there will be no real waiting since the calls on the results occur from velocity. * <p/> * As a bonus from using this, you don't need to data-model the commands that only are * there for navigation. * * * @version $Id$ */ public final class NavigationConfig implements Serializable { // Constants ----------------------------------------------------- public static final String USER_SORT_KEY = SearchConfiguration.DEFAULT_USER_SORT_PARAMETER; // Attributes ---------------------------------------------------- private final Map<String, Nav> navMap = new HashMap<String, Nav>(); private final Map<String, Navigation> navigationMap = new HashMap<String, Navigation>(); private final List<Navigation> navigationList = new ArrayList<Navigation>(); // Static -------------------------------------------------------- // Constructors -------------------------------------------------- public NavigationConfig(final NavigationConfig inherit) { // inheritence first so that self-configuration can override if(null != inherit){ navMap.putAll(inherit.getNavMap()); navigationMap.putAll(inherit.getNavigationMap()); navigationList.addAll(inherit.getNavigationList()); } } // Public -------------------------------------------------------- public Map<String, Nav> getNavMap() { return Collections.unmodifiableMap(navMap); } public Map<String, Navigation> getNavigationMap() { return Collections.unmodifiableMap(navigationMap); } public List<Navigation> getNavigationList() { return Collections.unmodifiableList(navigationList); } public void addNavigation(final Navigation navigation) { if (navigation.getId() != null) { if (navigationMap.containsKey(navigation.getId())) { for (Iterator<Navigation> iterator = navigationList.iterator(); iterator.hasNext();) { final Navigation n = iterator.next(); if (navigation.getId().equals(n.getId())) { iterator.remove(); } } } navigationMap.put(navigation.getId(), navigation); } navigationList.add(navigation); } // Protected ----------------------------------------------------- // Private ------------------------------------------------------- private static List<Element> getDirectChildren(final Element element, final String elementName) { final List<Element> children = new ArrayList<Element>(); if (element != null) { final NodeList childNodes = element.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { final Node childNode = childNodes.item(i); if (childNode instanceof Element && childNode.getNodeName().equals(elementName)) { children.add((Element) childNode); } } } return children; } // Inner classes ------------------------------------------------- public static final class Navigation implements Serializable { private String id; private String commandName; private String tab; private boolean out = false; private boolean excludeQuery = false; private List<Nav> navList; private Map<String, Nav> navMap; private Set<String> resetNavSet; private String prefix; private String urlGenerator; public Navigation() { } public Navigation(final Element navigationElement) { fillBeanProperty(this, null, "id", ParseType.String, navigationElement, null); fillBeanProperty(this, null, "commandName", ParseType.String, navigationElement, null); fillBeanProperty(this, null, "tab", ParseType.String, navigationElement, null); fillBeanProperty(this, null, "out", ParseType.Boolean, navigationElement, null); fillBeanProperty(this, null, "excludeQuery", ParseType.Boolean, navigationElement, "false"); fillBeanProperty(this, null, "prefix", ParseType.Boolean, navigationElement, "/search/"); fillBeanProperty(this, null, "urlGenerator", ParseType.String, navigationElement, "no.sesat.search.view.navigation.BasicUrlGenerator"); this.navList = new ArrayList<NavigationConfig.Nav>(); this.navMap = new HashMap<String, NavigationConfig.Nav>(); this.resetNavSet = new HashSet<String>(); } public void addReset(final Nav nav) { if (nav != null) { resetNavSet.add(nav.getField()); for (Nav childNav : nav.getChildNavs()) { addReset(childNav); } } } public void addNav(final Nav nav, final NavigationConfig cfg) { navList.add(nav); updateNavMap(nav, cfg.navMap); updateNavMap(nav, navMap); } private void updateNavMap(final Nav nav, final Map<String, Nav> navMap) { navMap.put(nav.getId(), nav); for (Nav subNav : nav.getChildNavs()) { updateNavMap(subNav, navMap); } } public String getId() { return id; } public void setId(final String id) { this.id = id; } public Map<String, Nav> getNavMap() { return navMap; } public List<Nav> getNavList() { return navList; } public Set<String> getResetNavSet() { return resetNavSet; } public void setNavList(final List<Nav> navList) { this.navList = navList; } public String getTab() { return tab; } public void setTab(final String tab) { this.tab = tab; } public String getCommandName() { return commandName; } public void setCommandName(final String commandName) { this.commandName = commandName; } public boolean isOut() { return out; } public void setOut(final boolean out) { this.out = out; } public void setExcludeQuery(final boolean excludeQuery) { this.excludeQuery = excludeQuery; } public boolean isExcludeQuery() { return excludeQuery; } @Override public String toString() { return "\nNavigation{" + "id='" + id + '\'' + ",commandName='" + commandName + '\'' + ", tab='" + tab + '\'' + ", out=" + out + ", excludeQuery=" + excludeQuery + ", prefix='" + prefix + '\'' + ", urlGenerator='" + urlGenerator + '\'' + '}'; } /** * Returns the prefix to use when generating URLs. The default is <tt>"/search"</tt>. If no prefix is desired, * set the prefix to RELATIVE. * * @return the URL prefix. */ public String getPrefix() { return prefix; } /** * Sets the prefix to use when generating URLs. * * @param prefix The prefix to use. */ public void setPrefix(final String prefix) { this.prefix = prefix; } /** * The UrlGenerator to use. * * @return the url generator. * */ public String getUrlGenerator() { return urlGenerator; } /** * Sets the UrlGenerator to use. * * @param urlGenerator the url generator. */ public void setUrlGenerator(String urlGenerator) { this.urlGenerator = urlGenerator; } } @Nav.ControllerFactory("no.sesat.search.view.navigation.NoOpNavigationControllerFactoryImpl") public static class Nav implements Serializable { private static final String OPTION_ELEMENT = "option"; private static final String STATIC_PARAMETER_ELEMENT = "static-parameter"; private String id; private String commandName; private String field; private String tab; private String backText; private boolean out; private int maxsize; private Map<String,String> staticParameters = new HashMap<String,String>(); private List<Nav> childNavs; private final Navigation navigation; private final Nav parent; private boolean autoNavigation; public Nav(final Nav parent, final Navigation navigation, final Element navElement) { this.navigation = navigation; this.parent = parent; this.childNavs = new ArrayList<Nav>(1); fillBeanProperty( this, null, "commandName", ParseType.String, navElement, navigation.getCommandName()); fillBeanProperty(this, null, "id", ParseType.String, navElement, null); fillBeanProperty(this, null, "field", ParseType.String, navElement, id); fillBeanProperty(this, null, "tab", ParseType.String, navElement, navigation.getTab()); fillBeanProperty(this,null, "out",ParseType.Boolean,navElement,Boolean.toString(navigation.isOut())); fillBeanProperty(this, null, "maxsize", ParseType.Int, navElement, "100"); fillBeanProperty(this, null, "backText", ParseType.String, navElement, ""); fillBeanProperty(this, null, "autoNavigation", ParseType.Boolean, navElement, "true"); // staticParameters final List<Element> staticParamElements = getDirectChildren(navElement, STATIC_PARAMETER_ELEMENT); for (Element staticParamElement : staticParamElements) { String name = staticParamElement.getAttribute("name"); String value = staticParamElement.getAttribute("value"); if (name != null && value != null) { staticParameters.put(name, value); } } } public Nav(final Navigation navigation, final Element navElement) { this(null, navigation, navElement); } public Nav getParent() { return parent; } public Navigation getNavigation() { return navigation; } public void addChild(final Nav nav) { childNavs.add(nav); } public List<Nav> getChildNavs() { return childNavs; } public int getMaxsize() { return maxsize; } public void setMaxsize(final int maxsize) { this.maxsize = maxsize; } public Map<String, String> getStaticParameters() { return Collections.unmodifiableMap(staticParameters); } public void setStaticParameters(final Map<String, String> staticParameters) { this.staticParameters = staticParameters; } public String getId() { return id; } public void setId(final String id) { this.id = id; } public String getTab() { return tab; } public void setTab(final String tab) { this.tab = tab; // The tab property takes preference over any url parameters. intialse it here and use against urlGenerator. staticParameters.put(SearchTab.PARAMETER_KEY, tab); } public boolean isOut() { return out; } public void setOut(final boolean out) { this.out = out; } public String getCommandName() { return commandName; } public void setCommandName(final String commandName) { this.commandName = commandName; } public String getField() { return field; } public void setField(final String field) { this.field = field; } public String getBackText() { return backText; } public void setBackText(String backText) { this.backText = backText; } public boolean isVirtual() { return false; } @Override public String toString() { return "Nav{" + "id='" + id + '\'' + ", commandName='" + commandName + '\'' + ", field='" + field + '\'' + ", tab='" + tab + '\'' + ", backText='" + backText + '\'' + ", out=" + out + ", maxsize=" + maxsize + ", autoNavigation=" + autoNavigation + ", staticParameters=" + staticParameters + ", childNavs=" + (childNavs != null ? childNavs : "null") + ", parent='" + (parent != null ? parent.getId() : "null") + '\'' + ", navigation='" + (navigation != null ? navigation.getId() : "null") + '\'' + '}'; } /** Automatically select a child when only one child exists. * This repeats down to a level where the user actually has a choice of navigators to pick from. * For this to work any model level navigators must exist down each level, * eg defining fast navigators in a hierarchical way in modes.xml prevents the next level of navigators down * from being selected there autoNavigation will not work. * It is also required that the child navigation URLs can work * without the auto-navigated parameter values being explicit in the URL. * @return true if autoNavigation is enabled */ public boolean isAutoNavigation() { return autoNavigation; } public void setAutoNavigation(boolean autoNavigation) { this.autoNavigation = autoNavigation; } /** * */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Inherited public @interface ControllerFactory { /** * * @return */ public String value(); } } }