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}