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