001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.devutils.stateless; 018 019import org.apache.wicket.Component; 020import org.apache.wicket.MarkupContainer; 021import org.apache.wicket.Page; 022import org.apache.wicket.application.IComponentOnBeforeRenderListener; 023import org.apache.wicket.behavior.Behavior; 024import org.apache.wicket.util.lang.Classes; 025import org.apache.wicket.util.string.StringList; 026import org.apache.wicket.util.visit.IVisit; 027import org.apache.wicket.util.visit.IVisitor; 028 029/** 030 * Stateless checker. Checks if components with {@link StatelessComponent} annotation are really 031 * stateless. This is a utility that is intended for use primarily during development. If you add an 032 * instance of this class to your application, it will check all components or pages marked with the 033 * <tt>StatelessComponent</tt> annotation to make sure that they are stateless as you intended. 034 * 035 * This is useful when trying to maintain stateless pages since it is very easy to inadvertently add 036 * a component to a page that internally uses stateful links, etc. 037 * 038 * @author Marat Radchenko 039 * @see StatelessComponent 040 */ 041public class StatelessChecker implements IComponentOnBeforeRenderListener 042{ 043 /** 044 * Returns <code>true</code> if checker must check given component, <code>false</code> 045 * otherwise. 046 * 047 * @param component 048 * component to check. 049 * @return <code>true</code> if checker must check given component. 050 */ 051 protected boolean mustCheck(final Component component) 052 { 053 final StatelessComponent ann = component.getClass().getAnnotation(StatelessComponent.class); 054 return (ann != null) && ann.enabled(); 055 } 056 /** 057 * The given component claims to be stateless but isn't. 058 * 059 * @param e StatelessCheckFailureException 060 */ 061 protected void fail(StatelessCheckFailureException e) 062 { 063 throw e; 064 } 065 066 /** 067 * @see org.apache.wicket.application.IComponentOnBeforeRenderListener#onBeforeRender(org.apache.wicket.Component) 068 */ 069 @Override 070 public void onBeforeRender(final Component component) 071 { 072 if (mustCheck(component)) 073 { 074 final IVisitor<Component, Component> visitor = new IVisitor<Component, Component>() 075 { 076 @Override 077 public void component(final Component comp, final IVisit<Component> visit) 078 { 079 if ((component instanceof Page) && mustCheck(comp)) 080 { 081 // Do not go deeper, because this component will be 082 // checked by checker 083 // itself. 084 // Actually we could go deeper but that would mean we 085 // traverse it twice 086 // (for current component and for inspected one). 087 // We go deeper for Page because full tree will be 088 // inspected during 089 // isPageStateless call. 090 visit.dontGoDeeper(); 091 } 092 else if (!comp.isStateless()) 093 { 094 visit.stop(comp); 095 } 096 else 097 { 098 // continue 099 } 100 } 101 }; 102 103 if (component.isStateless() == false) 104 { 105 StringList statefulBehaviors = new StringList(); 106 for (Behavior b : component.getBehaviors()) 107 { 108 if (b.getStatelessHint(component) == false) 109 { 110 statefulBehaviors.add(Classes.name(b.getClass())); 111 } 112 } 113 String reason; 114 if (statefulBehaviors.size() == 0) 115 { 116 reason = " Possible reason: no stateless hint"; 117 } 118 else 119 { 120 reason = " Stateful behaviors: " + statefulBehaviors.join(); 121 } 122 fail(new StatelessCheckFailureException(component, reason)); 123 return; 124 } 125 126 if (component instanceof MarkupContainer) 127 { 128 MarkupContainer container = ((MarkupContainer)component); 129 // Traverse children 130 final Object o = container.visitChildren(visitor); 131 if (o != null) 132 { 133 fail(new StatelessCheckFailureException(container, " Offending component: " + o)); 134 return; 135 } 136 } 137 138 if (component instanceof Page) 139 { 140 final Page p = (Page)component; 141 if (!p.isBookmarkable()) 142 { 143 fail(new StatelessCheckFailureException(p, " Only bookmarkable pages can be stateless")); 144 return; 145 } 146 if (!p.isPageStateless()) 147 { 148 fail(new StatelessCheckFailureException(p, " for unknown reason")); 149 return; 150 } 151 } 152 } 153 } 154}