/* * * * Copyright (c) 2016. David Sowerby * * * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * * specific language governing permissions and limitations under the License. * */ package uk.q3c.krail.core.navigate.sitemap; import com.google.common.collect.ImmutableList; import uk.q3c.krail.core.i18n.I18NKey; import uk.q3c.krail.core.navigate.Navigator; import uk.q3c.krail.core.shiro.PageAccessControl; import uk.q3c.krail.core.view.KrailView; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import java.util.ArrayList; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; /** * Represents a node in the site map (equivalent to a web site 'page'). At a minimum, it contains a URI segment and an id generated by the {@link * MasterSitemap}. * <p/> * The uri segment this is just one part of the URI, so the node for the page at /private/account/open would contain just 'open'). To obtain the full URI, * use {@link MasterSitemap#uri(SitemapNode)}. * <p/> * {@link #viewClass} is the class of {@link KrailView} to be used in displaying the page, and the {@link #getLabelKey()} is an {@link I18NKey} key used to * provide a localised label for the page * <p/> * The {@link #id} is required because the URI segment alone may not be unique, and the other elements are optional. For the node to be used in a graph, it * needs a unique identifier. The id is provided by {@link MasterSitemap#addChild(SitemapNode, SitemapNode)} and {@link MasterSitemap#addNode(SitemapNode)}. * This field has an additional purpose in providing a record of insertion order, so that nodes can be sorted by insertion order if required - although this * is not a particularly reliable method of determining order, so it is recommended to use the positionIndex if you want to determine position by index * rather than by alphabetic sorting * <p/> * The type of user access control applied to the page is determined by {@link #pageAccessControl}. Note that permissions and roles are mutually exclusive, * so a page cannot require both roles and permissions. This control is applied by the {@link Navigator} during page changes, thereby disallowing access to * an unauthorised page. * * @author David Sowerby 6 May 2013 */ @Immutable public class MasterSitemapNode implements SitemapNode { private final int id; private final String uriSegment; private final I18NKey labelKey; private final PageAccessControl pageAccessControl; private final int positionIndex; /** * Contains roles required to access this page, but is not used unless {@link #pageAccessControl} is * {@link PageAccessControl#ROLES} */ private final ImmutableList<String> roles; private final Class<? extends KrailView> viewClass; public MasterSitemapNode(int id, @Nonnull String uriSegment) { checkNotNull(uriSegment); this.id = id; this.uriSegment = uriSegment; labelKey = null; positionIndex = -1; pageAccessControl = PageAccessControl.PERMISSION; this.roles = ImmutableList.copyOf(new ArrayList<>()); this.viewClass = null; } public MasterSitemapNode(int id, @Nonnull NodeRecord nodeRecord) { this(id, nodeRecord.getUriSegment(), nodeRecord.getViewClass(), nodeRecord.getLabelKey(), nodeRecord.getPositionIndex(), nodeRecord .getPageAccessControl(), nodeRecord.getRoles()); } public MasterSitemapNode(int id, @Nonnull String uriSegment, @Nullable Class<? extends KrailView> viewClass, @Nullable I18NKey labelKey, int positionIndex, @Nullable PageAccessControl pageAccessControl, @Nullable List<String> roles) { super(); checkNotNull(uriSegment); if (roles == null) { this.roles = ImmutableList.copyOf(new ArrayList<>()); } else { //remove stray empty strings while (roles.contains("")) { roles.remove(""); } this.roles = ImmutableList.copyOf(roles); } this.pageAccessControl = pageAccessControl == null ? PageAccessControl.PERMISSION : pageAccessControl; this.labelKey = labelKey; this.positionIndex = positionIndex; this.uriSegment = uriSegment; this.viewClass = viewClass; this.id = id; } @Override public String getUriSegment() { return uriSegment; } @Override public I18NKey getLabelKey() { return labelKey; } @Override public Class<? extends KrailView> getViewClass() { return viewClass; } public String toStringAsMapEntry() { StringBuilder buf = new StringBuilder(); buf.append((uriSegment == null) ? "no segment given" : uriSegment); buf.append((viewClass == null) ? "" : "\t\t: " + viewClass.getSimpleName()); buf.append((labelKey == null) ? "" : "\t~ " + ((Enum<?>) labelKey).name()); return buf.toString(); } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("id="); buf.append(Integer.toString(id)); buf.append(", segment="); buf.append((uriSegment == null) ? "null" : uriSegment); buf.append(", viewClass="); buf.append((viewClass == null) ? "null" : viewClass.getName()); buf.append(", labelKey="); buf.append((labelKey == null) ? "null" : ((Enum<?>) labelKey).name()); buf.append(", roles="); if (roles.isEmpty()) { buf.append("none"); } else { boolean first = true; for (String role : roles) { if (!first) { buf.append(';'); } buf.append('['); buf.append(role); buf.append(']'); first = false; } } return buf.toString(); } public int getId() { return id; } public boolean isPublicPage() { return pageAccessControl == PageAccessControl.PUBLIC; } public boolean hasRoles() { return !roles.isEmpty(); } @Override public PageAccessControl getPageAccessControl() { return pageAccessControl; } @Override public ImmutableList<String> getRoles() { return roles; } public int getPositionIndex() { return positionIndex; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof MasterSitemapNode)) { return false; } MasterSitemapNode that = (MasterSitemapNode) o; return id == that.id; } /** * Returns a copy of this node, with the {@code pageAccessControl modified} * * @param pageAccessControl * * @return modified copy of this node */ public MasterSitemapNode modifyPageAccessControl(PageAccessControl pageAccessControl) { checkNotNull(pageAccessControl); return new MasterSitemapNode(id, uriSegment, viewClass, labelKey, positionIndex, pageAccessControl, roles); } /** * Returns a copy of this node with the {@code viewClass} modified * * @param viewClass * * @return modified copy of this node */ public MasterSitemapNode modifyView(Class<? extends KrailView> viewClass) { checkNotNull(viewClass); return new MasterSitemapNode(id, uriSegment, viewClass, labelKey, positionIndex, pageAccessControl, roles); } /** * Returns a copy of this node with the {@code labelKey} modified * * @param labelKey * * @return modified copy of this node */ public MasterSitemapNode modifyLabelKey(I18NKey labelKey) { checkNotNull(labelKey); return new MasterSitemapNode(id, uriSegment, viewClass, labelKey, positionIndex, pageAccessControl, roles); } @Override public int hashCode() { return id; } }