/*
* 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.isis.viewer.wicket.viewer.integration.wicket;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Set;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import org.apache.wicket.Application;
import org.apache.wicket.IPageFactory;
import org.apache.wicket.Page;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.Session;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.core.request.handler.PageProvider;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler.RedirectPolicy;
import org.apache.wicket.protocol.http.PageExpiredException;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.component.IRequestablePage;
import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.isis.applib.services.exceprecog.ExceptionRecognizer;
import org.apache.isis.applib.services.exceprecog.ExceptionRecognizerComposite;
import org.apache.isis.applib.services.exceprecog.ExceptionRecognizerForType;
import org.apache.isis.core.commons.authentication.AuthenticationSession;
import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.specloader.validator.MetaModelInvalidException;
import org.apache.isis.core.runtime.system.context.IsisContext;
import org.apache.isis.core.runtime.system.session.IsisSession;
import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
import org.apache.isis.viewer.wicket.model.models.PageType;
import org.apache.isis.viewer.wicket.ui.errors.ExceptionModel;
import org.apache.isis.viewer.wicket.ui.pages.PageClassRegistry;
import org.apache.isis.viewer.wicket.ui.pages.error.ErrorPage;
import org.apache.isis.viewer.wicket.ui.pages.login.WicketSignInPage;
import org.apache.isis.viewer.wicket.ui.pages.mmverror.MmvErrorPage;
/**
* Isis-specific implementation of the Wicket's {@link RequestCycle},
* automatically opening a {@link IsisSession} at the beginning of the request
* and committing the transaction and closing the session at the end.
*/
public class WebRequestCycleForIsis extends AbstractRequestCycleListener {
private static final Logger LOG = LoggerFactory.getLogger(WebRequestCycleForIsis.class);
private PageClassRegistry pageClassRegistry;
@Override
public synchronized void onBeginRequest(RequestCycle requestCycle) {
if (!Session.exists()) {
return;
}
final AuthenticatedWebSessionForIsis wicketSession = AuthenticatedWebSessionForIsis.get();
final AuthenticationSession authenticationSession = wicketSession.getAuthenticationSession();
if (authenticationSession == null) {
return;
}
getIsisSessionFactory().openSession(authenticationSession);
getTransactionManager().startTransaction();
}
@Override
public void onRequestHandlerResolved(final RequestCycle cycle, final IRequestHandler handler)
{
if(handler instanceof RenderPageRequestHandler) {
AdapterManager.ConcurrencyChecking.disable();
final MetaModelInvalidException mmie = IsisContext.getMetaModelInvalidExceptionIfAny();
if(mmie != null) {
RenderPageRequestHandler requestHandler = (RenderPageRequestHandler) handler;
final IRequestablePage nextPage = requestHandler.getPage();
if(nextPage instanceof ErrorPage || nextPage instanceof MmvErrorPage) {
// do nothing
return;
}
throw mmie;
}
}
}
/**
* Is called prior to {@link #onEndRequest(RequestCycle)}, and offers the opportunity to
* throw an exception.
*/
@Override
public void onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler) {
if(LOG.isDebugEnabled()) {
LOG.debug("onRequestHandlerExecuted: handler: " + handler);
}
if(handler instanceof RenderPageRequestHandler) {
AdapterManager.ConcurrencyChecking.reset(AdapterManager.ConcurrencyChecking.CHECK);
}
if (getIsisSessionFactory().inSession()) {
try {
// will commit (or abort) the transaction;
// an abort will cause the exception to be thrown.
getTransactionManager().endTransaction();
} catch(Exception ex) {
// will redirect to error page after this,
// so make sure there is a new transaction ready to go.
if(getTransactionManager().getCurrentTransaction().getState().isComplete()) {
getTransactionManager().startTransaction();
}
if(handler instanceof RenderPageRequestHandler) {
RenderPageRequestHandler requestHandler = (RenderPageRequestHandler) handler;
if(requestHandler.getPage() instanceof ErrorPage) {
// do nothing
return;
}
}
// shouldn't return null given that we're in a session ...
PageProvider errorPageProvider = errorPageProviderFor(ex);
throw new RestartResponseException(errorPageProvider, RedirectPolicy.ALWAYS_REDIRECT);
}
}
}
/**
* It is not possible to throw exceptions here, hence use of {@link #onRequestHandlerExecuted(RequestCycle, IRequestHandler)}.
*/
@Override
public synchronized void onEndRequest(RequestCycle cycle) {
if (getIsisSessionFactory().inSession()) {
try {
// belt and braces
getTransactionManager().endTransaction();
} finally {
getIsisSessionFactory().closeSession();
}
}
}
@Override
public IRequestHandler onException(RequestCycle cycle, Exception ex) {
final MetaModelInvalidException mmie = IsisContext.getMetaModelInvalidExceptionIfAny();
if(mmie != null) {
final Set<String> validationErrors = mmie.getValidationErrors();
final MmvErrorPage mmvErrorPage = new MmvErrorPage(validationErrors);
return new RenderPageRequestHandler(new PageProvider(mmvErrorPage), RedirectPolicy.ALWAYS_REDIRECT);
}
PageProvider errorPageProvider = errorPageProviderFor(ex);
// avoid infinite redirect loops
RedirectPolicy redirectPolicy = ex instanceof PageExpiredException
? RedirectPolicy.NEVER_REDIRECT
: RedirectPolicy.ALWAYS_REDIRECT;
return errorPageProvider != null
? new RenderPageRequestHandler(errorPageProvider, redirectPolicy)
: null;
}
protected PageProvider errorPageProviderFor(Exception ex) {
IRequestablePage errorPage = errorPageFor(ex);
return errorPage != null? new PageProvider(errorPage): null;
}
// special case handling for PageExpiredException, otherwise infinite loop
private final static ExceptionRecognizerForType pageExpiredExceptionRecognizer =
new ExceptionRecognizerForType(PageExpiredException.class, new Function<String,String>(){
@Override
public String apply(String input) {
return "Requested page is no longer available.";
}
});
protected IRequestablePage errorPageFor(Exception ex) {
List<ExceptionRecognizer> exceptionRecognizers = Lists.newArrayList();
exceptionRecognizers.add(pageExpiredExceptionRecognizer);
if(inIsisSession()) {
exceptionRecognizers.addAll(getServicesInjector().lookupServices(ExceptionRecognizer.class));
} else {
final MetaModelInvalidException mmie = IsisContext.getMetaModelInvalidExceptionIfAny();
if(mmie != null) {
Set<String> validationErrors = mmie.getValidationErrors();
return new MmvErrorPage(validationErrors);
}
// not sure whether this can ever happen now...
LOG.warn("Unable to obtain exceptionRecognizers (no session), will be treated as unrecognized exception", ex);
}
String recognizedMessageIfAny = new ExceptionRecognizerComposite(exceptionRecognizers).recognize(ex);
ExceptionModel exceptionModel = ExceptionModel.create(recognizedMessageIfAny, ex);
return isSignedIn() ? new ErrorPage(exceptionModel) : newSignInPage(exceptionModel);
}
/**
* Tries to instantiate the configured {@link PageType#SIGN_IN signin page} with the given exception model
*
* @param exceptionModel A model bringing the information about the occurred problem
* @return An instance of the configured signin page
*/
private IRequestablePage newSignInPage(final ExceptionModel exceptionModel) {
Class<? extends Page> signInPageClass = null;
if (pageClassRegistry != null) {
signInPageClass = pageClassRegistry.getPageClass(PageType.SIGN_IN);
}
if (signInPageClass == null) {
signInPageClass = WicketSignInPage.class;
}
final PageParameters parameters = new PageParameters();
Page signInPage;
try {
Constructor<? extends Page> constructor = signInPageClass.getConstructor(PageParameters.class, ExceptionModel.class);
signInPage = constructor.newInstance(parameters, exceptionModel);
} catch (Exception ex) {
try {
IPageFactory pageFactory = Application.get().getPageFactory();
signInPage = pageFactory.newPage(signInPageClass, parameters);
} catch (Exception x) {
throw new WicketRuntimeException("Cannot instantiate the configured sign in page", x);
}
}
return signInPage;
}
/**
* TODO: this is very hacky...
*
* <p>
* Matters should improve once ISIS-299 gets implemented...
*/
protected boolean isSignedIn() {
if(!inIsisSession()) {
return false;
}
if(getAuthenticationSession() == null) {
return false;
}
return getWicketAuthenticationSession().isSignedIn();
}
public void setPageClassRegistry(PageClassRegistry pageClassRegistry) {
this.pageClassRegistry = pageClassRegistry;
}
//region > Dependencies (from isis' context)
protected ServicesInjector getServicesInjector() {
return getIsisSessionFactory().getServicesInjector();
}
protected IsisTransactionManager getTransactionManager() {
return getIsisSessionFactory().getCurrentSession().getPersistenceSession().getTransactionManager();
}
protected boolean inIsisSession() {
return getIsisSessionFactory().inSession();
}
protected AuthenticationSession getAuthenticationSession() {
return getIsisSessionFactory().getCurrentSession().getAuthenticationSession();
}
IsisSessionFactory getIsisSessionFactory() {
return IsisContext.getSessionFactory();
}
//endregion
//region > Dependencies (from wicket)
protected AuthenticatedWebSession getWicketAuthenticationSession() {
return AuthenticatedWebSession.get();
}
//endregion
}