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}