| /* |
| * 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(); |
| } |
| } |
| } |
| } |