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.HashSet;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.Map.Entry;
026import java.util.Set;
027
028import org.apache.activemq.broker.BrokerService;
029import org.apache.activemq.broker.BrokerServiceAware;
030import org.apache.activemq.broker.ConnectionContext;
031import org.apache.activemq.broker.scheduler.JobSchedulerStore;
032import org.apache.activemq.command.ActiveMQDestination;
033import org.apache.activemq.command.ActiveMQQueue;
034import org.apache.activemq.command.ActiveMQTempQueue;
035import org.apache.activemq.command.ActiveMQTempTopic;
036import org.apache.activemq.command.ActiveMQTopic;
037import org.apache.activemq.command.Message;
038import org.apache.activemq.command.MessageAck;
039import org.apache.activemq.command.MessageId;
040import org.apache.activemq.command.ProducerId;
041import org.apache.activemq.command.SubscriptionInfo;
042import org.apache.activemq.command.TransactionId;
043import org.apache.activemq.command.XATransactionId;
044import org.apache.activemq.openwire.OpenWireFormat;
045import org.apache.activemq.protobuf.Buffer;
046import org.apache.activemq.store.AbstractMessageStore;
047import org.apache.activemq.store.MessageRecoveryListener;
048import org.apache.activemq.store.MessageStore;
049import org.apache.activemq.store.PersistenceAdapter;
050import org.apache.activemq.store.TopicMessageStore;
051import org.apache.activemq.store.TransactionRecoveryListener;
052import org.apache.activemq.store.TransactionStore;
053import org.apache.activemq.store.kahadb.data.KahaAddMessageCommand;
054import org.apache.activemq.store.kahadb.data.KahaDestination;
055import org.apache.activemq.store.kahadb.data.KahaDestination.DestinationType;
056import org.apache.activemq.store.kahadb.data.KahaLocation;
057import org.apache.activemq.store.kahadb.data.KahaRemoveDestinationCommand;
058import org.apache.activemq.store.kahadb.data.KahaRemoveMessageCommand;
059import org.apache.activemq.store.kahadb.data.KahaSubscriptionCommand;
060import org.apache.activemq.store.kahadb.disk.journal.Location;
061import org.apache.activemq.store.kahadb.disk.page.Transaction;
062import org.apache.activemq.usage.MemoryUsage;
063import org.apache.activemq.usage.SystemUsage;
064import org.apache.activemq.util.ByteSequence;
065import org.apache.activemq.wireformat.WireFormat;
066
067public class TempKahaDBStore extends TempMessageDatabase implements PersistenceAdapter, BrokerServiceAware {
068
069    private final WireFormat wireFormat = new OpenWireFormat();
070    private BrokerService brokerService;
071
072    @Override
073    public void setBrokerName(String brokerName) {
074    }
075    @Override
076    public void setUsageManager(SystemUsage usageManager) {
077    }
078
079    @Override
080    public TransactionStore createTransactionStore() throws IOException {
081        return new TransactionStore(){
082
083            @Override
084            public void commit(TransactionId txid, boolean wasPrepared, Runnable preCommit,Runnable postCommit) throws IOException {
085                if (preCommit != null) {
086                    preCommit.run();
087                }
088                processCommit(txid);
089                if (postCommit != null) {
090                    postCommit.run();
091                }
092            }
093            @Override
094            public void prepare(TransactionId txid) throws IOException {
095                processPrepare(txid);
096            }
097            @Override
098            public void rollback(TransactionId txid) throws IOException {
099                processRollback(txid);
100            }
101            @Override
102            public void recover(TransactionRecoveryListener listener) throws IOException {
103                for (Map.Entry<TransactionId, ArrayList<Operation>> entry : preparedTransactions.entrySet()) {
104                    XATransactionId xid = (XATransactionId)entry.getKey();
105                    ArrayList<Message> messageList = new ArrayList<Message>();
106                    ArrayList<MessageAck> ackList = new ArrayList<MessageAck>();
107
108                    for (Operation op : entry.getValue()) {
109                        if( op.getClass() == AddOpperation.class ) {
110                            AddOpperation addOp = (AddOpperation)op;
111                            Message msg = (Message)wireFormat.unmarshal( new DataInputStream(addOp.getCommand().getMessage().newInput()) );
112                            messageList.add(msg);
113                        } else {
114                            RemoveOpperation rmOp = (RemoveOpperation)op;
115                            MessageAck ack = (MessageAck)wireFormat.unmarshal( new DataInputStream(rmOp.getCommand().getAck().newInput()) );
116                            ackList.add(ack);
117                        }
118                    }
119
120                    Message[] addedMessages = new Message[messageList.size()];
121                    MessageAck[] acks = new MessageAck[ackList.size()];
122                    messageList.toArray(addedMessages);
123                    ackList.toArray(acks);
124                    listener.recover(xid, addedMessages, acks);
125                }
126            }
127            @Override
128            public void start() throws Exception {
129            }
130            @Override
131            public void stop() throws Exception {
132            }
133        };
134    }
135
136    public class KahaDBMessageStore extends AbstractMessageStore {
137        protected KahaDestination dest;
138
139        public KahaDBMessageStore(ActiveMQDestination destination) {
140            super(destination);
141            this.dest = convert( destination );
142        }
143
144        @Override
145        public ActiveMQDestination getDestination() {
146            return destination;
147        }
148
149        @Override
150        public void addMessage(ConnectionContext context, Message message) throws IOException {
151            KahaAddMessageCommand command = new KahaAddMessageCommand();
152            command.setDestination(dest);
153            command.setMessageId(message.getMessageId().toProducerKey());
154            processAdd(command, message.getTransactionId(), wireFormat.marshal(message));
155        }
156
157        @Override
158        public void removeMessage(ConnectionContext context, MessageAck ack) throws IOException {
159            KahaRemoveMessageCommand command = new KahaRemoveMessageCommand();
160            command.setDestination(dest);
161            command.setMessageId(ack.getLastMessageId().toProducerKey());
162            processRemove(command, ack.getTransactionId());
163        }
164
165        @Override
166        public void removeAllMessages(ConnectionContext context) throws IOException {
167            KahaRemoveDestinationCommand command = new KahaRemoveDestinationCommand();
168            command.setDestination(dest);
169            process(command);
170        }
171
172        @Override
173        public Message getMessage(MessageId identity) throws IOException {
174            final String key = identity.toProducerKey();
175
176            // Hopefully one day the page file supports concurrent read operations... but for now we must
177            // externally synchronize...
178            ByteSequence data;
179            synchronized(indexMutex) {
180                data = pageFile.tx().execute(new Transaction.CallableClosure<ByteSequence, IOException>(){
181                    @Override
182                    public ByteSequence execute(Transaction tx) throws IOException {
183                        StoredDestination sd = getStoredDestination(dest, tx);
184                        Long sequence = sd.messageIdIndex.get(tx, key);
185                        if( sequence ==null ) {
186                            return null;
187                        }
188                        return sd.orderIndex.get(tx, sequence).data;
189                    }
190                });
191            }
192            if( data == null ) {
193                return null;
194            }
195
196            Message msg = (Message)wireFormat.unmarshal( data );
197            return msg;
198        }
199
200        @Override
201        public void recover(final MessageRecoveryListener listener) throws Exception {
202            synchronized(indexMutex) {
203                pageFile.tx().execute(new Transaction.Closure<Exception>(){
204                    @Override
205                    public void execute(Transaction tx) throws Exception {
206                        StoredDestination sd = getStoredDestination(dest, tx);
207                        for (Iterator<Entry<Long, MessageRecord>> iterator = sd.orderIndex.iterator(tx); iterator.hasNext();) {
208                            Entry<Long, MessageRecord> entry = iterator.next();
209                            listener.recoverMessage( (Message) wireFormat.unmarshal(entry.getValue().data) );
210                        }
211                    }
212                });
213            }
214        }
215
216        long cursorPos=0;
217
218        @Override
219        public void recoverNextMessages(final int maxReturned, final MessageRecoveryListener listener) throws Exception {
220            synchronized(indexMutex) {
221                pageFile.tx().execute(new Transaction.Closure<Exception>(){
222                    @Override
223                    public void execute(Transaction tx) throws Exception {
224                        StoredDestination sd = getStoredDestination(dest, tx);
225                        Entry<Long, MessageRecord> entry=null;
226                        int counter = 0;
227                        for (Iterator<Entry<Long, MessageRecord>> iterator = sd.orderIndex.iterator(tx, cursorPos); iterator.hasNext();) {
228                            entry = iterator.next();
229                            listener.recoverMessage( (Message) wireFormat.unmarshal(entry.getValue().data ) );
230                            counter++;
231                            if( counter >= maxReturned ) {
232                                break;
233                            }
234                        }
235                        if( entry!=null ) {
236                            cursorPos = entry.getKey()+1;
237                        }
238                    }
239                });
240            }
241        }
242
243        @Override
244        public void resetBatching() {
245            cursorPos=0;
246        }
247
248
249        @Override
250        public void setBatch(MessageId identity) throws IOException {
251            final String key = identity.toProducerKey();
252
253            // Hopefully one day the page file supports concurrent read operations... but for now we must
254            // externally synchronize...
255            Long location;
256            synchronized(indexMutex) {
257                location = pageFile.tx().execute(new Transaction.CallableClosure<Long, IOException>(){
258                    @Override
259                    public Long execute(Transaction tx) throws IOException {
260                        StoredDestination sd = getStoredDestination(dest, tx);
261                        return sd.messageIdIndex.get(tx, key);
262                    }
263                });
264            }
265            if( location!=null ) {
266                cursorPos=location+1;
267            }
268
269        }
270
271        @Override
272        public void setMemoryUsage(MemoryUsage memoryUsage) {
273        }
274        @Override
275        public void start() throws Exception {
276        }
277        @Override
278        public void stop() throws Exception {
279        }
280
281        @Override
282        public void recoverMessageStoreStatistics() throws IOException {
283            int count = 0;
284            synchronized(indexMutex) {
285                count = pageFile.tx().execute(new Transaction.CallableClosure<Integer, IOException>(){
286                    @Override
287                    public Integer execute(Transaction tx) throws IOException {
288                        // Iterate through all index entries to get a count of messages in the destination.
289                        StoredDestination sd = getStoredDestination(dest, tx);
290                        int rc=0;
291                        for (Iterator<Entry<String, Long>> iterator = sd.messageIdIndex.iterator(tx); iterator.hasNext();) {
292                            iterator.next();
293                            rc++;
294                        }
295                        return rc;
296                    }
297                });
298            }
299            getMessageStoreStatistics().getMessageCount().setCount(count);
300        }
301
302    }
303
304    class KahaDBTopicMessageStore extends KahaDBMessageStore implements TopicMessageStore {
305        public KahaDBTopicMessageStore(ActiveMQTopic destination) {
306            super(destination);
307        }
308
309        @Override
310        public void acknowledge(ConnectionContext context, String clientId, String subscriptionName,
311                                MessageId messageId, MessageAck ack) throws IOException {
312            KahaRemoveMessageCommand command = new KahaRemoveMessageCommand();
313            command.setDestination(dest);
314            command.setSubscriptionKey(subscriptionKey(clientId, subscriptionName));
315            command.setMessageId(messageId.toProducerKey());
316            // We are not passed a transaction info.. so we can't participate in a transaction.
317            // Looks like a design issue with the TopicMessageStore interface.  Also we can't recover the original ack
318            // to pass back to the XA recover method.
319            // command.setTransactionInfo();
320            processRemove(command, null);
321        }
322
323        @Override
324        public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroactive) throws IOException {
325            String subscriptionKey = subscriptionKey(subscriptionInfo.getClientId(), subscriptionInfo.getSubscriptionName());
326            KahaSubscriptionCommand command = new KahaSubscriptionCommand();
327            command.setDestination(dest);
328            command.setSubscriptionKey(subscriptionKey);
329            command.setRetroactive(retroactive);
330            org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(subscriptionInfo);
331            command.setSubscriptionInfo(new Buffer(packet.getData(), packet.getOffset(), packet.getLength()));
332            process(command);
333        }
334
335        @Override
336        public void deleteSubscription(String clientId, String subscriptionName) throws IOException {
337            KahaSubscriptionCommand command = new KahaSubscriptionCommand();
338            command.setDestination(dest);
339            command.setSubscriptionKey(subscriptionKey(clientId, subscriptionName));
340            process(command);
341        }
342
343        @Override
344        public SubscriptionInfo[] getAllSubscriptions() throws IOException {
345
346            final ArrayList<SubscriptionInfo> subscriptions = new ArrayList<SubscriptionInfo>();
347            synchronized(indexMutex) {
348                pageFile.tx().execute(new Transaction.Closure<IOException>(){
349                    @Override
350                    public void execute(Transaction tx) throws IOException {
351                        StoredDestination sd = getStoredDestination(dest, tx);
352                        for (Iterator<Entry<String, KahaSubscriptionCommand>> iterator = sd.subscriptions.iterator(tx); iterator.hasNext();) {
353                            Entry<String, KahaSubscriptionCommand> entry = iterator.next();
354                            SubscriptionInfo info = (SubscriptionInfo)wireFormat.unmarshal( new DataInputStream(entry.getValue().getSubscriptionInfo().newInput()) );
355                            subscriptions.add(info);
356
357                        }
358                    }
359                });
360            }
361
362            SubscriptionInfo[]rc=new SubscriptionInfo[subscriptions.size()];
363            subscriptions.toArray(rc);
364            return rc;
365        }
366
367        @Override
368        public SubscriptionInfo lookupSubscription(String clientId, String subscriptionName) throws IOException {
369            final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
370            synchronized(indexMutex) {
371                return pageFile.tx().execute(new Transaction.CallableClosure<SubscriptionInfo, IOException>(){
372                    @Override
373                    public SubscriptionInfo execute(Transaction tx) throws IOException {
374                        StoredDestination sd = getStoredDestination(dest, tx);
375                        KahaSubscriptionCommand command = sd.subscriptions.get(tx, subscriptionKey);
376                        if( command ==null ) {
377                            return null;
378                        }
379                        return (SubscriptionInfo)wireFormat.unmarshal( new DataInputStream(command.getSubscriptionInfo().newInput()) );
380                    }
381                });
382            }
383        }
384
385        @Override
386        public int getMessageCount(String clientId, String subscriptionName) throws IOException {
387            final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
388            synchronized(indexMutex) {
389                return pageFile.tx().execute(new Transaction.CallableClosure<Integer, IOException>(){
390                    @Override
391                    public Integer execute(Transaction tx) throws IOException {
392                        StoredDestination sd = getStoredDestination(dest, tx);
393                        Long cursorPos = sd.subscriptionAcks.get(tx, subscriptionKey);
394                        if ( cursorPos==null ) {
395                            // The subscription might not exist.
396                            return 0;
397                        }
398                        cursorPos += 1;
399
400                        int counter = 0;
401                        for (Iterator<Entry<Long, MessageRecord>> iterator = sd.orderIndex.iterator(tx, cursorPos); iterator.hasNext();) {
402                            iterator.next();
403                            counter++;
404                        }
405                        return counter;
406                    }
407                });
408            }
409        }
410
411        @Override
412        public void recoverSubscription(String clientId, String subscriptionName, final MessageRecoveryListener listener) throws Exception {
413            final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
414            synchronized(indexMutex) {
415                pageFile.tx().execute(new Transaction.Closure<Exception>(){
416                    @Override
417                    public void execute(Transaction tx) throws Exception {
418                        StoredDestination sd = getStoredDestination(dest, tx);
419                        Long cursorPos = sd.subscriptionAcks.get(tx, subscriptionKey);
420                        cursorPos += 1;
421
422                        for (Iterator<Entry<Long, MessageRecord>> iterator = sd.orderIndex.iterator(tx, cursorPos); iterator.hasNext();) {
423                            Entry<Long, MessageRecord> entry = iterator.next();
424                            listener.recoverMessage( (Message) wireFormat.unmarshal(entry.getValue().data ) );
425                        }
426                    }
427                });
428            }
429        }
430
431        @Override
432        public void recoverNextMessages(String clientId, String subscriptionName, final int maxReturned, final MessageRecoveryListener listener) throws Exception {
433            final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
434            synchronized(indexMutex) {
435                pageFile.tx().execute(new Transaction.Closure<Exception>(){
436                    @Override
437                    public void execute(Transaction tx) throws Exception {
438                        StoredDestination sd = getStoredDestination(dest, tx);
439                        Long cursorPos = sd.subscriptionCursors.get(subscriptionKey);
440                        if( cursorPos == null ) {
441                            cursorPos = sd.subscriptionAcks.get(tx, subscriptionKey);
442                            cursorPos += 1;
443                        }
444
445                        Entry<Long, MessageRecord> entry=null;
446                        int counter = 0;
447                        for (Iterator<Entry<Long, MessageRecord>> iterator = sd.orderIndex.iterator(tx, cursorPos); iterator.hasNext();) {
448                            entry = iterator.next();
449                            listener.recoverMessage( (Message) wireFormat.unmarshal(entry.getValue().data ) );
450                            counter++;
451                            if( counter >= maxReturned ) {
452                                break;
453                            }
454                        }
455                        if( entry!=null ) {
456                            sd.subscriptionCursors.put(subscriptionKey, entry.getKey() + 1);
457                        }
458                    }
459                });
460            }
461        }
462
463        @Override
464        public void resetBatching(String clientId, String subscriptionName) {
465            try {
466                final String subscriptionKey = subscriptionKey(clientId, subscriptionName);
467                synchronized(indexMutex) {
468                    pageFile.tx().execute(new Transaction.Closure<IOException>(){
469                        @Override
470                        public void execute(Transaction tx) throws IOException {
471                            StoredDestination sd = getStoredDestination(dest, tx);
472                            sd.subscriptionCursors.remove(subscriptionKey);
473                        }
474                    });
475                }
476            } catch (IOException e) {
477                throw new RuntimeException(e);
478            }
479        }
480    }
481
482    String subscriptionKey(String clientId, String subscriptionName){
483        return clientId+":"+subscriptionName;
484    }
485
486    @Override
487    public MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException {
488        return new KahaDBMessageStore(destination);
489    }
490
491    @Override
492    public TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException {
493        return new KahaDBTopicMessageStore(destination);
494    }
495
496    /**
497     * Cleanup method to remove any state associated with the given destination.
498     * This method does not stop the message store (it might not be cached).
499     *
500     * @param destination Destination to forget
501     */
502    @Override
503    public void removeQueueMessageStore(ActiveMQQueue destination) {
504    }
505
506    /**
507     * Cleanup method to remove any state associated with the given destination
508     * This method does not stop the message store (it might not be cached).
509     *
510     * @param destination Destination to forget
511     */
512    @Override
513    public void removeTopicMessageStore(ActiveMQTopic destination) {
514    }
515
516    @Override
517    public void deleteAllMessages() throws IOException {
518    }
519
520
521    @Override
522    public Set<ActiveMQDestination> getDestinations() {
523        try {
524            final HashSet<ActiveMQDestination> rc = new HashSet<ActiveMQDestination>();
525            synchronized(indexMutex) {
526                pageFile.tx().execute(new Transaction.Closure<IOException>(){
527                    @Override
528                    public void execute(Transaction tx) throws IOException {
529                        for (Iterator<Entry<String, StoredDestination>> iterator = destinations.iterator(tx); iterator.hasNext();) {
530                            Entry<String, StoredDestination> entry = iterator.next();
531                            rc.add(convert(entry.getKey()));
532                        }
533                    }
534                });
535            }
536            return rc;
537        } catch (IOException e) {
538            throw new RuntimeException(e);
539        }
540    }
541
542    @Override
543    public long getLastMessageBrokerSequenceId() throws IOException {
544        return 0;
545    }
546
547    @Override
548    public long size() {
549        if ( !started.get() ) {
550            return 0;
551        }
552        try {
553            return pageFile.getDiskSize();
554        } catch (IOException e) {
555            throw new RuntimeException(e);
556        }
557    }
558
559    @Override
560    public void beginTransaction(ConnectionContext context) throws IOException {
561        throw new IOException("Not yet implemented.");
562    }
563    @Override
564    public void commitTransaction(ConnectionContext context) throws IOException {
565        throw new IOException("Not yet implemented.");
566    }
567    @Override
568    public void rollbackTransaction(ConnectionContext context) throws IOException {
569        throw new IOException("Not yet implemented.");
570    }
571
572    @Override
573    public void checkpoint(boolean sync) throws IOException {
574    }
575
576    ///////////////////////////////////////////////////////////////////
577    // Internal conversion methods.
578    ///////////////////////////////////////////////////////////////////
579
580
581
582    KahaLocation convert(Location location) {
583        KahaLocation rc = new KahaLocation();
584        rc.setLogId(location.getDataFileId());
585        rc.setOffset(location.getOffset());
586        return rc;
587    }
588
589    KahaDestination convert(ActiveMQDestination dest) {
590        KahaDestination rc = new KahaDestination();
591        rc.setName(dest.getPhysicalName());
592        switch( dest.getDestinationType() ) {
593        case ActiveMQDestination.QUEUE_TYPE:
594            rc.setType(DestinationType.QUEUE);
595            return rc;
596        case ActiveMQDestination.TOPIC_TYPE:
597            rc.setType(DestinationType.TOPIC);
598            return rc;
599        case ActiveMQDestination.TEMP_QUEUE_TYPE:
600            rc.setType(DestinationType.TEMP_QUEUE);
601            return rc;
602        case ActiveMQDestination.TEMP_TOPIC_TYPE:
603            rc.setType(DestinationType.TEMP_TOPIC);
604            return rc;
605        default:
606            return null;
607        }
608    }
609
610    ActiveMQDestination convert(String dest) {
611        int p = dest.indexOf(":");
612        if( p<0 ) {
613            throw new IllegalArgumentException("Not in the valid destination format");
614        }
615        int type = Integer.parseInt(dest.substring(0, p));
616        String name = dest.substring(p+1);
617
618        switch( KahaDestination.DestinationType.valueOf(type) ) {
619        case QUEUE:
620            return new ActiveMQQueue(name);
621        case TOPIC:
622            return new ActiveMQTopic(name);
623        case TEMP_QUEUE:
624            return new ActiveMQTempQueue(name);
625        case TEMP_TOPIC:
626            return new ActiveMQTempTopic(name);
627        default:
628            throw new IllegalArgumentException("Not in the valid destination format");
629        }
630    }
631
632    @Override
633    public long getLastProducerSequenceId(ProducerId id) {
634        return -1;
635    }
636
637    @Override
638    public void setBrokerService(BrokerService brokerService) {
639        this.brokerService = brokerService;
640    }
641
642    @Override
643    public void load() throws IOException {
644        if( brokerService!=null ) {
645            wireFormat.setVersion(brokerService.getStoreOpenWireVersion());
646        }
647        super.load();
648    }
649    @Override
650    public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException {
651        throw new UnsupportedOperationException();
652    }
653}