org.apache.activemq.broker.scheduler.SchedulerBroker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activemq-broker Show documentation
Show all versions of activemq-broker Show documentation
The ActiveMQ Message Broker implementation
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.broker.scheduler;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import jakarta.jms.MessageFormatException;
import org.apache.activemq.ScheduledMessage;
import org.apache.activemq.advisory.AdvisorySupport;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerFilter;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.Connection;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.Connector;
import org.apache.activemq.broker.ProducerBrokerExchange;
import org.apache.activemq.broker.region.ConnectionStatistics;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.Command;
import org.apache.activemq.command.ConnectionControl;
import org.apache.activemq.command.ExceptionResponse;
import org.apache.activemq.command.Message;
import org.apache.activemq.command.MessageId;
import org.apache.activemq.command.ProducerId;
import org.apache.activemq.command.ProducerInfo;
import org.apache.activemq.command.Response;
import org.apache.activemq.openwire.OpenWireFormat;
import org.apache.activemq.security.SecurityContext;
import org.apache.activemq.state.ProducerState;
import org.apache.activemq.transaction.Synchronization;
import org.apache.activemq.usage.JobSchedulerUsage;
import org.apache.activemq.usage.SystemUsage;
import org.apache.activemq.util.ByteSequence;
import org.apache.activemq.util.IdGenerator;
import org.apache.activemq.util.LongSequenceGenerator;
import org.apache.activemq.util.TypeConversionSupport;
import org.apache.activemq.wireformat.WireFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SchedulerBroker extends BrokerFilter implements JobListener {
private static final Logger LOG = LoggerFactory.getLogger(SchedulerBroker.class);
private static final IdGenerator ID_GENERATOR = new IdGenerator();
private static final LongSequenceGenerator longGenerator = new LongSequenceGenerator();
/**
* The max repeat value allowed to prevent clients from causing DoS issues with huge repeat counts
*/
private static final int MAX_REPEAT_ALLOWED = 1000;
private final LongSequenceGenerator messageIdGenerator = new LongSequenceGenerator();
private final AtomicBoolean started = new AtomicBoolean();
private final WireFormat wireFormat = new OpenWireFormat();
private final ConnectionContext context = new ConnectionContext();
private final ProducerId producerId = new ProducerId();
private final SystemUsage systemUsage;
private final JobSchedulerStore store;
private JobScheduler scheduler;
private int maxRepeatAllowed = MAX_REPEAT_ALLOWED;
public SchedulerBroker(BrokerService brokerService, Broker next, JobSchedulerStore store) throws Exception {
super(next);
this.store = store;
this.producerId.setConnectionId(ID_GENERATOR.generateId());
this.context.setSecurityContext(SecurityContext.BROKER_SECURITY_CONTEXT);
// we only get response on unexpected error
this.context.setConnection(new Connection() {
private final long connectedTimestamp = System.currentTimeMillis();
@Override
public Connector getConnector() {
return null;
}
@Override
public void dispatchSync(Command message) {
if (message instanceof ExceptionResponse) {
LOG.warn("Unexpected response: {}", message);
}
}
@Override
public void dispatchAsync(Command command) {
if (command instanceof ExceptionResponse) {
LOG.warn("Unexpected response: {}", command);
}
}
@Override
public Response service(Command command) {
return null;
}
@Override
public void serviceException(Throwable error) {
LOG.warn("Unexpected exception", error);
}
@Override
public boolean isSlow() {
return false;
}
@Override
public boolean isBlocked() {
return false;
}
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean isActive() {
return false;
}
@Override
public int getDispatchQueueSize() {
return 0;
}
@Override
public ConnectionStatistics getStatistics() {
return null;
}
@Override
public boolean isManageable() {
return false;
}
@Override
public String getRemoteAddress() {
return null;
}
@Override
public void serviceExceptionAsync(IOException e) {
LOG.warn("Unexpected async ioexception", e);
}
@Override
public String getConnectionId() {
return null;
}
@Override
public boolean isNetworkConnection() {
return false;
}
@Override
public boolean isFaultTolerantConnection() {
return false;
}
@Override
public void updateClient(ConnectionControl control) {}
@Override
public int getActiveTransactionCount() {
return 0;
}
@Override
public Long getOldestActiveTransactionDuration() {
return null;
}
@Override
public Long getConnectedTimestamp() {
return connectedTimestamp;
}
@Override
public void start() throws Exception {}
@Override
public void stop() throws Exception {}
});
this.context.setBroker(next);
this.systemUsage = brokerService.getSystemUsage();
wireFormat.setVersion(brokerService.getStoreOpenWireVersion());
}
public synchronized JobScheduler getJobScheduler() throws Exception {
return new JobSchedulerFacade(this);
}
@Override
public void start() throws Exception {
this.started.set(true);
getInternalScheduler();
super.start();
}
@Override
public void stop() throws Exception {
if (this.started.compareAndSet(true, false)) {
if (this.store != null) {
this.store.stop();
}
if (this.scheduler != null) {
this.scheduler.removeListener(this);
this.scheduler = null;
}
}
super.stop();
}
@Override
public void send(ProducerBrokerExchange producerExchange, final Message messageSend) throws Exception {
ConnectionContext context = producerExchange.getConnectionContext();
final String jobId = (String) messageSend.getProperty(ScheduledMessage.AMQ_SCHEDULED_ID);
final Object cronValue = messageSend.getProperty(ScheduledMessage.AMQ_SCHEDULED_CRON);
final Object periodValue = messageSend.getProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD);
final Object delayValue = messageSend.getProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY);
String physicalName = messageSend.getDestination().getPhysicalName();
boolean schedularManage = physicalName.regionMatches(true, 0, ScheduledMessage.AMQ_SCHEDULER_MANAGEMENT_DESTINATION, 0,
ScheduledMessage.AMQ_SCHEDULER_MANAGEMENT_DESTINATION.length());
if (schedularManage == true) {
JobScheduler scheduler = getInternalScheduler();
ActiveMQDestination replyTo = messageSend.getReplyTo();
String action = (String) messageSend.getProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION);
if (action != null) {
Object startTime = messageSend.getProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION_START_TIME);
Object endTime = messageSend.getProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION_END_TIME);
if (replyTo != null && action.equals(ScheduledMessage.AMQ_SCHEDULER_ACTION_BROWSE)) {
if (startTime != null && endTime != null) {
long start = (Long) TypeConversionSupport.convert(startTime, Long.class);
long finish = (Long) TypeConversionSupport.convert(endTime, Long.class);
for (Job job : scheduler.getAllJobs(start, finish)) {
sendScheduledJob(producerExchange.getConnectionContext(), job, replyTo);
}
} else {
for (Job job : scheduler.getAllJobs()) {
sendScheduledJob(producerExchange.getConnectionContext(), job, replyTo);
}
}
}
if (jobId != null && action.equals(ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVE)) {
scheduler.remove(jobId);
} else if (action.equals(ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVEALL)) {
if (startTime != null && endTime != null) {
long start = (Long) TypeConversionSupport.convert(startTime, Long.class);
long finish = (Long) TypeConversionSupport.convert(endTime, Long.class);
scheduler.removeAllJobs(start, finish);
} else {
scheduler.removeAllJobs();
}
}
}
} else if ((cronValue != null || periodValue != null || delayValue != null) && jobId == null) {
// Check for room in the job scheduler store
if (systemUsage.getJobSchedulerUsage() != null) {
JobSchedulerUsage usage = systemUsage.getJobSchedulerUsage();
if (usage.isFull()) {
final String logMessage = "Job Scheduler Store is Full (" +
usage.getPercentUsage() + "% of " + usage.getLimit() +
"). Stopping producer (" + messageSend.getProducerId() +
") to prevent flooding of the job scheduler store." +
" See http://activemq.apache.org/producer-flow-control.html for more info";
long start = System.currentTimeMillis();
long nextWarn = start;
while (!usage.waitForSpace(1000)) {
if (context.getStopping().get()) {
throw new IOException("Connection closed, send aborted.");
}
long now = System.currentTimeMillis();
if (now >= nextWarn) {
LOG.info("{}: {} (blocking for: {}s)", usage, logMessage, (now - start) / 1000);
nextWarn = now + 30000l;
}
}
}
}
if (context.isInTransaction()) {
context.getTransaction().addSynchronization(new Synchronization() {
@Override
public void afterCommit() throws Exception {
doSchedule(messageSend, cronValue, periodValue, delayValue);
}
});
} else {
doSchedule(messageSend, cronValue, periodValue, delayValue);
}
} else {
super.send(producerExchange, messageSend);
}
}
private void doSchedule(Message messageSend, Object cronValue, Object periodValue, Object delayValue) throws Exception {
long delay = 0;
long period = 0;
int repeat = 0;
String cronEntry = "";
// clear transaction context
Message msg = messageSend.copy();
msg.setTransactionId(null);
org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(msg);
if (cronValue != null) {
cronEntry = cronValue.toString();
}
if (periodValue != null) {
period = (Long) TypeConversionSupport.convert(periodValue, Long.class);
}
if (delayValue != null) {
delay = (Long) TypeConversionSupport.convert(delayValue, Long.class);
}
Object repeatValue = msg.getProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT);
if (repeatValue != null) {
repeat = (Integer) TypeConversionSupport.convert(repeatValue, Integer.class);
if (repeat > maxRepeatAllowed) {
throw new MessageFormatException("The scheduled repeat value is too large");
}
}
//job id should be unique for every job (Same format as MessageId)
MessageId jobId = new MessageId(messageSend.getMessageId().getProducerId(), longGenerator.getNextSequenceId());
getInternalScheduler().schedule(jobId.toString(),
new ByteSequence(packet.data, packet.offset, packet.length), cronEntry, delay, period, repeat);
}
@Override
public void scheduledJob(String id, ByteSequence job) {
org.apache.activemq.util.ByteSequence packet = new org.apache.activemq.util.ByteSequence(job.getData(), job.getOffset(), job.getLength());
try {
Message messageSend = (Message) wireFormat.unmarshal(packet);
messageSend.setOriginalTransactionId(null);
Object repeatValue = messageSend.getProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT);
Object cronValue = messageSend.getProperty(ScheduledMessage.AMQ_SCHEDULED_CRON);
String cronStr = cronValue != null ? cronValue.toString() : null;
int repeat = 0;
if (repeatValue != null) {
repeat = (Integer) TypeConversionSupport.convert(repeatValue, Integer.class);
if (repeat > maxRepeatAllowed) {
throw new MessageFormatException("The scheduled repeat value is too large");
}
}
if (repeat != 0 || cronStr != null && cronStr.length() > 0) {
// create a unique id - the original message could be sent
// lots of times
messageSend.setMessageId(new MessageId(producerId, messageIdGenerator.getNextSequenceId()));
}
// Add the jobId as a property
messageSend.setProperty("scheduledJobId", id);
// if this goes across a network - we don't want it rescheduled
messageSend.removeProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD);
messageSend.removeProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY);
messageSend.removeProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT);
messageSend.removeProperty(ScheduledMessage.AMQ_SCHEDULED_CRON);
if (messageSend.getTimestamp() > 0 && messageSend.getExpiration() > 0) {
long oldExpiration = messageSend.getExpiration();
long newTimeStamp = System.currentTimeMillis();
long timeToLive = 0;
long oldTimestamp = messageSend.getTimestamp();
if (oldExpiration > 0) {
timeToLive = oldExpiration - oldTimestamp;
}
long expiration = timeToLive + newTimeStamp;
if (expiration > oldExpiration) {
if (timeToLive > 0 && expiration > 0) {
messageSend.setExpiration(expiration);
}
messageSend.setTimestamp(newTimeStamp);
LOG.debug("Set message {} timestamp from {} to {}",
messageSend.getMessageId(), oldTimestamp, newTimeStamp);
}
}
// Repackage the message contents prior to send now that all updates are complete.
messageSend.beforeMarshall(wireFormat);
final ProducerBrokerExchange producerExchange = new ProducerBrokerExchange();
producerExchange.setConnectionContext(context);
producerExchange.setMutable(true);
producerExchange.setProducerState(new ProducerState(new ProducerInfo()));
super.send(producerExchange, messageSend);
} catch (Exception e) {
LOG.error("Failed to send scheduled message {}", id, e);
}
}
protected synchronized JobScheduler getInternalScheduler() throws Exception {
if (this.started.get()) {
if (this.scheduler == null && store != null) {
this.scheduler = store.getJobScheduler("JMS");
this.scheduler.addListener(this);
this.scheduler.startDispatching();
}
return this.scheduler;
}
return null;
}
protected void sendScheduledJob(ConnectionContext context, Job job, ActiveMQDestination replyTo) throws Exception {
org.apache.activemq.util.ByteSequence packet = new org.apache.activemq.util.ByteSequence(job.getPayload());
try {
Message msg = (Message) this.wireFormat.unmarshal(packet);
msg.setOriginalTransactionId(null);
msg.setPersistent(false);
msg.setType(AdvisorySupport.ADIVSORY_MESSAGE_TYPE);
msg.setMessageId(new MessageId(this.producerId, this.messageIdGenerator.getNextSequenceId()));
// Preserve original destination
msg.setOriginalDestination(msg.getDestination());
msg.setDestination(replyTo);
msg.setResponseRequired(false);
msg.setProducerId(this.producerId);
// Add the jobId as a property
msg.setProperty("scheduledJobId", job.getJobId());
final boolean originalFlowControl = context.isProducerFlowControl();
final ProducerBrokerExchange producerExchange = new ProducerBrokerExchange();
producerExchange.setConnectionContext(context);
producerExchange.setMutable(true);
producerExchange.setProducerState(new ProducerState(new ProducerInfo()));
try {
context.setProducerFlowControl(false);
this.next.send(producerExchange, msg);
} finally {
context.setProducerFlowControl(originalFlowControl);
}
} catch (Exception e) {
LOG.error("Failed to send scheduled message {}", job.getJobId(), e);
}
}
public int getMaxRepeatAllowed() {
return maxRepeatAllowed;
}
public void setMaxRepeatAllowed(int maxRepeatAllowed) {
this.maxRepeatAllowed = maxRepeatAllowed;
}
}