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