/* * JBoss, Home of Professional Open Source * Copyright 2010, Red Hat, Inc. and/or its affiliates, * and individual contributors as indicated by the @author tags. * See the copyright.txt in the distribution for a * full listing of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * (C) 2010, * @author JBoss, by Red Hat. */ package com.arjuna.ats.internal.arjuna.objectstore.hornetq; import java.io.File; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.activemq.artemis.core.journal.Journal; import org.apache.activemq.artemis.core.journal.JournalLoadInformation; import org.apache.activemq.artemis.core.journal.PreparedTransactionInfo; import org.apache.activemq.artemis.core.journal.RecordInfo; import org.apache.activemq.artemis.core.io.SequentialFileFactory; import org.apache.activemq.artemis.core.journal.TransactionFailureCallback; import org.apache.activemq.artemis.core.io.aio.AIOSequentialFileFactory; import org.apache.activemq.artemis.core.journal.impl.JournalImpl; import org.apache.activemq.artemis.core.io.nio.NIOSequentialFileFactory; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.arjuna.exceptions.ObjectStoreException; import com.arjuna.ats.arjuna.logging.tsLogger; import com.arjuna.ats.arjuna.state.InputBuffer; import com.arjuna.ats.arjuna.state.InputObjectState; import com.arjuna.ats.arjuna.state.OutputBuffer; import com.arjuna.ats.arjuna.state.OutputObjectState; import com.arjuna.ats.internal.arjuna.common.UidHelper; /** * Implementation of the tx store backed by HornetQ's journal. * This is a bean suitable for hooking into the app server lifecycle. * * @author Jonathan Halliday (jonathan.halliday@redhat.com), 2010-03 */ public class HornetqJournalStore { private final Journal journal; private final ConcurrentMap<String,ConcurrentMap<Uid, RecordInfo>> content = new ConcurrentHashMap<String, ConcurrentMap<Uid, RecordInfo>>(); private final boolean syncWrites; private final boolean syncDeletes; private final AtomicLong maxID = new AtomicLong(0); private final String storeDirCanonicalPath; private static final byte RECORD_TYPE = 0x00; public void stop() throws Exception { journal.stop(); } public void start() throws Exception { journal.start(); List<RecordInfo> committedRecords = new LinkedList<RecordInfo>(); List<PreparedTransactionInfo> preparedTransactions = new LinkedList<PreparedTransactionInfo>(); TransactionFailureCallback failureCallback = new TransactionFailureCallback() { public void failedTransaction(long l, List<RecordInfo> recordInfos, List<RecordInfo> recordInfos1) { tsLogger.i18NLogger.warn_journal_load_error(); } }; JournalLoadInformation journalLoadInformation = journal.load(committedRecords, preparedTransactions, failureCallback); maxID.set(journalLoadInformation.getMaxID()); if(!preparedTransactions.isEmpty()) { tsLogger.i18NLogger.warn_journal_load_error(); } for(RecordInfo record : committedRecords) { InputBuffer inputBuffer = new InputBuffer(record.data); Uid uid = UidHelper.unpackFrom(inputBuffer); String typeName = inputBuffer.unpackString(); getContentForType(typeName).put(uid, record); // don't unpack the rest yet, we may never need it. read_committed does it on demand. } } public HornetqJournalStore(HornetqJournalEnvironmentBean envBean) throws IOException { syncWrites = envBean.isSyncWrites(); syncDeletes = envBean.isSyncDeletes(); File storeDir = new File(envBean.getStoreDir()); if(!storeDir.exists() && !storeDir.mkdirs()) { throw new IOException(tsLogger.i18NLogger.get_dir_create_failed(storeDir.getCanonicalPath())); } storeDirCanonicalPath = storeDir.getCanonicalPath(); SequentialFileFactory sequentialFileFactory; if(envBean.isAsyncIO() && AIOSequentialFileFactory.isSupported()) { sequentialFileFactory = new AIOSequentialFileFactory( storeDir, envBean.getBufferSize(), (int)(1000000000d / envBean.getBufferFlushesPerSecond()), // bufferTimeout nanos .000000001 second envBean.getMaxIO(), envBean.isLogRates()); } else { sequentialFileFactory = new NIOSequentialFileFactory( storeDir, true, envBean.getBufferSize(), (int)(1000000000d / envBean.getBufferFlushesPerSecond()), // bufferTimeout nanos .000000001 second envBean.getMaxIO(), envBean.isLogRates()); } journal = new JournalImpl(envBean.getFileSize(), envBean.getMinFiles(), envBean.getPoolSize(), envBean.getCompactMinFiles(), envBean.getCompactPercentage(), sequentialFileFactory, envBean.getFilePrefix(), envBean.getFileExtension(), envBean.getMaxIO()); } /** * Remove the object's committed state. * * @param uid The object to work on. * @param typeName The type of the object to work on. * @return <code>true</code> if no errors occurred, <code>false</code> * otherwise. * @throws ObjectStoreException if things go wrong. */ public boolean remove_committed(Uid uid, String typeName) throws ObjectStoreException { try { RecordInfo record = getContentForType(typeName).remove(uid); long id = (record != null ? record.id : getId(uid, typeName)); journal.appendDeleteRecord(id, syncDeletes); return true; } catch (IllegalStateException e) { tsLogger.i18NLogger.warn_hornetqobjectstore_remove_state_exception(e); return false; } catch(Exception e) { throw new ObjectStoreException(e); } } /** * Write a new copy of the object's committed state. * * @param uid The object to work on. * @param typeName The type of the object to work on. * @param txData The state to write. * @return <code>true</code> if no errors occurred, <code>false</code> * otherwise. * @throws ObjectStoreException if things go wrong. */ public boolean write_committed(Uid uid, String typeName, OutputObjectState txData) throws ObjectStoreException { RecordInfo previousRecord = null; try { OutputBuffer outputBuffer = new OutputBuffer(); UidHelper.packInto(uid, outputBuffer); outputBuffer.packString(typeName); outputBuffer.packBytes(txData.buffer()); byte[] data = outputBuffer.buffer(); RecordInfo record = new RecordInfo(getId(uid, typeName), RECORD_TYPE, data, false, (short)0); previousRecord = getContentForType(typeName).putIfAbsent(uid, record); if(previousRecord != null) { // the packed data may have changed so updated the map with the latest data getContentForType(typeName).replace(uid, record); journal.appendUpdateRecord(previousRecord.id, RECORD_TYPE, data, syncWrites); } else { journal.appendAddRecord(record.id, RECORD_TYPE, data, syncWrites); } } catch(Exception e) { if (previousRecord == null) { // if appendAddRecord() fails, remove record from map. Leave it there if appendUpdateRecord() fails. getContentForType(typeName).remove(uid); } throw new ObjectStoreException(e); } return true; } /** * Read the object's committed state. * * @param uid The object to work on. * @param typeName The type of the object to work on. * @return the state of the object. * @throws ObjectStoreException if things go wrong. */ public InputObjectState read_committed(Uid uid, String typeName) throws ObjectStoreException { RecordInfo record = getContentForType(typeName).get(uid); if(record == null) { return null; } // this repeated unpacking is a little inefficient - subclass RecordInfo to hold unpacked form too? // not too much of an issue as log reads are done for recovery only. try { InputBuffer inputBuffer = new InputBuffer(record.data); UidHelper.unpackFrom(inputBuffer); inputBuffer.unpackString(); return new InputObjectState(uid, typeName, inputBuffer.unpackBytes()); } catch(Exception e) { throw new ObjectStoreException(e); } } public boolean contains(Uid uid, String typeName) { return getContentForType(typeName).containsKey(uid); } /** * @return the "name" of the object store. Where in the hierarchy it appears, e.g., /ObjectStore/MyName/... */ public String getStoreName() { return this.getClass().getSimpleName()+":"+storeDirCanonicalPath; } public String[] getKnownTypes() { return content.keySet().toArray(new String[content.size()]); } public Uid[] getUidsForType(String typeName) { Set<Uid> keySet = getContentForType(typeName).keySet(); return keySet.toArray(new Uid[keySet.size()]); } ///////////////////////////////// private ConcurrentMap<Uid, RecordInfo> getContentForType(String typeName) { ConcurrentMap<Uid, RecordInfo> result = content.get(typeName); if(result == null) { ConcurrentHashMap<Uid, RecordInfo> newMap = new ConcurrentHashMap<Uid, RecordInfo>(); result = content.putIfAbsent(typeName, newMap); if(result == null) { result = newMap; } } return result; } private long getId(Uid uid, String typeName) { RecordInfo record = getContentForType(typeName).get(uid); if(record != null) { return record.id; } else { return maxID.incrementAndGet(); } } }