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.broker;
018
019import org.apache.activemq.broker.region.Destination;
020import org.apache.activemq.broker.region.Region;
021import org.apache.activemq.command.Message;
022import org.apache.activemq.command.MessageId;
023import org.apache.activemq.state.ProducerState;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027import java.io.IOException;
028import java.util.concurrent.atomic.AtomicBoolean;
029import java.util.concurrent.atomic.AtomicLong;
030
031/**
032 * Holds internal state in the broker for a MessageProducer
033 */
034public class ProducerBrokerExchange {
035
036    private static final Logger LOG = LoggerFactory.getLogger(ProducerBrokerExchange.class);
037    private ConnectionContext connectionContext;
038    private Destination regionDestination;
039    private Region region;
040    private ProducerState producerState;
041    private boolean mutable = true;
042    private AtomicLong lastSendSequenceNumber = new AtomicLong(-1);
043    private boolean auditProducerSequenceIds;
044    private boolean isNetworkProducer;
045    private BrokerService brokerService;
046    private FlowControlInfo flowControlInfo = new FlowControlInfo();
047
048    public ProducerBrokerExchange() {
049    }
050
051    public ProducerBrokerExchange copy() {
052        ProducerBrokerExchange rc = new ProducerBrokerExchange();
053        rc.connectionContext = connectionContext.copy();
054        rc.regionDestination = regionDestination;
055        rc.region = region;
056        rc.producerState = producerState;
057        rc.mutable = mutable;
058        rc.flowControlInfo = flowControlInfo;
059        return rc;
060    }
061
062
063    /**
064     * @return the connectionContext
065     */
066    public ConnectionContext getConnectionContext() {
067        return this.connectionContext;
068    }
069
070    /**
071     * @param connectionContext the connectionContext to set
072     */
073    public void setConnectionContext(ConnectionContext connectionContext) {
074        this.connectionContext = connectionContext;
075    }
076
077    /**
078     * @return the mutable
079     */
080    public boolean isMutable() {
081        return this.mutable;
082    }
083
084    /**
085     * @param mutable the mutable to set
086     */
087    public void setMutable(boolean mutable) {
088        this.mutable = mutable;
089    }
090
091    /**
092     * @return the regionDestination
093     */
094    public Destination getRegionDestination() {
095        return this.regionDestination;
096    }
097
098    /**
099     * @param regionDestination the regionDestination to set
100     */
101    public void setRegionDestination(Destination regionDestination) {
102        this.regionDestination = regionDestination;
103    }
104
105    /**
106     * @return the region
107     */
108    public Region getRegion() {
109        return this.region;
110    }
111
112    /**
113     * @param region the region to set
114     */
115    public void setRegion(Region region) {
116        this.region = region;
117    }
118
119    /**
120     * @return the producerState
121     */
122    public ProducerState getProducerState() {
123        return this.producerState;
124    }
125
126    /**
127     * @param producerState the producerState to set
128     */
129    public void setProducerState(ProducerState producerState) {
130        this.producerState = producerState;
131    }
132
133    /**
134     * Enforce duplicate suppression using info from persistence adapter
135     *
136     * @return false if message should be ignored as a duplicate
137     */
138    public boolean canDispatch(Message messageSend) {
139        boolean canDispatch = true;
140        if (auditProducerSequenceIds && messageSend.isPersistent()) {
141            final long producerSequenceId = messageSend.getMessageId().getProducerSequenceId();
142            if (isNetworkProducer) {
143                //  messages are multiplexed on this producer so we need to query the persistenceAdapter
144                long lastStoredForMessageProducer = getStoredSequenceIdForMessage(messageSend.getMessageId());
145                if (producerSequenceId <= lastStoredForMessageProducer) {
146                    canDispatch = false;
147                    LOG.warn("suppressing duplicate message send [{}] from network producer with producerSequence [{}] less than last stored: {}",
148                            (LOG.isTraceEnabled() ? messageSend : messageSend.getMessageId()), producerSequenceId, lastStoredForMessageProducer);
149                }
150            } else if (producerSequenceId <= lastSendSequenceNumber.get()) {
151                canDispatch = false;
152                if (messageSend.isInTransaction()) {
153                    LOG.warn("suppressing duplicated message send [{}] with producerSequenceId [{}] <= last stored: {}",
154                            (LOG.isTraceEnabled() ? messageSend : messageSend.getMessageId()), producerSequenceId, lastSendSequenceNumber);
155                } else {
156                    LOG.debug("suppressing duplicated message send [{}] with producerSequenceId [{}] <= last stored: {}",
157                            (LOG.isTraceEnabled() ? messageSend : messageSend.getMessageId()), producerSequenceId, lastSendSequenceNumber);
158
159                }
160            } else {
161                // track current so we can suppress duplicates later in the stream
162                lastSendSequenceNumber.set(producerSequenceId);
163            }
164        }
165        return canDispatch;
166    }
167
168    private long getStoredSequenceIdForMessage(MessageId messageId) {
169        try {
170            return brokerService.getPersistenceAdapter().getLastProducerSequenceId(messageId.getProducerId());
171        } catch (IOException ignored) {
172            LOG.debug("Failed to determine last producer sequence id for: {}", messageId, ignored);
173        }
174        return -1;
175    }
176
177    public void setLastStoredSequenceId(long l) {
178        auditProducerSequenceIds = true;
179        if (connectionContext.isNetworkConnection()) {
180            brokerService = connectionContext.getBroker().getBrokerService();
181            isNetworkProducer = true;
182        }
183        lastSendSequenceNumber.set(l);
184        LOG.debug("last stored sequence id set: {}", l);
185    }
186
187    public void incrementSend() {
188        flowControlInfo.incrementSend();
189    }
190
191    public void blockingOnFlowControl(boolean blockingOnFlowControl) {
192        flowControlInfo.setBlockingOnFlowControl(blockingOnFlowControl);
193    }
194
195    public void incrementTimeBlocked(Destination destination, long timeBlocked) {
196        flowControlInfo.incrementTimeBlocked(timeBlocked);
197    }
198
199
200    public boolean isBlockedForFlowControl() {
201        return flowControlInfo.isBlockingOnFlowControl();
202    }
203
204    public void resetFlowControl() {
205        flowControlInfo.reset();
206    }
207
208    public long getTotalTimeBlocked() {
209        return flowControlInfo.getTotalTimeBlocked();
210    }
211
212    public int getPercentageBlocked() {
213        double value = flowControlInfo.getTotalSends() == 0 ? 0 : flowControlInfo.getSendsBlocked() / flowControlInfo.getTotalSends();
214        return (int) value * 100;
215    }
216
217
218    public static class FlowControlInfo {
219        private AtomicBoolean blockingOnFlowControl = new AtomicBoolean();
220        private AtomicLong totalSends = new AtomicLong();
221        private AtomicLong sendsBlocked = new AtomicLong();
222        private AtomicLong totalTimeBlocked = new AtomicLong();
223
224
225        public boolean isBlockingOnFlowControl() {
226            return blockingOnFlowControl.get();
227        }
228
229        public void setBlockingOnFlowControl(boolean blockingOnFlowControl) {
230            this.blockingOnFlowControl.set(blockingOnFlowControl);
231            if (blockingOnFlowControl) {
232                incrementSendBlocked();
233            }
234        }
235
236
237        public long getTotalSends() {
238            return totalSends.get();
239        }
240
241        public void incrementSend() {
242            this.totalSends.incrementAndGet();
243        }
244
245        public long getSendsBlocked() {
246            return sendsBlocked.get();
247        }
248
249        public void incrementSendBlocked() {
250            this.sendsBlocked.incrementAndGet();
251        }
252
253        public long getTotalTimeBlocked() {
254            return totalTimeBlocked.get();
255        }
256
257        public void incrementTimeBlocked(long time) {
258            this.totalTimeBlocked.addAndGet(time);
259        }
260
261        public void reset() {
262            blockingOnFlowControl.set(false);
263            totalSends.set(0);
264            sendsBlocked.set(0);
265            totalTimeBlocked.set(0);
266
267        }
268    }
269}