/* * 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.activemq.artemis.jms.persistence.impl.journal; import java.io.File; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQBuffers; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.io.SequentialFileFactory; import org.apache.activemq.artemis.core.io.nio.NIOSequentialFileFactory; import org.apache.activemq.artemis.core.journal.Journal; import org.apache.activemq.artemis.core.journal.PreparedTransactionInfo; import org.apache.activemq.artemis.core.journal.RecordInfo; import org.apache.activemq.artemis.core.journal.impl.JournalImpl; import org.apache.activemq.artemis.core.replication.ReplicatedJournal; import org.apache.activemq.artemis.core.replication.ReplicationManager; import org.apache.activemq.artemis.core.server.JournalType; import org.apache.activemq.artemis.jms.persistence.JMSStorageManager; import org.apache.activemq.artemis.jms.persistence.config.PersistedBindings; import org.apache.activemq.artemis.jms.persistence.config.PersistedConnectionFactory; import org.apache.activemq.artemis.jms.persistence.config.PersistedDestination; import org.apache.activemq.artemis.jms.persistence.config.PersistedType; import org.apache.activemq.artemis.utils.ExecutorFactory; import org.apache.activemq.artemis.utils.IDGenerator; public final class JMSJournalStorageManagerImpl implements JMSStorageManager { // Constants ----------------------------------------------------- public static final byte CF_RECORD = 1; public static final byte DESTINATION_RECORD = 2; public static final byte BINDING_RECORD = 3; // Attributes ---------------------------------------------------- private final IDGenerator idGenerator; private final boolean createDir; private final Journal jmsJournal; private volatile boolean started; private final Map<String, PersistedConnectionFactory> mapFactories = new ConcurrentHashMap<>(); private final Map<Pair<PersistedType, String>, PersistedDestination> destinations = new ConcurrentHashMap<>(); private final Map<Pair<PersistedType, String>, PersistedBindings> mapBindings = new ConcurrentHashMap<>(); private final Configuration config; // Static -------------------------------------------------------- // Constructors -------------------------------------------------- public JMSJournalStorageManagerImpl(ExecutorFactory ioExecutors, final IDGenerator idGenerator, final Configuration config, final ReplicationManager replicator) { final EnumSet<JournalType> supportedJournalTypes = EnumSet.allOf(JournalType.class); if (!supportedJournalTypes.contains(config.getJournalType())) { throw new IllegalArgumentException("Only " + supportedJournalTypes + " are supported Journal types"); } this.config = config; createDir = config.isCreateBindingsDir(); SequentialFileFactory bindingsJMS = new NIOSequentialFileFactory(config.getBindingsLocation(), 1); Journal localJMS = new JournalImpl(ioExecutors, 1024 * 1024, 2, config.getJournalPoolFiles(), config.getJournalCompactMinFiles(), config.getJournalCompactPercentage(), bindingsJMS, "activemq-jms", "jms", 1, 0); if (replicator != null) { jmsJournal = new ReplicatedJournal((byte) 2, localJMS, replicator); } else { jmsJournal = localJMS; } this.idGenerator = idGenerator; } // Public -------------------------------------------------------- @Override public List<PersistedConnectionFactory> recoverConnectionFactories() { List<PersistedConnectionFactory> cfs = new ArrayList<>(mapFactories.values()); return cfs; } @Override public void storeConnectionFactory(final PersistedConnectionFactory connectionFactory) throws Exception { deleteConnectionFactory(connectionFactory.getName()); long id = idGenerator.generateID(); connectionFactory.setId(id); jmsJournal.appendAddRecord(id, CF_RECORD, connectionFactory, true); mapFactories.put(connectionFactory.getName(), connectionFactory); } @Override public void deleteConnectionFactory(final String cfName) throws Exception { PersistedConnectionFactory oldCF = mapFactories.remove(cfName); if (oldCF != null) { jmsJournal.appendDeleteRecord(oldCF.getId(), false); } } @Override public List<PersistedDestination> recoverDestinations() { List<PersistedDestination> destinations = new ArrayList<>(this.destinations.values()); return destinations; } @Override public void storeDestination(final PersistedDestination destination) throws Exception { deleteDestination(destination.getType(), destination.getName()); long id = idGenerator.generateID(); destination.setId(id); jmsJournal.appendAddRecord(id, DESTINATION_RECORD, destination, true); destinations.put(new Pair<>(destination.getType(), destination.getName()), destination); } @Override public List<PersistedBindings> recoverPersistedBindings() throws Exception { ArrayList<PersistedBindings> list = new ArrayList<>(mapBindings.values()); return list; } @Override public void addBindings(PersistedType type, String name, String... address) throws Exception { Pair<PersistedType, String> key = new Pair<>(type, name); long tx = idGenerator.generateID(); PersistedBindings currentBindings = mapBindings.get(key); if (currentBindings != null) { jmsJournal.appendDeleteRecordTransactional(tx, currentBindings.getId()); } else { currentBindings = new PersistedBindings(type, name); } mapBindings.put(key, currentBindings); for (String adItem : address) { currentBindings.addBinding(adItem); } long newId = idGenerator.generateID(); currentBindings.setId(newId); jmsJournal.appendAddRecordTransactional(tx, newId, BINDING_RECORD, currentBindings); jmsJournal.appendCommitRecord(tx, true); } @Override public void deleteBindings(PersistedType type, String name, String address) throws Exception { Pair<PersistedType, String> key = new Pair<>(type, name); long tx = idGenerator.generateID(); PersistedBindings currentBindings = mapBindings.get(key); if (currentBindings == null) { return; } else { jmsJournal.appendDeleteRecordTransactional(tx, currentBindings.getId()); } currentBindings.deleteBinding(address); if (currentBindings.getBindings().size() == 0) { mapBindings.remove(key); } else { long newId = idGenerator.generateID(); currentBindings.setId(newId); jmsJournal.appendAddRecordTransactional(tx, newId, BINDING_RECORD, currentBindings); } jmsJournal.appendCommitRecord(tx, true); } @Override public void deleteBindings(PersistedType type, String name) throws Exception { Pair<PersistedType, String> key = new Pair<>(type, name); PersistedBindings currentBindings = mapBindings.remove(key); if (currentBindings != null) { jmsJournal.appendDeleteRecord(currentBindings.getId(), true); } } @Override public void deleteDestination(final PersistedType type, final String name) throws Exception { PersistedDestination destination = destinations.remove(new Pair<>(type, name)); if (destination != null) { jmsJournal.appendDeleteRecord(destination.getId(), false); } } @Override public boolean isStarted() { return started; } @Override public void start() throws Exception { checkAndCreateDir(config.getBindingsLocation(), createDir); jmsJournal.start(); started = true; } @Override public void stop() throws Exception { this.started = false; jmsJournal.stop(); } @Override public void load() throws Exception { mapFactories.clear(); List<RecordInfo> data = new ArrayList<>(); ArrayList<PreparedTransactionInfo> list = new ArrayList<>(); jmsJournal.load(data, list, null); for (RecordInfo record : data) { long id = record.id; ActiveMQBuffer buffer = ActiveMQBuffers.wrappedBuffer(record.data); byte rec = record.getUserRecordType(); if (rec == CF_RECORD) { PersistedConnectionFactory cf = new PersistedConnectionFactory(); cf.decode(buffer); cf.setId(id); mapFactories.put(cf.getName(), cf); } else if (rec == DESTINATION_RECORD) { PersistedDestination destination = new PersistedDestination(); destination.decode(buffer); destination.setId(id); destinations.put(new Pair<>(destination.getType(), destination.getName()), destination); } else if (rec == BINDING_RECORD) { PersistedBindings bindings = new PersistedBindings(); bindings.decode(buffer); bindings.setId(id); Pair<PersistedType, String> key = new Pair<>(bindings.getType(), bindings.getName()); mapBindings.put(key, bindings); } else { throw new IllegalStateException("Invalid record type " + rec); } } } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- // Private ------------------------------------------------------- private void checkAndCreateDir(final File dir, final boolean create) { if (!dir.exists()) { if (create) { if (!dir.mkdirs()) { throw new IllegalStateException("Failed to create directory " + dir); } } else { throw new IllegalArgumentException("Directory " + dir + " does not exist and will not create it"); } } } // Inner classes ------------------------------------------------- }