/* * 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.ignite.cache.store.hibernate; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import javax.cache.integration.CacheWriterException; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.cache.store.CacheStore; import org.apache.ignite.cache.store.CacheStoreSession; import org.apache.ignite.cache.store.CacheStoreSessionListener; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lifecycle.LifecycleAware; import org.apache.ignite.resources.LoggerResource; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.resource.transaction.spi.TransactionStatus; /** * Hibernate-based cache store session listener. * <p> * This listener creates a new Hibernate session for each store * session. If there is an ongoing cache transaction, a corresponding * Hibernate transaction is created as well. * <p> * The Hibernate session is saved as a store session * {@link CacheStoreSession#attachment() attachment}. * The listener guarantees that the session will be * available for any store operation. If there is an * ongoing cache transaction, all operations within this * transaction will share a DB transaction. * <p> * As an example, here is how the {@link CacheStore#write(javax.cache.Cache.Entry)} * method can be implemented if {@link CacheHibernateStoreSessionListener} * is configured: * <pre name="code" class="java"> * private static class Store extends CacheStoreAdapter<Integer, Integer> { * @CacheStoreSessionResource * private CacheStoreSession ses; * * @Override public void write(Cache.Entry<? extends Integer, ? extends Integer> entry) throws CacheWriterException { * // Get Hibernate session from the current store session. * Session hibSes = ses.attachment(); * * // Persist the value. * hibSes.persist(entry.getValue()); * } * } * </pre> * Hibernate session will be automatically created by the listener * at the start of the session and closed when it ends. * <p> * {@link CacheHibernateStoreSessionListener} requires that either * {@link #setSessionFactory(SessionFactory)} session factory} * or {@link #setHibernateConfigurationPath(String) Hibernate configuration file} * is provided. If non of them is set, exception is thrown. Is both are provided, * session factory will be used. */ public class CacheHibernateStoreSessionListener implements CacheStoreSessionListener, LifecycleAware { /** Hibernate session factory. */ private SessionFactory sesFactory; /** Hibernate configuration file path. */ private String hibernateCfgPath; /** Logger. */ @LoggerResource private IgniteLogger log; /** Whether to close session on stop. */ private boolean closeSesOnStop; /** * Sets Hibernate session factory. * <p> * Either session factory or configuration file is required. * If none is provided, exception will be thrown on startup. * * @param sesFactory Session factory. */ public void setSessionFactory(SessionFactory sesFactory) { this.sesFactory = sesFactory; } /** * Gets Hibernate session factory. * * @return Session factory. */ public SessionFactory getSessionFactory() { return sesFactory; } /** * Sets hibernate configuration path. * <p> * Either session factory or configuration file is required. * If none is provided, exception will be thrown on startup. * * @param hibernateCfgPath Hibernate configuration path. */ public void setHibernateConfigurationPath(String hibernateCfgPath) { this.hibernateCfgPath = hibernateCfgPath; } /** * Gets hibernate configuration path. * * @return Hibernate configuration path. */ public String getHibernateConfigurationPath() { return hibernateCfgPath; } /** {@inheritDoc} */ @SuppressWarnings("deprecation") @Override public void start() throws IgniteException { if (sesFactory == null && F.isEmpty(hibernateCfgPath)) throw new IgniteException("Either session factory or Hibernate configuration file is required by " + getClass().getSimpleName() + '.'); if (!F.isEmpty(hibernateCfgPath)) { if (sesFactory == null) { try { URL url = new URL(hibernateCfgPath); sesFactory = new Configuration().configure(url).buildSessionFactory(); } catch (MalformedURLException ignored) { // No-op. } if (sesFactory == null) { File cfgFile = new File(hibernateCfgPath); if (cfgFile.exists()) sesFactory = new Configuration().configure(cfgFile).buildSessionFactory(); } if (sesFactory == null) sesFactory = new Configuration().configure(hibernateCfgPath).buildSessionFactory(); if (sesFactory == null) throw new IgniteException("Failed to resolve Hibernate configuration file: " + hibernateCfgPath); closeSesOnStop = true; } else U.warn(log, "Hibernate configuration file configured in " + getClass().getSimpleName() + " will be ignored (session factory is already set)."); } } /** {@inheritDoc} */ @Override public void stop() throws IgniteException { if (closeSesOnStop && sesFactory != null && !sesFactory.isClosed()) sesFactory.close(); } /** {@inheritDoc} */ @Override public void onSessionStart(CacheStoreSession ses) { if (ses.attachment() == null) { try { Session hibSes = sesFactory.openSession(); ses.attach(hibSes); if (ses.isWithinTransaction()) hibSes.beginTransaction(); } catch (HibernateException e) { throw new CacheWriterException("Failed to start store session [tx=" + ses.transaction() + ']', e); } } } /** {@inheritDoc} */ @Override public void onSessionEnd(CacheStoreSession ses, boolean commit) { Session hibSes = ses.attach(null); if (hibSes != null) { try { Transaction tx = hibSes.getTransaction(); if (commit) { if (hibSes.isDirty()) hibSes.flush(); if (tx.getStatus() == TransactionStatus.ACTIVE) tx.commit(); } else if (tx.getStatus().canRollback()) tx.rollback(); } catch (HibernateException e) { throw new CacheWriterException("Failed to end store session [tx=" + ses.transaction() + ']', e); } finally { hibSes.close(); } } } }