/*
* 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.core.runtime.system.session;
import java.util.List;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.fixtures.LogonFixture;
import org.apache.isis.applib.services.i18n.TranslationService;
import org.apache.isis.applib.services.title.TitleService;
import org.apache.isis.core.commons.authentication.AuthenticationSession;
import org.apache.isis.core.commons.components.ApplicationScopedComponent;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.specloader.ServiceInitializer;
import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
import org.apache.isis.core.runtime.authentication.AuthenticationManager;
import org.apache.isis.core.runtime.authentication.exploration.ExplorationSession;
import org.apache.isis.core.runtime.authorization.AuthorizationManager;
import org.apache.isis.core.runtime.fixtures.FixturesInstallerFromConfiguration;
import org.apache.isis.core.runtime.system.DeploymentType;
import org.apache.isis.core.runtime.system.MessageRegistry;
import org.apache.isis.core.runtime.system.internal.InitialisationSession;
import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
import org.apache.isis.core.runtime.system.persistence.PersistenceSessionFactory;
import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
import org.apache.isis.core.runtime.system.transaction.IsisTransactionManagerException;
/**
* Is the factory of {@link IsisSession}s, also holding a reference to the current session using
* a thread-local.
*
* <p>
* The class can in considered as analogous to (and is in many ways a wrapper for) a JDO
* <code>PersistenceManagerFactory</code>.
* </p>
*
* <p>
* The class is only instantiated once; it is also registered with {@link ServicesInjector}, meaning that
* it can be {@link Inject}'d into other domain services.
* </p>
*/
public class IsisSessionFactory implements ApplicationScopedComponent {
@SuppressWarnings("unused")
private final static Logger LOG = LoggerFactory.getLogger(IsisSessionFactory.class);
//region > constructor, fields
private final DeploymentCategory deploymentCategory;
private final IsisConfiguration configuration;
private final SpecificationLoader specificationLoader;
private final ServicesInjector servicesInjector;
private final AuthenticationManager authenticationManager;
private final AuthorizationManager authorizationManager;
private final PersistenceSessionFactory persistenceSessionFactory;
public IsisSessionFactory(
final DeploymentCategory deploymentCategory,
final ServicesInjector servicesInjector) {
this.servicesInjector = servicesInjector;
this.deploymentCategory = deploymentCategory;
this.configuration = servicesInjector.getConfigurationServiceInternal();
this.specificationLoader = servicesInjector.getSpecificationLoader();
this.authenticationManager = servicesInjector.getAuthenticationManager();
this.authorizationManager = servicesInjector.getAuthorizationManager();
this.persistenceSessionFactory = servicesInjector.lookupServiceElseFail(PersistenceSessionFactory.class);
}
//endregion
//region > constructServices, destroyServicesAndShutdown
private ServiceInitializer serviceInitializer;
@Programmatic
public void constructServices() {
// do postConstruct. We store the initializer to do preDestroy on shutdown
serviceInitializer = new ServiceInitializer(configuration, servicesInjector.getRegisteredServices());
serviceInitializer.validate();
openSession(new InitialisationSession());
try {
//
// postConstructInSession
//
IsisTransactionManager transactionManager = getCurrentSessionTransactionManager();
transactionManager.startTransaction();
try {
serviceInitializer.postConstruct();
} catch(RuntimeException ex) {
transactionManager.getCurrentTransaction().setAbortCause(new IsisTransactionManagerException(ex));
} finally {
// will commit or abort
transactionManager.endTransaction();
}
//
// installFixturesIfRequired
//
final FixturesInstallerFromConfiguration fixtureInstaller =
new FixturesInstallerFromConfiguration(this);
fixtureInstaller.installFixtures();
// only allow logon fixtures if not in production mode.
if (!deploymentCategory.isProduction()) {
logonFixture = fixtureInstaller.getLogonFixture();
}
//
// translateServicesAndEnumConstants
//
final List<Object> services = servicesInjector.getRegisteredServices();
// take a copy of all services to avoid occasionall concurrent modification exceptions
// that can sometimes occur in the loop
final List<Object> copyOfServices = Lists.newArrayList(services);
final TitleService titleService = servicesInjector.lookupServiceElseFail(TitleService.class);
for (Object service : copyOfServices) {
final String unused = titleService.titleOf(service);
}
for (final ObjectSpecification objSpec : servicesInjector.getSpecificationLoader().allSpecifications()) {
final Class<?> correspondingClass = objSpec.getCorrespondingClass();
if(correspondingClass.isEnum()) {
final Object[] enumConstants = correspondingClass.getEnumConstants();
for (Object enumConstant : enumConstants) {
final String unused = titleService.titleOf(enumConstant);
}
}
}
// as used by the Wicket UI
final TranslationService translationService = servicesInjector.lookupServiceElseFail(TranslationService.class);
final String context = IsisSessionFactoryBuilder.class.getName();
final MessageRegistry messageRegistry = new MessageRegistry();
final List<String> messages = messageRegistry.listMessages();
for (String message : messages) {
translationService.translate(context, message);
}
} finally {
closeSession();
}
}
@Programmatic
public void destroyServicesAndShutdown() {
destroyServices();
shutdown();
}
private void destroyServices() {
// may not be set if the metamodel validation failed during initialization
if (serviceInitializer == null) {
return;
}
// call @PreDestroy (in a session)
openSession(new InitialisationSession());
IsisTransactionManager transactionManager = getCurrentSessionTransactionManager();
try {
transactionManager.startTransaction();
try {
serviceInitializer.preDestroy();
} catch (RuntimeException ex) {
transactionManager.getCurrentTransaction().setAbortCause(
new IsisTransactionManagerException(ex));
} finally {
// will commit or abort
transactionManager.endTransaction();
}
} finally {
closeSession();
}
}
private void shutdown() {
persistenceSessionFactory.shutdown();
authenticationManager.shutdown();
specificationLoader.shutdown();
}
//endregion
//region > logonFixture
private LogonFixture logonFixture;
/**
* The {@link LogonFixture}, if any, obtained by running fixtures.
*
* <p>
* Intended to be used when for {@link DeploymentType#SERVER_EXPLORATION
* exploration} (instead of an {@link ExplorationSession}) or
* {@link DeploymentType#SERVER_PROTOTYPE prototype} deployments (saves logging
* in). Should be <i>ignored</i> in other {@link DeploymentType}s.
*/
@Programmatic
public LogonFixture getLogonFixture() {
return logonFixture;
}
//endregion
//region > openSession, closeSession, currentSession, inSession
private final ThreadLocal<IsisSession> currentSession = new ThreadLocal<>();
/**
* Creates and {@link IsisSession#open() open}s the {@link IsisSession}.
*/
@Programmatic
public IsisSession openSession(final AuthenticationSession authenticationSession) {
closeSession();
final PersistenceSession persistenceSession =
persistenceSessionFactory.createPersistenceSession(servicesInjector, authenticationSession);
IsisSession session = new IsisSession(authenticationSession, persistenceSession);
currentSession.set(session);
session.open();
return session;
}
@Programmatic
public void closeSession() {
final IsisSession existingSessionIfAny = getCurrentSession();
if (existingSessionIfAny == null) {
return;
}
existingSessionIfAny.close();
currentSession.set(null);
}
@Programmatic
public IsisSession getCurrentSession() {
return currentSession.get();
}
private IsisTransactionManager getCurrentSessionTransactionManager() {
final IsisSession currentSession = getCurrentSession();
return currentSession.getPersistenceSession().getTransactionManager();
}
@Programmatic
public boolean inSession() {
return getCurrentSession() != null;
}
@Programmatic
public boolean inTransaction() {
if (inSession()) {
if (getCurrentSession().getCurrentTransaction() != null) {
if (!getCurrentSession().getCurrentTransaction().getState().isComplete()) {
return true;
}
}
}
return false;
}
/**
* As per {@link #doInSession(Runnable, AuthenticationSession)}, using a default {@link InitialisationSession}.
* @param runnable
*/
@Programmatic
public void doInSession(final Runnable runnable) {
doInSession(runnable, new InitialisationSession());
}
/**
* A template method that executes a piece of code in a session.
* If there is an open session then it is reused, otherwise a temporary one
* is created.
*
* @param runnable The piece of code to run.
* @param authenticationSession
*/
@Programmatic
public void doInSession(final Runnable runnable, final AuthenticationSession authenticationSession) {
doInSession(new Callable<Void>() {
@Override
public Void call() throws Exception {
runnable.run();
return null;
}
}, authenticationSession);
}
/**
* As per {@link #doInSession(Callable), AuthenticationSession}, using a default {@link InitialisationSession}.
*/
@Programmatic
public <R> R doInSession(final Callable<R> callable) {
return doInSession(callable, new InitialisationSession());
}
/**
* A template method that executes a piece of code in a session.
* If there is an open session then it is reused, otherwise a temporary one
* is created.
*
* @param callable The piece of code to run.
* @param authenticationSession - the user to run under
*/
@Programmatic
public <R> R doInSession(final Callable<R> callable, final AuthenticationSession authenticationSession) {
final IsisSessionFactory sessionFactory = this;
boolean noSession = !sessionFactory.inSession();
try {
if (noSession) {
sessionFactory.openSession(authenticationSession);
}
return callable.call();
} catch (Exception x) {
throw new RuntimeException(
String.format("An error occurred while executing code in %s session", noSession ? "a temporary" : "a"),
x);
} finally {
if (noSession) {
sessionFactory.closeSession();
}
}
}
//endregion
//region > component accessors
/**
* The {@link ApplicationScopedComponent application-scoped}
* {@link DeploymentCategory}.
*/
@Programmatic
public DeploymentCategory getDeploymentCategory() {
return deploymentCategory;
}
/**
* The {@link ApplicationScopedComponent application-scoped}
* {@link IsisConfiguration}.
*/
@Programmatic
public IsisConfiguration getConfiguration() {
return configuration;
}
/**
* The {@link ApplicationScopedComponent application-scoped} {@link ServicesInjector}.
*/
@Programmatic
public ServicesInjector getServicesInjector() {
return servicesInjector;
}
/**
* Derived from {@link #getServicesInjector()}.
*
* @deprecated - use {@link #getServicesInjector()} instead.
*/
@Programmatic
@Deprecated
public List<Object> getServices() {
return servicesInjector.getRegisteredServices();
}
/**
* The {@link ApplicationScopedComponent application-scoped}
* {@link SpecificationLoader}.
*/
@Programmatic
public SpecificationLoader getSpecificationLoader() {
return specificationLoader;
}
/**
* The {@link AuthenticationManager} that will be used to authenticate and
* create {@link AuthenticationSession}s
* {@link IsisSession#getAuthenticationSession() within} the
* {@link IsisSession}.
*/
@Programmatic
public AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
/**
* The {@link AuthorizationManager} that will be used to authorize access to
* domain objects.
*/
@Programmatic
public AuthorizationManager getAuthorizationManager() {
return authorizationManager;
}
/**
* The {@link org.apache.isis.core.runtime.system.persistence.PersistenceSessionFactory} that will be used to create
* {@link PersistenceSession} {@link IsisSession#getPersistenceSession()
* within} the {@link IsisSession}.
*/
@Programmatic
public PersistenceSessionFactory getPersistenceSessionFactory() {
return persistenceSessionFactory;
}
//endregion
}