/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.wicket.security.components.markup.html.links; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.model.IModel; import org.apache.wicket.security.actions.Enable; import org.apache.wicket.security.actions.WaspAction; import org.apache.wicket.security.checks.ISecurityCheck; import org.apache.wicket.security.checks.LinkSecurityCheck; import org.apache.wicket.security.components.ISecureComponent; import org.apache.wicket.security.components.SecureComponentHelper; /** * A secure link to handle panel replacements or any other type of {@link MarkupContainer} * s. It is also usable as a link to switch between 2 or more panels. Security is enforced * on the replacing class, not on the panel. This means that the panels do not need to be * {@link ISecureComponent}s, but are allowed to be if so desired. The link however will * only show up if the user has the {@link Enable} action for the class of the replacement * panel. Please consult your implementation on how to do that. This link is typically * placed as a child on the parent of the panel it is supposed to replace. Like so:<br/> * <code> * MarkupContainer parent=new WebMarkupContainer("parent); * parent.add(new FirstPanel("replaceMe",new Model("hello")); * parent.add(new SecureContainerLink("link",SecondPanel.class,parent,"replaceMe"){....}); * </code> * * @author marrink */ public abstract class SecureContainerLink<T> extends Link<T> implements ISecureComponent { /** * */ private static final long serialVersionUID = 1L; private Class< ? extends MarkupContainer> replacementClass; private MarkupContainer containerParent; private String containerId; /** * Constructs a new replacement link. * * @param id * id of the link * @param replacementPanel * the class of the container replacing the component on the supplied * parent * @param parentOfReplaceablePanel * the parent component where the replacement needs to take place * @param panelId * the id of the component to be replaced */ public SecureContainerLink(String id, Class< ? extends MarkupContainer> replacementPanel, MarkupContainer parentOfReplaceablePanel, String panelId) { this(id, null, replacementPanel, parentOfReplaceablePanel, panelId); } /** * Constructs a new replacement link. * * @param id * id of the link * @param object * model of the link * @param replacementPanel * the class of the container replacing the component on the supplied * parent * @param parentOfReplaceablePanel * the parent component where the replacement needs to take place * @param panelId * the id of the component to be replaced */ public SecureContainerLink(String id, IModel<T> object, Class< ? extends MarkupContainer> replacementPanel, MarkupContainer parentOfReplaceablePanel, String panelId) { super(id, object); setReplacementClass(replacementPanel); if (parentOfReplaceablePanel == null) throw new WicketRuntimeException("Parent required for replacing components."); containerParent = parentOfReplaceablePanel; if (panelId == null) throw new WicketRuntimeException("Id required from component to be replaced."); containerId = panelId; } /** * Performs the replacement, only if an actual replacement was constructed. * * @see org.apache.wicket.markup.html.link.Link#onClick() * @see #getReplacementFor(Component, String, Class) * @throws WicketRuntimeException * if a problem occurs in replacing the container. */ @Override public final void onClick() { Component replaceMe = getComponentToBeReplaced(); if (replaceMe == null) throw new WicketRuntimeException("unable to find child with id: " + containerId + " on parent: " + containerParent); Class< ? extends MarkupContainer> myReplacementClass = getReplacementClass(); MarkupContainer replacement = getReplacementFor(replaceMe, containerId, myReplacementClass); if (replacement == null) return; // do nothing if (!containerId.equals(replacement.getId())) throw new WicketRuntimeException("The replacement does not have the specified id: " + containerId + ", but id: " + replacement.getId()); if (myReplacementClass.isAssignableFrom(replacement.getClass())) containerParent.replace(replacement); else throw new WicketRuntimeException("The replacement for " + containerId + " on " + containerParent + " is not assignable from " + myReplacementClass); } /** * The component, usually a MarkupContainer or subclass, that will be replaced by the * output from {@link #getReplacementFor(Component, String, Class)}. * * @return the component or null if the id specified at the constructor is bogus. */ protected final Component getComponentToBeReplaced() { return containerParent.get(containerId); } /** * Creates a replacement for a component. although the component to be replaced does * not need to be a {@link MarkupContainer} it typically is. The replacement however * does need to be a MarkupContainer, more specifically a (sub)class of * replacementClass. Implementation may choose at this point to do the next * replacement with a different class by using {@link #setReplacementClass(Class)} in * order to create a switch like behavior. The intention of this method is thus to * create the new panel, for example:<br/> * <code> * protected MarkupContainer getReplacementFor(Component current, String id, * Class replacementClass) * { * //does a one time replace * if(MyPanel.class.isAssignableFrom(replacementClass) * { * //if this link is not a child of current, you might want to hide it after the replace has taken place * this.setVisible(false); * return new MyPanel(id, current.getModel()); * } * } * </code> Or if this link should switch between panels and is situated somewhere * higher in the component hierarchy as these panels it is supposed to switch you * could do something like this: <br/> * <code> * protected MarkupContainer getReplacementFor(Component current, String id, * Class replacementClass) * { * //continually switches between two panels * * //prepare the next replacement * setReplacementClass(current.getClass(); * //do the switch * if(MyPanel.class.isAssignableFrom(replacementClass) * { * return new MyPanel(id, current.getModel()); * } * //other panel * else * { * return new MyOtherPanel(id, current.getModel()); * } * } * </code> * * @param current * the component to be replaced * @param id * the id of the new container * @param replacementClass * the class of the replacement * @return a new replacement or null if the original component is not to be replaced * @see #setReplacementClass(Class) */ @SuppressWarnings("hiding") protected abstract MarkupContainer getReplacementFor(Component current, String id, Class< ? extends MarkupContainer> replacementClass); /** * Generates the securitycheck for this link. by default this is a * {@link LinkSecurityCheck} but implementations may choose to override this. Note * that the returned LinkSecurityCheck should not be placed in alternative rendering * mode as this will completely change the intended behavior. * * @return the securitycheck for this link or null if no security is to be enforced */ protected ISecurityCheck generateSecurityCheck() { return new LinkSecurityCheck(this, getReplacementClass()); } /** * * @see org.apache.wicket.security.components.ISecureComponent#getSecurityCheck() */ public ISecurityCheck getSecurityCheck() { return SecureComponentHelper.getSecurityCheck(this); } /** * * @see org.apache.wicket.security.components.ISecureComponent#isActionAuthorized(java.lang.String) */ public boolean isActionAuthorized(String waspAction) { return SecureComponentHelper.isActionAuthorized(this, waspAction); } /** * * @see org.apache.wicket.security.components.ISecureComponent#isActionAuthorized(WaspAction) */ public boolean isActionAuthorized(WaspAction action) { return SecureComponentHelper.isActionAuthorized(this, action); } /** * * @see org.apache.wicket.security.components.ISecureComponent#isAuthenticated() */ public boolean isAuthenticated() { return SecureComponentHelper.isAuthenticated(this); } /** * * @see org.apache.wicket.security.components.ISecureComponent#setSecurityCheck(org.apache.wicket.security.checks.ISecurityCheck) */ public void setSecurityCheck(ISecurityCheck check) { SecureComponentHelper.setSecurityCheck(this, check); } /** * Gets replacementClass. * * @return replacementClass */ protected final Class< ? extends MarkupContainer> getReplacementClass() { return replacementClass; } /** * Sets replacementClass. Note by changing the replacement class a new securitycheck * is automatically created. * * @param replacementClass * replacementClass * @see #generateSecurityCheck() * @throws WicketRuntimeException * if the class is null or not a {@link MarkupContainer} */ protected final void setReplacementClass(Class< ? extends MarkupContainer> replacementClass) { if (replacementClass == null) throw new WicketRuntimeException("replacementClass cannot be null"); this.replacementClass = replacementClass; setSecurityCheck(generateSecurityCheck()); } }