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;
053
054/**
055 * Supplements the services defined by {@link org.apache.tapestry5.hibernate.modules.HibernateCoreModule} with additional
056 * services and configuration specific to Tapestry web application.
057 */
058public class HibernateModule
059{
060    public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration)
061    {
062        configuration.add(HibernateSymbols.PROVIDE_ENTITY_VALUE_ENCODERS, "true");
063        configuration.add(HibernateSymbols.ENTITY_SESSION_STATE_PERSISTENCE_STRATEGY_ENABLED, "false");
064    }
065
066    /**
067     * Contributes the package "&lt;root&gt;.entities" to the configuration, so that it will be scanned for annotated
068     * entity classes.
069     */
070    public static void contributeHibernateEntityPackageManager(Configuration<String> configuration,
071
072                                                               @Symbol(TapestryHttpInternalConstants.TAPESTRY_APP_PACKAGE_PARAM)
073                                                               String appRootPackage)
074    {
075        configuration.add(appRootPackage + ".entities");
076    }
077
078    @Contribute(ServiceOverride.class)
079    public static void provideInjectableSessionObject(MappedConfiguration<Class, Object> configuration, @HibernateCore
080    Session session)
081    {
082        configuration.add(Session.class, session);
083    }
084
085    /**
086     * Contributes {@link ValueEncoderFactory}s for all registered Hibernate entity classes. Encoding and decoding are
087     * based on the id property value of the entity using type coercion. Hence, if the id can be coerced to a String and
088     * back then the entity can be coerced.
089     */
090    @SuppressWarnings("unchecked")
091    public static void contributeValueEncoderSource(
092            MappedConfiguration<Class, ValueEncoderFactory> configuration,
093            @Symbol(HibernateSymbols.PROVIDE_ENTITY_VALUE_ENCODERS) boolean provideEncoders,
094            final HibernateSessionSource sessionSource, final Session session,
095            final TypeCoercer typeCoercer, final PropertyAccess propertyAccess,
096            final LoggerSource loggerSource)
097    {
098        if (!provideEncoders)
099            return;
100
101        Set<EntityType<?>> entities = sessionSource.getSessionFactory().getMetamodel().getEntities();
102        for (EntityType<?> entityType : entities)
103        {
104            Class<?> entityClass = entityType.getJavaType();
105            if (entityClass != null)
106            {
107                SingularAttribute<?, ?> id = entityType.getId(entityType.getIdType().getJavaType());
108                final String idenfierPropertyName = id.getName();
109                ValueEncoderFactory factory = new ValueEncoderFactory()
110                {
111                    @Override
112                    public ValueEncoder create(Class type)
113                    {
114                        return new HibernateEntityValueEncoder(entityClass, idenfierPropertyName,
115                                session, propertyAccess, typeCoercer,
116                                loggerSource.getLogger(entityClass));
117                    }
118                };
119
120                configuration.add(entityClass, factory);
121
122            }
123        }
124    }
125
126    /**
127     * Contributes the following:
128     * <dl>
129     * <dt>entity</dt>
130     * <dd>Stores the id of the entity and reloads from the {@link Session}</dd>
131     * </dl>
132     */
133    public static void contributePersistentFieldManager(
134            MappedConfiguration<String, PersistentFieldStrategy> configuration)
135    {
136        configuration.addInstance(HibernatePersistenceConstants.ENTITY, EntityPersistentFieldStrategy.class);
137    }
138
139    /**
140     * Contributes the following strategy:
141     * <dl>
142     * <dt>entity</dt>
143     * <dd>Stores the id of the entity and reloads from the {@link Session}</dd>
144     * </dl>
145     */
146    public void contributeApplicationStatePersistenceStrategySource(
147            MappedConfiguration<String, ApplicationStatePersistenceStrategy> configuration)
148    {
149        configuration
150                .addInstance(HibernatePersistenceConstants.ENTITY, EntityApplicationStatePersistenceStrategy.class);
151    }
152
153    /**
154     * Contributes {@link ApplicationStateContribution}s for all registered Hibernate entity classes.
155     *
156     * @param configuration
157     *         Configuration to contribute
158     * @param entitySessionStatePersistenceStrategyEnabled
159     *         indicates if contribution should take place
160     * @param sessionSource
161     *         creates Hibernate session
162     */
163    public static void contributeApplicationStateManager(
164            final MappedConfiguration<Class, ApplicationStateContribution> configuration,
165            @Symbol(HibernateSymbols.ENTITY_SESSION_STATE_PERSISTENCE_STRATEGY_ENABLED)
166            boolean entitySessionStatePersistenceStrategyEnabled, HibernateSessionSource sessionSource)
167    {
168
169        if (!entitySessionStatePersistenceStrategyEnabled)
170            return;
171        
172        sessionSource.getSessionFactory().getMetamodel().getEntities().stream()
173                .map(EntityType::getJavaType)
174                .filter(Objects::nonNull)
175                .forEach(e -> configuration.add(e, new ApplicationStateContribution(HibernatePersistenceConstants.ENTITY)));
176
177    }
178
179    /**
180     * Adds the CommitAfter annotation work, to process the
181     * {@link org.apache.tapestry5.hibernate.annotations.CommitAfter} annotation.
182     */
183    @Contribute(ComponentClassTransformWorker2.class)
184    @Primary
185    public static void provideCommitAfterAnnotationSupport(
186            OrderedConfiguration<ComponentClassTransformWorker2> configuration)
187    {
188        // If logging is enabled, we want logging to be the first advice, wrapping around the commit advice.
189
190        configuration.addInstance("CommitAfter", CommitAfterWorker.class, "after:Log");
191    }
192
193    @Contribute(DashboardManager.class)
194    public static void provideHibernateDashboardTab(OrderedConfiguration<DashboardTab> configuration)
195    {
196        configuration.add("HibernateStatistics", new DashboardTab("Hibernate", "hibernate/HibernateStatistics"), "after:Services");
197    }
198    
199    @Contribute(ComponentClassResolver.class)
200    public static void provideLibraryMapping(Configuration<LibraryMapping> configuration)
201    {
202        configuration.add(new LibraryMapping("hibernate", "org.apache.tapestry5.hibernate.web"));
203    }
204}