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.kahadb;
018
019import java.io.DataInputStream;
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025import java.util.concurrent.CancellationException;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ExecutionException;
028import java.util.concurrent.Future;
029
030import org.apache.activemq.broker.ConnectionContext;
031import org.apache.activemq.command.Message;
032import org.apache.activemq.command.MessageAck;
033import org.apache.activemq.command.MessageId;
034import org.apache.activemq.command.TransactionId;
035import org.apache.activemq.command.XATransactionId;
036import org.apache.activemq.protobuf.Buffer;
037import org.apache.activemq.store.AbstractMessageStore;
038import org.apache.activemq.store.ListenableFuture;
039import org.apache.activemq.store.MessageStore;
040import org.apache.activemq.store.ProxyMessageStore;
041import org.apache.activemq.store.ProxyTopicMessageStore;
042import org.apache.activemq.store.TopicMessageStore;
043import org.apache.activemq.store.TransactionRecoveryListener;
044import org.apache.activemq.store.TransactionStore;
045import org.apache.activemq.store.kahadb.MessageDatabase.Operation;
046import org.apache.activemq.store.kahadb.data.KahaCommitCommand;
047import org.apache.activemq.store.kahadb.data.KahaPrepareCommand;
048import org.apache.activemq.store.kahadb.data.KahaRollbackCommand;
049import org.apache.activemq.store.kahadb.data.KahaTransactionInfo;
050import org.apache.activemq.wireformat.WireFormat;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053
054/**
055 * Provides a TransactionStore implementation that can create transaction aware
056 * MessageStore objects from non transaction aware MessageStore objects.
057 *
058 *
059 */
060public class KahaDBTransactionStore implements TransactionStore {
061    static final Logger LOG = LoggerFactory.getLogger(KahaDBTransactionStore.class);
062    ConcurrentHashMap<Object, Tx> inflightTransactions = new ConcurrentHashMap<Object, Tx>();
063    private final KahaDBStore theStore;
064
065    public KahaDBTransactionStore(KahaDBStore theStore) {
066        this.theStore = theStore;
067    }
068
069    private WireFormat wireFormat(){
070      return this.theStore.wireFormat;
071    }
072
073    public class Tx {
074        private final ArrayList<AddMessageCommand> messages = new ArrayList<AddMessageCommand>();
075
076        private final ArrayList<RemoveMessageCommand> acks = new ArrayList<RemoveMessageCommand>();
077
078        public void add(AddMessageCommand msg) {
079            messages.add(msg);
080        }
081
082        public void add(RemoveMessageCommand ack) {
083            acks.add(ack);
084        }
085
086        public Message[] getMessages() {
087            Message rc[] = new Message[messages.size()];
088            int count = 0;
089            for (Iterator<AddMessageCommand> iter = messages.iterator(); iter.hasNext();) {
090                AddMessageCommand cmd = iter.next();
091                rc[count++] = cmd.getMessage();
092            }
093            return rc;
094        }
095
096        public MessageAck[] getAcks() {
097            MessageAck rc[] = new MessageAck[acks.size()];
098            int count = 0;
099            for (Iterator<RemoveMessageCommand> iter = acks.iterator(); iter.hasNext();) {
100                RemoveMessageCommand cmd = iter.next();
101                rc[count++] = cmd.getMessageAck();
102            }
103            return rc;
104        }
105
106        /**
107         * @return true if something to commit
108         * @throws IOException
109         */
110        public List<Future<Object>> commit() throws IOException {
111            List<Future<Object>> results = new ArrayList<Future<Object>>();
112            // Do all the message adds.
113            for (Iterator<AddMessageCommand> iter = messages.iterator(); iter.hasNext();) {
114                AddMessageCommand cmd = iter.next();
115                results.add(cmd.run());
116
117            }
118            // And removes..
119            for (Iterator<RemoveMessageCommand> iter = acks.iterator(); iter.hasNext();) {
120                RemoveMessageCommand cmd = iter.next();
121                cmd.run();
122                results.add(cmd.run());
123            }
124
125            return results;
126        }
127    }
128
129    public abstract class AddMessageCommand {
130        private final ConnectionContext ctx;
131        AddMessageCommand(ConnectionContext ctx) {
132            this.ctx = ctx;
133        }
134        abstract Message getMessage();
135        Future<Object> run() throws IOException {
136            return run(this.ctx);
137        }
138        abstract Future<Object> run(ConnectionContext ctx) throws IOException;
139    }
140
141    public abstract class RemoveMessageCommand {
142
143        private final ConnectionContext ctx;
144        RemoveMessageCommand(ConnectionContext ctx) {
145            this.ctx = ctx;
146        }
147        abstract MessageAck getMessageAck();
148        Future<Object> run() throws IOException {
149            return run(this.ctx);
150        }
151        abstract Future<Object> run(ConnectionContext context) throws IOException;
152    }
153
154    public MessageStore proxy(MessageStore messageStore) {
155        return new ProxyMessageStore(messageStore) {
156            @Override
157            public void addMessage(ConnectionContext context, final Message send) throws IOException {
158                KahaDBTransactionStore.this.addMessage(context, getDelegate(), send);
159            }
160
161            @Override
162            public void addMessage(ConnectionContext context, final Message send, boolean canOptimize) throws IOException {
163                KahaDBTransactionStore.this.addMessage(context, getDelegate(), send);
164            }
165
166            @Override
167            public ListenableFuture<Object> asyncAddQueueMessage(ConnectionContext context, Message message) throws IOException {
168                return KahaDBTransactionStore.this.asyncAddQueueMessage(context, getDelegate(), message);
169            }
170
171            @Override
172            public ListenableFuture<Object> asyncAddQueueMessage(ConnectionContext context, Message message, boolean canOptimize) throws IOException {
173                return KahaDBTransactionStore.this.asyncAddQueueMessage(context, getDelegate(), message);
174            }
175
176            @Override
177            public void removeMessage(ConnectionContext context, final MessageAck ack) throws IOException {
178                KahaDBTransactionStore.this.removeMessage(context, getDelegate(), ack);
179            }
180
181            @Override
182            public void removeAsyncMessage(ConnectionContext context, MessageAck ack) throws IOException {
183                KahaDBTransactionStore.this.removeAsyncMessage(context, getDelegate(), ack);
184            }
185        };
186    }
187
188    public TopicMessageStore proxy(TopicMessageStore messageStore) {
189        return new ProxyTopicMessageStore(messageStore) {
190            @Override
191            public void addMessage(ConnectionContext context, final Message send) throws IOException {
192                KahaDBTransactionStore.this.addMessage(context, getDelegate(), send);
193            }
194
195            @Override
196            public void addMessage(ConnectionContext context, final Message send, boolean canOptimize) throws IOException {
197                KahaDBTransactionStore.this.addMessage(context, getDelegate(), send);
198            }
199
200            @Override
201            public ListenableFuture<Object> asyncAddTopicMessage(ConnectionContext context, Message message) throws IOException {
202                return KahaDBTransactionStore.this.asyncAddTopicMessage(context, getDelegate(), message);
203            }
204
205            @Override
206            public ListenableFuture<Object> asyncAddTopicMessage(ConnectionContext context, Message message, boolean canOptimize) throws IOException {
207                return KahaDBTransactionStore.this.asyncAddTopicMessage(context, getDelegate(), message);
208            }
209
210            @Override
211            public void removeMessage(ConnectionContext context, final MessageAck ack) throws IOException {
212                KahaDBTransactionStore.this.removeMessage(context, getDelegate(), ack);
213            }
214
215            @Override
216            public void removeAsyncMessage(ConnectionContext context, MessageAck ack) throws IOException {
217                KahaDBTransactionStore.this.removeAsyncMessage(context, getDelegate(), ack);
218            }
219
220            @Override
221            public void acknowledge(ConnectionContext context, String clientId, String subscriptionName,
222                            MessageId messageId, MessageAck ack) throws IOException {
223                KahaDBTransactionStore.this.acknowledge(context, (TopicMessageStore)getDelegate(), clientId,
224                        subscriptionName, messageId, ack);
225            }
226
227        };
228    }
229
230    /**
231     * @throws IOException
232     * @see org.apache.activemq.store.TransactionStore#prepare(TransactionId)
233     */
234    public void prepare(TransactionId txid) throws IOException {
235        KahaTransactionInfo info = getTransactionInfo(txid);
236        if (txid.isXATransaction() || theStore.isConcurrentStoreAndDispatchTransactions() == false) {
237            theStore.store(new KahaPrepareCommand().setTransactionInfo(info), true, null, null);
238        } else {
239            Tx tx = inflightTransactions.remove(txid);
240            if (tx != null) {
241               theStore.store(new KahaPrepareCommand().setTransactionInfo(info), true, null, null);
242            }
243        }
244    }
245
246    public Tx getTx(Object txid) {
247        Tx tx = inflightTransactions.get(txid);
248        if (tx == null) {
249            tx = new Tx();
250            inflightTransactions.put(txid, tx);
251        }
252        return tx;
253    }
254
255    public void commit(TransactionId txid, boolean wasPrepared, final Runnable preCommit, Runnable postCommit)
256            throws IOException {
257        if (txid != null) {
258            if (!txid.isXATransaction() && theStore.isConcurrentStoreAndDispatchTransactions()) {
259                if (preCommit != null) {
260                    preCommit.run();
261                }
262                Tx tx = inflightTransactions.remove(txid);
263                if (tx != null) {
264                    List<Future<Object>> results = tx.commit();
265                    boolean doneSomething = false;
266                    for (Future<Object> result : results) {
267                        try {
268                            result.get();
269                        } catch (InterruptedException e) {
270                            theStore.brokerService.handleIOException(new IOException(e.getMessage()));
271                        } catch (ExecutionException e) {
272                            theStore.brokerService.handleIOException(new IOException(e.getMessage()));
273                        }catch(CancellationException e) {
274                        }
275                        if (!result.isCancelled()) {
276                            doneSomething = true;
277                        }
278                    }
279                    if (postCommit != null) {
280                        postCommit.run();
281                    }
282                    if (doneSomething) {
283                        KahaTransactionInfo info = getTransactionInfo(txid);
284                        theStore.store(new KahaCommitCommand().setTransactionInfo(info), theStore.isEnableJournalDiskSyncs(), null, null);
285                    }
286                }else {
287                    //The Tx will be null for failed over clients - lets run their post commits
288                    if (postCommit != null) {
289                        postCommit.run();
290                    }
291                }
292
293            } else {
294                KahaTransactionInfo info = getTransactionInfo(txid);
295                if (preCommit != null) {
296                    preCommit.run();
297                }
298                theStore.store(new KahaCommitCommand().setTransactionInfo(info), theStore.isEnableJournalDiskSyncs(), null, postCommit);
299                forgetRecoveredAcks(txid, false);
300            }
301        }else {
302           LOG.error("Null transaction passed on commit");
303        }
304    }
305
306    /**
307     * @throws IOException
308     * @see org.apache.activemq.store.TransactionStore#rollback(TransactionId)
309     */
310    public void rollback(TransactionId txid) throws IOException {
311        if (txid.isXATransaction() || theStore.isConcurrentStoreAndDispatchTransactions() == false) {
312            KahaTransactionInfo info = getTransactionInfo(txid);
313            theStore.store(new KahaRollbackCommand().setTransactionInfo(info), theStore.isEnableJournalDiskSyncs(), null, null);
314            forgetRecoveredAcks(txid, true);
315        } else {
316            inflightTransactions.remove(txid);
317        }
318    }
319
320    protected void forgetRecoveredAcks(TransactionId txid, boolean isRollback) throws IOException {
321        if (txid.isXATransaction()) {
322            XATransactionId xaTid = ((XATransactionId) txid);
323            theStore.forgetRecoveredAcks(xaTid.getPreparedAcks(), isRollback);
324        }
325    }
326
327    public void start() throws Exception {
328    }
329
330    public void stop() throws Exception {
331    }
332
333    public synchronized void recover(TransactionRecoveryListener listener) throws IOException {
334        for (Map.Entry<TransactionId, List<Operation>> entry : theStore.preparedTransactions.entrySet()) {
335            XATransactionId xid = (XATransactionId) entry.getKey();
336            ArrayList<Message> messageList = new ArrayList<Message>();
337            ArrayList<MessageAck> ackList = new ArrayList<MessageAck>();
338
339            for (Operation op : entry.getValue()) {
340                if (op.getClass() == MessageDatabase.AddOperation.class) {
341                    MessageDatabase.AddOperation addOp = (MessageDatabase.AddOperation) op;
342                    Message msg = (Message) wireFormat().unmarshal(new DataInputStream(addOp.getCommand().getMessage()
343                            .newInput()));
344                    messageList.add(msg);
345                } else {
346                    MessageDatabase.RemoveOperation rmOp = (MessageDatabase.RemoveOperation) op;
347                    Buffer ackb = rmOp.getCommand().getAck();
348                    MessageAck ack = (MessageAck) wireFormat().unmarshal(new DataInputStream(ackb.newInput()));
349                    ackList.add(ack);
350                }
351            }
352
353            Message[] addedMessages = new Message[messageList.size()];
354            MessageAck[] acks = new MessageAck[ackList.size()];
355            messageList.toArray(addedMessages);
356            ackList.toArray(acks);
357            xid.setPreparedAcks(ackList);
358            theStore.trackRecoveredAcks(ackList);
359            listener.recover(xid, addedMessages, acks);
360        }
361    }
362
363    /**
364     * @param message
365     * @throws IOException
366     */
367    void addMessage(ConnectionContext context, final MessageStore destination, final Message message)
368            throws IOException {
369
370        if (message.getTransactionId() != null) {
371            if (message.getTransactionId().isXATransaction() || theStore.isConcurrentStoreAndDispatchTransactions() == false) {
372                destination.addMessage(context, message);
373            } else {
374                Tx tx = getTx(message.getTransactionId());
375                tx.add(new AddMessageCommand(context) {
376                    @Override
377                    public Message getMessage() {
378                        return message;
379                    }
380                    @Override
381                    public Future<Object> run(ConnectionContext ctx) throws IOException {
382                        destination.addMessage(ctx, message);
383                        return AbstractMessageStore.FUTURE;
384                    }
385
386                });
387            }
388        } else {
389            destination.addMessage(context, message);
390        }
391    }
392
393    ListenableFuture<Object> asyncAddQueueMessage(ConnectionContext context, final MessageStore destination, final Message message)
394            throws IOException {
395
396        if (message.getTransactionId() != null) {
397            if (message.getTransactionId().isXATransaction() || theStore.isConcurrentStoreAndDispatchTransactions() == false) {
398                destination.addMessage(context, message);
399                return AbstractMessageStore.FUTURE;
400            } else {
401                Tx tx = getTx(message.getTransactionId());
402                tx.add(new AddMessageCommand(context) {
403                    @Override
404                    public Message getMessage() {
405                        return message;
406                    }
407                    @Override
408                    public Future<Object> run(ConnectionContext ctx) throws IOException {
409                        return destination.asyncAddQueueMessage(ctx, message);
410                    }
411
412                });
413                return AbstractMessageStore.FUTURE;
414            }
415        } else {
416            return destination.asyncAddQueueMessage(context, message);
417        }
418    }
419
420    ListenableFuture<Object> asyncAddTopicMessage(ConnectionContext context, final MessageStore destination, final Message message)
421            throws IOException {
422
423        if (message.getTransactionId() != null) {
424            if (message.getTransactionId().isXATransaction() || theStore.isConcurrentStoreAndDispatchTransactions()==false) {
425                destination.addMessage(context, message);
426                return AbstractMessageStore.FUTURE;
427            } else {
428                Tx tx = getTx(message.getTransactionId());
429                tx.add(new AddMessageCommand(context) {
430                    @Override
431                    public Message getMessage() {
432                        return message;
433                    }
434                    @Override
435                    public Future<Object> run(ConnectionContext ctx) throws IOException {
436                        return destination.asyncAddTopicMessage(ctx, message);
437                    }
438
439                });
440                return AbstractMessageStore.FUTURE;
441            }
442        } else {
443            return destination.asyncAddTopicMessage(context, message);
444        }
445    }
446
447    /**
448     * @param ack
449     * @throws IOException
450     */
451    final void removeMessage(ConnectionContext context, final MessageStore destination, final MessageAck ack)
452            throws IOException {
453
454        if (ack.isInTransaction()) {
455            if (ack.getTransactionId().isXATransaction() || theStore.isConcurrentStoreAndDispatchTransactions()== false) {
456                destination.removeMessage(context, ack);
457            } else {
458                Tx tx = getTx(ack.getTransactionId());
459                tx.add(new RemoveMessageCommand(context) {
460                    @Override
461                    public MessageAck getMessageAck() {
462                        return ack;
463                    }
464
465                    @Override
466                    public Future<Object> run(ConnectionContext ctx) throws IOException {
467                        destination.removeMessage(ctx, ack);
468                        return AbstractMessageStore.FUTURE;
469                    }
470                });
471            }
472        } else {
473            destination.removeMessage(context, ack);
474        }
475    }
476
477    final void removeAsyncMessage(ConnectionContext context, final MessageStore destination, final MessageAck ack)
478            throws IOException {
479
480        if (ack.isInTransaction()) {
481            if (ack.getTransactionId().isXATransaction() || theStore.isConcurrentStoreAndDispatchTransactions()==false) {
482                destination.removeAsyncMessage(context, ack);
483            } else {
484                Tx tx = getTx(ack.getTransactionId());
485                tx.add(new RemoveMessageCommand(context) {
486                    @Override
487                    public MessageAck getMessageAck() {
488                        return ack;
489                    }
490
491                    @Override
492                    public Future<Object> run(ConnectionContext ctx) throws IOException {
493                        destination.removeMessage(ctx, ack);
494                        return AbstractMessageStore.FUTURE;
495                    }
496                });
497            }
498        } else {
499            destination.removeAsyncMessage(context, ack);
500        }
501    }
502
503    final void acknowledge(ConnectionContext context, final TopicMessageStore destination, final String clientId, final String subscriptionName,
504                           final MessageId messageId, final MessageAck ack) throws IOException {
505
506        if (ack.isInTransaction()) {
507            if (ack.getTransactionId().isXATransaction() || theStore.isConcurrentStoreAndDispatchTransactions()== false) {
508                destination.acknowledge(context, clientId, subscriptionName, messageId, ack);
509            } else {
510                Tx tx = getTx(ack.getTransactionId());
511                tx.add(new RemoveMessageCommand(context) {
512                    public MessageAck getMessageAck() {
513                        return ack;
514                    }
515
516                    public Future<Object> run(ConnectionContext ctx) throws IOException {
517                        destination.acknowledge(ctx, clientId, subscriptionName, messageId, ack);
518                        return AbstractMessageStore.FUTURE;
519                    }
520                });
521            }
522        } else {
523            destination.acknowledge(context, clientId, subscriptionName, messageId, ack);
524        }
525    }
526
527
528    private KahaTransactionInfo getTransactionInfo(TransactionId txid) {
529        return TransactionIdConversion.convert(theStore.getTransactionIdTransformer().transform(txid));
530    }
531}