001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.store.memory;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.LinkedHashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ConcurrentMap;
028
029import org.apache.activemq.broker.ConnectionContext;
030import org.apache.activemq.command.Message;
031import org.apache.activemq.command.MessageAck;
032import org.apache.activemq.command.MessageId;
033import org.apache.activemq.command.TransactionId;
034import org.apache.activemq.command.XATransactionId;
035import org.apache.activemq.store.InlineListenableFuture;
036import org.apache.activemq.store.ListenableFuture;
037import org.apache.activemq.store.MessageStore;
038import org.apache.activemq.store.PersistenceAdapter;
039import org.apache.activemq.store.ProxyMessageStore;
040import org.apache.activemq.store.ProxyTopicMessageStore;
041import org.apache.activemq.store.TopicMessageStore;
042import org.apache.activemq.store.TransactionRecoveryListener;
043import org.apache.activemq.store.TransactionStore;
044
045/**
046 * Provides a TransactionStore implementation that can create transaction aware
047 * MessageStore objects from non transaction aware MessageStore objects.
048 */
049public class MemoryTransactionStore implements TransactionStore {
050
051    protected ConcurrentMap<Object, Tx> inflightTransactions = new ConcurrentHashMap<Object, Tx>();
052    protected Map<TransactionId, Tx> preparedTransactions = Collections.synchronizedMap(new LinkedHashMap<TransactionId, Tx>());
053    protected final PersistenceAdapter persistenceAdapter;
054
055    private boolean doingRecover;
056
057    public class Tx {
058
059        public List<AddMessageCommand> messages = Collections.synchronizedList(new ArrayList<AddMessageCommand>());
060
061        public final List<RemoveMessageCommand> acks = Collections.synchronizedList(new ArrayList<RemoveMessageCommand>());
062
063        public void add(AddMessageCommand msg) {
064            messages.add(msg);
065        }
066
067        public void add(RemoveMessageCommand ack) {
068            acks.add(ack);
069        }
070
071        public Message[] getMessages() {
072            Message rc[] = new Message[messages.size()];
073            int count = 0;
074            for (Iterator<AddMessageCommand> iter = messages.iterator(); iter.hasNext();) {
075                AddMessageCommand cmd = iter.next();
076                rc[count++] = cmd.getMessage();
077            }
078            return rc;
079        }
080
081        public MessageAck[] getAcks() {
082            MessageAck rc[] = new MessageAck[acks.size()];
083            int count = 0;
084            for (Iterator<RemoveMessageCommand> iter = acks.iterator(); iter.hasNext();) {
085                RemoveMessageCommand cmd = iter.next();
086                rc[count++] = cmd.getMessageAck();
087            }
088            return rc;
089        }
090
091        /**
092         * @throws IOException
093         */
094        public void commit() throws IOException {
095            ConnectionContext ctx = new ConnectionContext();
096            persistenceAdapter.beginTransaction(ctx);
097            try {
098
099                // Do all the message adds.
100                for (Iterator<AddMessageCommand> iter = messages.iterator(); iter.hasNext();) {
101                    AddMessageCommand cmd = iter.next();
102                    cmd.run(ctx);
103                }
104                // And removes..
105                for (Iterator<RemoveMessageCommand> iter = acks.iterator(); iter.hasNext();) {
106                    RemoveMessageCommand cmd = iter.next();
107                    cmd.run(ctx);
108                }
109
110                persistenceAdapter.commitTransaction(ctx);
111
112            } catch (IOException e) {
113                persistenceAdapter.rollbackTransaction(ctx);
114                throw e;
115            }
116        }
117    }
118
119    public interface AddMessageCommand {
120        Message getMessage();
121
122        MessageStore getMessageStore();
123
124        void run(ConnectionContext context) throws IOException;
125
126        void setMessageStore(MessageStore messageStore);
127    }
128
129    public interface RemoveMessageCommand {
130        MessageAck getMessageAck();
131
132        void run(ConnectionContext context) throws IOException;
133
134        MessageStore getMessageStore();
135    }
136
137    public MemoryTransactionStore(PersistenceAdapter persistenceAdapter) {
138        this.persistenceAdapter = persistenceAdapter;
139    }
140
141    public MessageStore proxy(MessageStore messageStore) {
142        ProxyMessageStore proxyMessageStore = new ProxyMessageStore(messageStore) {
143            @Override
144            public void addMessage(ConnectionContext context, final Message send) throws IOException {
145                MemoryTransactionStore.this.addMessage(context, getDelegate(), send);
146            }
147
148            @Override
149            public void addMessage(ConnectionContext context, final Message send, boolean canOptimize) throws IOException {
150                MemoryTransactionStore.this.addMessage(context, getDelegate(), send);
151            }
152
153            @Override
154            public ListenableFuture<Object> asyncAddQueueMessage(ConnectionContext context, Message message) throws IOException {
155                MemoryTransactionStore.this.addMessage(context, getDelegate(), message);
156                return new InlineListenableFuture();
157            }
158
159            @Override
160            public ListenableFuture<Object> asyncAddQueueMessage(ConnectionContext context, Message message, boolean canoptimize) throws IOException {
161                MemoryTransactionStore.this.addMessage(context, getDelegate(), message);
162                return new InlineListenableFuture();
163            }
164
165            @Override
166            public void removeMessage(ConnectionContext context, final MessageAck ack) throws IOException {
167                MemoryTransactionStore.this.removeMessage(getDelegate(), ack);
168            }
169
170            @Override
171            public void removeAsyncMessage(ConnectionContext context, MessageAck ack) throws IOException {
172                MemoryTransactionStore.this.removeMessage(getDelegate(), ack);
173            }
174        };
175        onProxyQueueStore(proxyMessageStore);
176        return proxyMessageStore;
177    }
178
179    protected void onProxyQueueStore(ProxyMessageStore proxyMessageStore) {
180    }
181
182    public TopicMessageStore proxy(TopicMessageStore messageStore) {
183        ProxyTopicMessageStore proxyTopicMessageStore = new ProxyTopicMessageStore(messageStore) {
184            @Override
185            public void addMessage(ConnectionContext context, final Message send) throws IOException {
186                MemoryTransactionStore.this.addMessage(context, getDelegate(), send);
187            }
188
189            @Override
190            public void addMessage(ConnectionContext context, final Message send, boolean canOptimize) throws IOException {
191                MemoryTransactionStore.this.addMessage(context, getDelegate(), send);
192            }
193
194            @Override
195            public ListenableFuture<Object> asyncAddTopicMessage(ConnectionContext context, Message message) throws IOException {
196                MemoryTransactionStore.this.addMessage(context, getDelegate(), message);
197                return new InlineListenableFuture();
198            }
199
200            @Override
201            public ListenableFuture<Object> asyncAddTopicMessage(ConnectionContext context, Message message, boolean canOptimize) throws IOException {
202                MemoryTransactionStore.this.addMessage(context, getDelegate(), message);
203                return new InlineListenableFuture();
204            }
205
206            @Override
207            public void removeMessage(ConnectionContext context, final MessageAck ack) throws IOException {
208                MemoryTransactionStore.this.removeMessage(getDelegate(), ack);
209            }
210
211            @Override
212            public void removeAsyncMessage(ConnectionContext context, MessageAck ack) throws IOException {
213                MemoryTransactionStore.this.removeMessage(getDelegate(), ack);
214            }
215
216            @Override
217            public void acknowledge(ConnectionContext context, String clientId, String subscriptionName, MessageId messageId, MessageAck ack)
218                throws IOException {
219                MemoryTransactionStore.this.acknowledge((TopicMessageStore) getDelegate(), clientId, subscriptionName, messageId, ack);
220            }
221        };
222        onProxyTopicStore(proxyTopicMessageStore);
223        return proxyTopicMessageStore;
224    }
225
226    protected void onProxyTopicStore(ProxyTopicMessageStore proxyTopicMessageStore) {
227    }
228
229    /**
230     * @see org.apache.activemq.store.TransactionStore#prepare(TransactionId)
231     */
232    @Override
233    public void prepare(TransactionId txid) throws IOException {
234        Tx tx = inflightTransactions.remove(txid);
235        if (tx == null) {
236            return;
237        }
238        preparedTransactions.put(txid, tx);
239    }
240
241    public Tx getTx(Object txid) {
242        Tx tx = inflightTransactions.get(txid);
243        if (tx == null) {
244            synchronized (inflightTransactions) {
245                tx = inflightTransactions.get(txid);
246                if ( tx == null) {
247                    tx = new Tx();
248                    inflightTransactions.put(txid, tx);
249                }
250            }
251        }
252        return tx;
253    }
254
255    public Tx getPreparedTx(TransactionId txid) {
256        Tx tx = preparedTransactions.get(txid);
257        if (tx == null) {
258            tx = new Tx();
259            preparedTransactions.put(txid, tx);
260        }
261        return tx;
262    }
263
264    @Override
265    public void commit(TransactionId txid, boolean wasPrepared, Runnable preCommit, Runnable postCommit) throws IOException {
266        if (preCommit != null) {
267            preCommit.run();
268        }
269        Tx tx;
270        if (wasPrepared) {
271            tx = preparedTransactions.get(txid);
272        } else {
273            tx = inflightTransactions.remove(txid);
274        }
275
276        if (tx != null) {
277            tx.commit();
278        }
279        if (wasPrepared) {
280            preparedTransactions.remove(txid);
281        }
282        if (postCommit != null) {
283            postCommit.run();
284        }
285    }
286
287    /**
288     * @see org.apache.activemq.store.TransactionStore#rollback(TransactionId)
289     */
290    @Override
291    public void rollback(TransactionId txid) throws IOException {
292        preparedTransactions.remove(txid);
293        inflightTransactions.remove(txid);
294    }
295
296    @Override
297    public void start() throws Exception {
298    }
299
300    @Override
301    public void stop() throws Exception {
302    }
303
304    @Override
305    public synchronized void recover(TransactionRecoveryListener listener) throws IOException {
306        // All the inflight transactions get rolled back..
307        inflightTransactions.clear();
308        this.doingRecover = true;
309        try {
310            for (Iterator<TransactionId> iter = preparedTransactions.keySet().iterator(); iter.hasNext();) {
311                Object txid = iter.next();
312                Tx tx = preparedTransactions.get(txid);
313                listener.recover((XATransactionId) txid, tx.getMessages(), tx.getAcks());
314                onRecovered(tx);
315            }
316        } finally {
317            this.doingRecover = false;
318        }
319    }
320
321    protected void onRecovered(Tx tx) {
322    }
323
324    /**
325     * @param message
326     * @throws IOException
327     */
328    void addMessage(final ConnectionContext context, final MessageStore destination, final Message message) throws IOException {
329
330        if (doingRecover) {
331            return;
332        }
333
334        if (message.getTransactionId() != null) {
335            Tx tx = getTx(message.getTransactionId());
336            tx.add(new AddMessageCommand() {
337                @SuppressWarnings("unused")
338                MessageStore messageStore = destination;
339
340                @Override
341                public Message getMessage() {
342                    return message;
343                }
344
345                @Override
346                public MessageStore getMessageStore() {
347                    return destination;
348                }
349
350                @Override
351                public void run(ConnectionContext ctx) throws IOException {
352                    destination.addMessage(ctx, message);
353                }
354
355                @Override
356                public void setMessageStore(MessageStore messageStore) {
357                    this.messageStore = messageStore;
358                }
359
360            });
361        } else {
362            destination.addMessage(context, message);
363        }
364    }
365
366    /**
367     * @param ack
368     * @throws IOException
369     */
370    final void removeMessage(final MessageStore destination, final MessageAck ack) throws IOException {
371        if (doingRecover) {
372            return;
373        }
374
375        if (ack.isInTransaction()) {
376            Tx tx = getTx(ack.getTransactionId());
377            tx.add(new RemoveMessageCommand() {
378                @Override
379                public MessageAck getMessageAck() {
380                    return ack;
381                }
382
383                @Override
384                public void run(ConnectionContext ctx) throws IOException {
385                    destination.removeMessage(ctx, ack);
386                }
387
388                @Override
389                public MessageStore getMessageStore() {
390                    return destination;
391                }
392            });
393        } else {
394            destination.removeMessage(null, ack);
395        }
396    }
397
398    public void acknowledge(final TopicMessageStore destination, final String clientId, final String subscriptionName, final MessageId messageId,
399        final MessageAck ack) throws IOException {
400        if (doingRecover) {
401            return;
402        }
403
404        if (ack.isInTransaction()) {
405            Tx tx = getTx(ack.getTransactionId());
406            tx.add(new RemoveMessageCommand() {
407                @Override
408                public MessageAck getMessageAck() {
409                    return ack;
410                }
411
412                @Override
413                public void run(ConnectionContext ctx) throws IOException {
414                    destination.acknowledge(ctx, clientId, subscriptionName, messageId, ack);
415                }
416
417                @Override
418                public MessageStore getMessageStore() {
419                    return destination;
420                }
421            });
422        } else {
423            destination.acknowledge(null, clientId, subscriptionName, messageId, ack);
424        }
425    }
426
427    public void delete() {
428        inflightTransactions.clear();
429        preparedTransactions.clear();
430        doingRecover = false;
431    }
432}