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.session; 018 019import java.lang.reflect.Constructor; 020import java.lang.reflect.InvocationTargetException; 021import java.lang.reflect.Modifier; 022import java.util.concurrent.ConcurrentMap; 023 024import org.apache.wicket.IPageFactory; 025import org.apache.wicket.Page; 026import org.apache.wicket.WicketRuntimeException; 027import org.apache.wicket.authorization.AuthorizationException; 028import org.apache.wicket.markup.MarkupException; 029import org.apache.wicket.request.RequestHandlerExecutor.ReplaceHandlerException; 030import org.apache.wicket.request.component.IRequestablePage; 031import org.apache.wicket.request.mapper.parameter.PageParameters; 032import org.apache.wicket.util.lang.Generics; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036 037/** 038 * A factory that constructs Pages. 039 * 040 * @see IPageFactory 041 * 042 * @author Juergen Donnerstag 043 * @author Jonathan Locke 044 */ 045public final class DefaultPageFactory implements IPageFactory 046{ 047 /** Log for reporting. */ 048 private static final Logger log = LoggerFactory.getLogger(DefaultPageFactory.class); 049 050 /** Map of Constructors for Page subclasses */ 051 private final ConcurrentMap<Class<?>, Constructor<?>> constructorForClass = Generics.newConcurrentHashMap(); 052 053 /** 054 * {@link #isBookmarkable(Class)} is expensive, we cache the result here 055 */ 056 private final ConcurrentMap<String, Boolean> pageToBookmarkableCache = Generics.newConcurrentHashMap(); 057 058 @Override 059 public final <C extends IRequestablePage> C newPage(final Class<C> pageClass) 060 { 061 try 062 { 063 // throw an exception in case default constructor is missing 064 // => improved error message 065 Constructor<C> constructor = pageClass.getDeclaredConstructor((Class<?>[]) null); 066 067 return processPage(newPage(constructor, null), null); 068 } 069 catch (NoSuchMethodException e) 070 { 071 // a bit of a hack here.. 072 Constructor<C> constructor = constructor(pageClass, PageParameters.class); 073 if (constructor != null) 074 { 075 PageParameters pp = new PageParameters(); 076 return processPage(newPage(constructor, pp), pp); 077 } 078 else 079 { 080 throw new WicketRuntimeException("Unable to create page from " + pageClass + 081 ". Class does not have a visible default constructor.", e); 082 } 083 } 084 } 085 086 @Override 087 public final <C extends IRequestablePage> C newPage(final Class<C> pageClass, 088 final PageParameters parameters) 089 { 090 // Try to get constructor that takes PageParameters 091 Constructor<C> constructor = constructor(pageClass, PageParameters.class); 092 093 // If we got a PageParameters constructor 094 if (constructor != null) 095 { 096 final PageParameters nullSafeParams = parameters == null ? new PageParameters() : parameters; 097 098 // return new Page(parameters) 099 return processPage(newPage(constructor, nullSafeParams), nullSafeParams); 100 } 101 102 // Always try default constructor if one exists 103 return processPage(newPage(pageClass), parameters); 104 } 105 106 /** 107 * Looks up a one-arg Page constructor by class and argument type. 108 * 109 * @param pageClass 110 * The class of page 111 * @param argumentType 112 * The argument type 113 * @return The page constructor, or null if no one-arg constructor can be found taking the given 114 * argument type. 115 */ 116 private <C extends IRequestablePage> Constructor<C> constructor(final Class<C> pageClass, 117 final Class<PageParameters> argumentType) 118 { 119 // Get constructor for page class from cache 120 Constructor<C> constructor = (Constructor<C>) constructorForClass.get(pageClass); 121 122 // Need to look up? 123 if (constructor == null) 124 { 125 try 126 { 127 // Try to find the constructor 128 constructor = pageClass.getDeclaredConstructor(new Class[] { argumentType }); 129 130 // Store it in the cache 131 Constructor<C> tmpConstructor = (Constructor<C>) constructorForClass.putIfAbsent(pageClass, constructor); 132 if (tmpConstructor != null) 133 { 134 constructor = tmpConstructor; 135 } 136 137 log.debug("Found constructor for Page of type '{}' and argument of type '{}'.", 138 pageClass, argumentType); 139 } 140 catch (NoSuchMethodException e) 141 { 142 log.debug( 143 "Page of type '{}' has not visible constructor with an argument of type '{}'.", 144 pageClass, argumentType); 145 146 return null; 147 } 148 } 149 150 return constructor; 151 } 152 153 /** 154 * Creates a new Page using the given constructor and argument. 155 * 156 * @param constructor 157 * The constructor to invoke 158 * @param argument 159 * The argument to pass to the constructor or null to pass no arguments 160 * @return The new page 161 * @throws WicketRuntimeException 162 * Thrown if the Page cannot be instantiated using the given constructor and 163 * argument. 164 */ 165 private <C extends IRequestablePage> C newPage(final Constructor<C> constructor, final PageParameters argument) 166 { 167 try 168 { 169 if (argument != null) 170 { 171 return constructor.newInstance(argument); 172 } 173 else 174 { 175 return constructor.newInstance(); 176 } 177 } 178 catch (InstantiationException e) 179 { 180 throw new WicketRuntimeException(createDescription(constructor, argument), e); 181 } 182 catch (IllegalAccessException e) 183 { 184 throw new WicketRuntimeException(createDescription(constructor, argument), e); 185 } 186 catch (InvocationTargetException e) 187 { 188 if (e.getTargetException() instanceof ReplaceHandlerException || 189 e.getTargetException() instanceof AuthorizationException || 190 e.getTargetException() instanceof MarkupException) 191 { 192 throw (RuntimeException)e.getTargetException(); 193 } 194 throw new WicketRuntimeException(createDescription(constructor, argument), e); 195 } 196 } 197 198 private <C extends IRequestablePage> C processPage(final C page, final PageParameters pageParameters) 199 { 200 // the page might have not propagate page parameters from constructor. if that's the case 201 // we force the parameters 202 if ((pageParameters != null) && (page.getPageParameters() != pageParameters)) 203 { 204 page.getPageParameters().overwriteWith(pageParameters); 205 } 206 207 ((Page)page).setWasCreatedBookmarkable(true); 208 209 return page; 210 } 211 212 private String createDescription(final Constructor<?> constructor, final Object argument) 213 { 214 StringBuilder msg = new StringBuilder(); 215 msg.append("Can't instantiate page using constructor '").append(constructor).append('\''); 216 if (argument != null) 217 { 218 msg.append(" and argument '").append(argument).append('\''); 219 } 220 msg.append('.'); 221 222 if (constructor != null) 223 { 224 if (Modifier.isPrivate(constructor.getModifiers())) 225 { 226 msg.append(" This constructor is private!"); 227 } 228 else 229 { 230 msg.append(" An exception has been thrown during construction!"); 231 } 232 } 233 else 234 { 235 msg.append(" There is no such constructor!"); 236 } 237 238 return msg.toString(); 239 } 240 241 @Override 242 public <C extends IRequestablePage> boolean isBookmarkable(Class<C> pageClass) 243 { 244 Boolean bookmarkable = pageToBookmarkableCache.get(pageClass.getName()); 245 if (bookmarkable == null) 246 { 247 try 248 { 249 if (pageClass.getDeclaredConstructor(new Class[] { }) != null) 250 { 251 bookmarkable = Boolean.TRUE; 252 } 253 } 254 catch (Exception ignore) 255 { 256 try 257 { 258 if (pageClass.getDeclaredConstructor(new Class[] { PageParameters.class }) != null) 259 { 260 bookmarkable = Boolean.TRUE; 261 } 262 } 263 catch (Exception ignored) 264 { 265 } 266 } 267 268 if (bookmarkable == null) 269 { 270 bookmarkable = Boolean.FALSE; 271 } 272 Boolean tmpBookmarkable = pageToBookmarkableCache.putIfAbsent(pageClass.getName(), bookmarkable); 273 if (tmpBookmarkable != null) 274 { 275 bookmarkable = tmpBookmarkable; 276 } 277 } 278 279 return bookmarkable; 280 } 281}