    001// Copyright 2007-2013 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.hibernate.web.modules;
    016
    017import java.util.Objects;
    018import java.util.Set;
    019
    020import javax.persistence.metamodel.EntityType;
    021import javax.persistence.metamodel.SingularAttribute;
    022
    023import org.apache.tapestry5.ValueEncoder;
    024import org.apache.tapestry5.commons.Configuration;
    025import org.apache.tapestry5.commons.MappedConfiguration;
    026import org.apache.tapestry5.commons.OrderedConfiguration;
    027import org.apache.tapestry5.commons.services.PropertyAccess;
    028import org.apache.tapestry5.commons.services.TypeCoercer;
    029import org.apache.tapestry5.hibernate.HibernateCore;
    030import org.apache.tapestry5.hibernate.HibernateSessionSource;
    031import org.apache.tapestry5.hibernate.HibernateSymbols;
    032import org.apache.tapestry5.hibernate.web.HibernatePersistenceConstants;
    033import org.apache.tapestry5.hibernate.web.internal.CommitAfterWorker;
    034import org.apache.tapestry5.hibernate.web.internal.EntityApplicationStatePersistenceStrategy;
    035import org.apache.tapestry5.hibernate.web.internal.EntityPersistentFieldStrategy;
    036import org.apache.tapestry5.hibernate.web.internal.HibernateEntityValueEncoder;
    037import org.apache.tapestry5.http.internal.TapestryHttpInternalConstants;
    038import org.apache.tapestry5.ioc.LoggerSource;
    039import org.apache.tapestry5.ioc.annotations.Contribute;
    040import org.apache.tapestry5.ioc.annotations.Primary;
    041import org.apache.tapestry5.ioc.annotations.Symbol;
    042import org.apache.tapestry5.ioc.services.ServiceOverride;
    043import org.apache.tapestry5.services.ApplicationStateContribution;
    044import org.apache.tapestry5.services.ApplicationStatePersistenceStrategy;
    045import org.apache.tapestry5.services.ComponentClassResolver;
    046import org.apache.tapestry5.services.LibraryMapping;
    047import org.apache.tapestry5.services.PersistentFieldStrategy;
    048import org.apache.tapestry5.services.ValueEncoderFactory;
    049import org.apache.tapestry5.services.dashboard.DashboardManager;
    050import org.apache.tapestry5.services.dashboard.DashboardTab;
    051import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
    052import org.hibernate.Session;
    053import org.slf4j.Logger;
    054
    055/**
    056 * Supplements the services defined by {@link org.apache.tapestry5.hibernate.modules.HibernateCoreModule} with additional
    057 * services and configuration specific to Tapestry web application.
    058 */
    059public class HibernateModule
    060{
    061    public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration)
    062    {
    063        configuration.add(HibernateSymbols.PROVIDE_ENTITY_VALUE_ENCODERS, "true");
    064        configuration.add(HibernateSymbols.ENTITY_SESSION_STATE_PERSISTENCE_STRATEGY_ENABLED, "false");
    065    }
    066
    067    /**
    068     * Contributes the package "&lt;root&gt;.entities" to the configuration, so that it will be scanned for annotated
    069     * entity classes.
    070     */
    071    public static void contributeHibernateEntityPackageManager(Configuration<String> configuration,
    072
    073                                                               @Symbol(TapestryHttpInternalConstants.TAPESTRY_APP_PACKAGE_PARAM)
    074                                                               String appRootPackage)
    075    {
    076        configuration.add(appRootPackage + ".entities");
    077    }
    078
    079    @Contribute(ServiceOverride.class)
    080    public static void provideInjectableSessionObject(MappedConfiguration<Class, Object> configuration, @HibernateCore
    081    Session session)
    082    {
    083        configuration.add(Session.class, session);
    084    }
    085
    086    /**
    087     * Contributes {@link ValueEncoderFactory}s for all registered Hibernate entity classes. Encoding and decoding are
    088     * based on the id property value of the entity using type coercion. Hence, if the id can be coerced to a String and
    089     * back then the entity can be coerced.
    090     */
    091    @SuppressWarnings("unchecked")
    092    public static void contributeValueEncoderSource(
    093            MappedConfiguration<Class, ValueEncoderFactory> configuration,
    094            @Symbol(HibernateSymbols.PROVIDE_ENTITY_VALUE_ENCODERS) boolean provideEncoders,
    095            final HibernateSessionSource sessionSource, final Session session,
    096            final TypeCoercer typeCoercer, final PropertyAccess propertyAccess,
    097            final LoggerSource loggerSource, final Logger logger)
    098    {
    099        if (!provideEncoders)
    100            return;
    101
    102        Set<EntityType<?>> entities = sessionSource.getSessionFactory().getMetamodel().getEntities();
    103        for (EntityType<?> entityType : entities)
    104        {
    105            Class<?> entityClass = entityType.getJavaType();
    106            if (entityClass != null)
    107            {
    108                if (entityType.hasSingleIdAttribute())
    109                {
    110                    SingularAttribute<?, ?> id = entityType.getId(entityType.getIdType().getJavaType());
    111                    final String idenfierPropertyName = id.getName();
    112                    ValueEncoderFactory factory = new ValueEncoderFactory()
    113                    {
    114                        @Override
    115                        public ValueEncoder create(Class type)
    116                        {
    117                            return new HibernateEntityValueEncoder(entityClass, idenfierPropertyName,
    118                                    session, propertyAccess, typeCoercer,
    119                                    loggerSource.getLogger(entityClass));
    120                        }
    121                    };
    122
    123                    configuration.add(entityClass, factory);
    124                } else {
    125                    logger.warn("Not creating a value encoder for {} as it does not have a single id attribute.", entityClass);
    126                }
    127            }
    128        }
    129    }
    130
    131    /**
    132     * Contributes the following:
    133     * <dl>
    134     * <dt>entity</dt>
    135     * <dd>Stores the id of the entity and reloads from the {@link Session}</dd>
    136     * </dl>
    137     */
    138    public static void contributePersistentFieldManager(
    139            MappedConfiguration<String, PersistentFieldStrategy> configuration)
    140    {
    141        configuration.addInstance(HibernatePersistenceConstants.ENTITY, EntityPersistentFieldStrategy.class);
    142    }
    143
    144    /**
    145     * Contributes the following strategy:
    146     * <dl>
    147     * <dt>entity</dt>
    148     * <dd>Stores the id of the entity and reloads from the {@link Session}</dd>
    149     * </dl>
    150     */
    151    public void contributeApplicationStatePersistenceStrategySource(
    152            MappedConfiguration<String, ApplicationStatePersistenceStrategy> configuration)
    153    {
    154        configuration
    155                .addInstance(HibernatePersistenceConstants.ENTITY, EntityApplicationStatePersistenceStrategy.class);
    156    }
    157
    158    /**
    159     * Contributes {@link ApplicationStateContribution}s for all registered Hibernate entity classes.
    160     *
    161     * @param configuration
    162     *         Configuration to contribute
    163     * @param entitySessionStatePersistenceStrategyEnabled
    164     *         indicates if contribution should take place
    165     * @param sessionSource
    166     *         creates Hibernate session
    167     */
    168    public static void contributeApplicationStateManager(
    169            final MappedConfiguration<Class, ApplicationStateContribution> configuration,
    170            @Symbol(HibernateSymbols.ENTITY_SESSION_STATE_PERSISTENCE_STRATEGY_ENABLED)
    171            boolean entitySessionStatePersistenceStrategyEnabled, HibernateSessionSource sessionSource)
    172    {
    173
    174        if (!entitySessionStatePersistenceStrategyEnabled)
    175            return;
    176
    177        sessionSource.getSessionFactory().getMetamodel().getEntities().stream()
    178                .map(EntityType::getJavaType)
    179                .filter(Objects::nonNull)
    180                .forEach(e -> configuration.add(e, new ApplicationStateContribution(HibernatePersistenceConstants.ENTITY)));
    181
    182    }
    183
    184    /**
    185     * Adds the CommitAfter annotation work, to process the
    186     * {@link org.apache.tapestry5.hibernate.annotations.CommitAfter} annotation.
    187     */
    188    @Contribute(ComponentClassTransformWorker2.class)
    189    @Primary
    190    public static void provideCommitAfterAnnotationSupport(
    191            OrderedConfiguration<ComponentClassTransformWorker2> configuration)
    192    {
    193        // If logging is enabled, we want logging to be the first advice, wrapping around the commit advice.
    194
    195        configuration.addInstance("CommitAfter", CommitAfterWorker.class, "after:Log");
    196    }
    197
    198    @Contribute(DashboardManager.class)
    199    public static void provideHibernateDashboardTab(OrderedConfiguration<DashboardTab> configuration)
    200    {
    201        configuration.add("HibernateStatistics", new DashboardTab("Hibernate", "hibernate/HibernateStatistics"), "after:Services");
    202    }
    203
    204    @Contribute(ComponentClassResolver.class)
    205    public static void provideLibraryMapping(Configuration<LibraryMapping> configuration)
    206    {
    207        configuration.add(new LibraryMapping("hibernate", "org.apache.tapestry5.hibernate.web"));
    208    }
    209}