Java tutorial
/* * Copyright 2015 Adaptris Ltd. * * Licensed 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 com.adaptris.core.jms; import static com.adaptris.core.AdaptrisMessageFactory.defaultIfNull; import static com.adaptris.core.jms.JmsConstants.JMS_DELIVERY_MODE; import static com.adaptris.core.jms.JmsConstants.JMS_EXPIRATION; import static com.adaptris.core.jms.JmsConstants.JMS_PRIORITY; import static com.adaptris.core.jms.NullCorrelationIdSource.defaultIfNull; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; import javax.validation.Valid; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import com.adaptris.annotation.AdvancedConfig; import com.adaptris.annotation.AutoPopulated; import com.adaptris.annotation.InputFieldDefault; import com.adaptris.annotation.Removal; import com.adaptris.core.AdaptrisMessage; import com.adaptris.core.AdaptrisMessageListener; import com.adaptris.core.CoreException; import com.adaptris.core.ProduceDestination; import com.adaptris.core.RequestReplyProducerImp; import com.adaptris.core.util.Args; import com.adaptris.core.util.LifecycleHelper; import com.adaptris.util.NumberUtils; public abstract class JmsProducerImpl extends RequestReplyProducerImp implements JmsActorConfig { // This is used to track the current message id, for the session factory. // There doesn't appear to be a good way of doing this, everything *depends* on currentSession() // which means // that we need execute setupSession multiple times... private transient String CURRENT_MESSAGE_ID = ""; private static final int DEFAULT_PRIORITY = 4; private static final String EXPIRATION_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; private static final String EXPIRATION_DATE_REGEXP = "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.*$"; @NotNull @AutoPopulated @Pattern(regexp = "AUTO_ACKNOWLEDGE|CLIENT_ACKNOWLEDGE|DUPS_OK_ACKNOWLEDGE|[0-9]+") @AdvancedConfig private String acknowledgeMode; @NotNull @AutoPopulated @Valid private MessageTypeTranslator messageTranslator; @Valid @AdvancedConfig private CorrelationIdSource correlationIdSource; @AdvancedConfig @InputFieldDefault(value = "false") private Boolean captureOutgoingMessageDetails; @NotNull @AutoPopulated @Pattern(regexp = "PERSISTENT|NON_PERSISTENT|[0-9]+") @AdvancedConfig private String deliveryMode; @Min(0) @Max(9) @AdvancedConfig @InputFieldDefault(value = "4") private Integer priority; @Min(0) @AdvancedConfig @InputFieldDefault(value = "0") private Long ttl; @AdvancedConfig @InputFieldDefault(value = "false") private Boolean perMessageProperties; @AutoPopulated @NotNull @Valid @AdvancedConfig private ProducerSessionFactory sessionFactory; protected transient ProducerSession producerSession; private transient Boolean transactedSession; private transient long rollbackTimeout = 30000; private enum ExpirationConverter { Milliseconds { @Override Date convert(String s) { return new Date(Long.parseLong(s)); } @Override boolean convertable(String s) { boolean result = false; try { Long.parseLong(s); result = true; } catch (NumberFormatException e) { } return result; } }, ISO8601 { @Override Date convert(String s) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat(EXPIRATION_DATE_FORMAT); return sdf.parse(s); } @Override boolean convertable(String s) { return s.matches(EXPIRATION_DATE_REGEXP); } }; abstract Date convert(String s) throws ParseException; abstract boolean convertable(String s); }; public JmsProducerImpl() { // defaults setAcknowledgeMode(AcknowledgeMode.Mode.CLIENT_ACKNOWLEDGE.name()); setDeliveryMode(DeliveryMode.Mode.PERSISTENT.name()); setMessageTranslator(new TextMessageTranslator()); setSessionFactory(new DefaultProducerSessionFactory()); } public JmsProducerImpl(ProduceDestination d) { this(); setDestination(d); } @Override public void prepare() throws CoreException { LifecycleHelper.prepare(getMessageTranslator()); LifecycleHelper.prepare(getSessionFactory()); } @Override public void init() throws CoreException { messageTranslator.registerMessageFactory(defaultIfNull(getMessageFactory())); LifecycleHelper.init(messageTranslator); LifecycleHelper.init(getSessionFactory()); } @Override public void stop() { LifecycleHelper.stop(getMessageTranslator()); LifecycleHelper.stop(getSessionFactory()); CURRENT_MESSAGE_ID = ""; producerSession = null; } @Override public void start() throws CoreException { LifecycleHelper.start(getMessageTranslator()); LifecycleHelper.start(getSessionFactory()); } @Override public void close() { LifecycleHelper.close(getMessageTranslator()); LifecycleHelper.close(getSessionFactory()); } @Override protected long defaultTimeout() { return 0L; } protected ProducerSession setupSession(AdaptrisMessage msg) throws JMSException { if (!msg.getUniqueId().equals(CURRENT_MESSAGE_ID) || producerSession == null) { producerSession = getSessionFactory().createProducerSession(this, msg); configuredMessageTranslator().registerSession(producerSession.getSession()); CURRENT_MESSAGE_ID = msg.getUniqueId(); } return producerSession; } protected void logLinkedException(String prefix, Exception e) { if (!(e instanceof JMSException)) return; JMSException je = (JMSException) e; currentLogger().warn("JMSException caught [{}], [{}]", StringUtils.defaultIfEmpty(prefix, ""), e.getMessage()); if (je.getLinkedException() != null) { currentLogger().trace("Linked Exception available..."); currentLogger().trace(je.getLinkedException().getMessage(), je.getLinkedException()); } else { currentLogger().trace("No Linked Exception available"); } } protected Destination createDestination(ProduceDestination d, AdaptrisMessage msg) throws CoreException { Destination dest = null; if (d instanceof JmsReplyToDestination) { dest = ((JmsReplyToDestination) d).retrieveJmsDestination(msg); } return dest; } protected int calculateDeliveryMode(AdaptrisMessage msg, String defaultDeliveryMode) { int deliveryMode; if (msg.headersContainsKey(JMS_DELIVERY_MODE)) { deliveryMode = DeliveryMode.getMode(msg.getMetadataValue(JMS_DELIVERY_MODE)); } else { deliveryMode = DeliveryMode.getMode(defaultDeliveryMode); } currentLogger().trace("deliveryMode overridden to be {}", deliveryMode); return deliveryMode; } protected long calculateTimeToLive(AdaptrisMessage msg, Long defaultTTL) throws JMSException { long ttl = NumberUtils.toLongDefaultIfNull(defaultTTL, 0); try { if (msg.headersContainsKey(JMS_EXPIRATION)) { Date expiration = new Date(); String value = msg.getMetadataValue(JMS_EXPIRATION); for (ExpirationConverter c : ExpirationConverter.values()) { if (c.convertable(value)) { expiration = c.convert(value); break; } } currentLogger().trace("Expiration Date from metadata is " + expiration); ttl = expiration.getTime() - System.currentTimeMillis(); if (ttl < 0) { currentLogger().trace("TTL calculated as negative number, using configured ttl"); ttl = NumberUtils.toLongDefaultIfNull(defaultTTL, 0); } } } catch (ParseException e) { JmsUtils.rethrowJMSException(e); } currentLogger().trace("Time to live overridden to be " + ttl); return ttl; } protected Message translate(AdaptrisMessage msg, Destination replyTo) throws JMSException { Message result = configuredMessageTranslator().translate(msg); configuredCorrelationIdSource().processCorrelationId(msg, result); if (replyTo != null) { // OpenJMS is fussy about null here result.setJMSReplyTo(replyTo); } return result; } protected int calculatePriority(AdaptrisMessage msg, Integer defaultPriority) { int priority = NumberUtils.toIntDefaultIfNull(defaultPriority, DEFAULT_PRIORITY); if (msg.headersContainsKey(JMS_PRIORITY)) { priority = Integer.parseInt(msg.getMetadataValue(JMS_PRIORITY)); } currentLogger().trace("Priority overridden to be {}", priority); return priority; } /** * <p> * Returns the JMS delivery mode. * </p> * * @return the JMS delivery mode */ public String getDeliveryMode() { return deliveryMode; } /** * <p> * Sets the JMS delivery mode. * </p> * <p> * The value may be either "PERSISENT", "NON_PERSISTENT", or the int corresponding to the * javax.jms.DeliveryMode constant. * * @param i the JMS delivery mode */ public void setDeliveryMode(String i) { deliveryMode = i; } /** * <p> * Returns the JMS priority. * </p> * * @return the JMS priority */ public Integer getPriority() { return priority; } /** * <p> * Sets the JMS priority. Valid values are 0 to 9. * </p> * * @param i the JMS priority */ public void setPriority(Integer i) { priority = i; } protected int messagePriority() { return NumberUtils.toIntDefaultIfNull(getPriority(), DEFAULT_PRIORITY); } /** * <p> * Returns the time to live. 0 means live forever. * </p> * * @return the time to live */ public Long getTtl() { return ttl; } /** * @deprecated use {@link #getTtl()} instead. */ @Deprecated @Removal(version = "3.9.0") public long getTimeToLive() { return timeToLive(); } protected long timeToLive() { return NumberUtils.toLongDefaultIfNull(getTtl(), 0); } /** * <p> * Sets the time to live. * </p> * * @param l the time to live */ public void setTtl(Long l) { ttl = l; } /** * <p> * Sets the <code>MessageTypeTranslator</code> to use. * </p> * * @param translator the <code>MessageTypeTranslator</code> to use */ public void setMessageTranslator(MessageTypeTranslator translator) { messageTranslator = Args.notNull(translator, "messageTranslator"); } /** * <p> * Returns the <code>MessageTypeTranslator</code> to use. * </p> * * @return the <code>MessageTypeTranslator</code> to use */ public MessageTypeTranslator getMessageTranslator() { return messageTranslator; } /** * <p> * Sets the JMS acknowledge mode. * </p> * <p> * The value may be AUTO_KNOWLEDGE, CLIENT_ACKNOWLEDGE, DUPS_OK_ACKNOWLEDGE or the int values * corresponding to the JMS Session Constant * </p> */ public void setAcknowledgeMode(String s) { acknowledgeMode = s; } /** * <p> * Returns the JMS acknowledge mode. * </p> * * @return the JMS acknowledge mode */ public String getAcknowledgeMode() { return acknowledgeMode; } /** * <p> * Returns correlationIdSource. * </p> * * @return correlationIdSource */ public CorrelationIdSource getCorrelationIdSource() { return correlationIdSource; } /** * <p> * Sets correlationIdSource. * </p> * * @param c the correlationIdSource to set */ public void setCorrelationIdSource(CorrelationIdSource c) { correlationIdSource = c; } /** * @return the perMessageProperties */ public Boolean getPerMessageProperties() { return perMessageProperties; } /** * Specify message properties per message rather than per producer. * <p> * If set to true, then each message that is produced can have its own individual time-to-live, * priority and delivery mode. These properties are taken from the producer's configuration but * can be overriden via metadata. * </p> * * @see JmsConstants#JMS_PRIORITY * @see JmsConstants#JMS_DELIVERY_MODE * @see JmsConstants#JMS_EXPIRATION * @param b the perMessageProperties to set */ public void setPerMessageProperties(Boolean b) { perMessageProperties = b; } protected boolean perMessageProperties() { return BooleanUtils.toBooleanDefaultIfNull(getPerMessageProperties(), false); } // BUG#915 protected void commit() throws JMSException { if (currentSession().getTransacted()) { currentLogger().trace("Committing transacted session"); currentSession().commit(); } } // BUG#915 protected void rollback() { boolean tryRollback = false; try { tryRollback = currentSession().getTransacted(); } catch (JMSException f) { // session is probably broken, can't rollback anyway. } if (tryRollback) { try { currentLogger().trace("Attempting to rollback transacted session"); currentSession().rollback(); } catch (JMSException f) { currentLogger().trace("Error encountered rolling back transaction : {}", f.getMessage()); } } } protected void acknowledge(Message msg) throws JMSException { if (msg == null) { return; } if (configuredAcknowledgeMode() != Session.AUTO_ACKNOWLEDGE && !currentSession().getTransacted()) { msg.acknowledge(); } } @Override public CorrelationIdSource configuredCorrelationIdSource() { return defaultIfNull(getCorrelationIdSource()); } @Override public MessageTypeTranslator configuredMessageTranslator() { return getMessageTranslator(); } @Override public int configuredAcknowledgeMode() { return AcknowledgeMode.getMode(getAcknowledgeMode()); } @Override public AdaptrisMessageListener configuredMessageListener() { throw new UnsupportedOperationException("No Message Listener associated with a producer"); } @Override public Session currentSession() { return producerSession.getSession(); } @Override public Logger currentLogger() { return log; } /** * @return the transacted */ private Boolean getTransactedSession() { return transactedSession; } /** * @param b the transacted to set */ private void setTransacted(boolean b) { transactedSession = b; } boolean transactedSession() { return BooleanUtils.toBooleanDefaultIfNull(getTransactedSession(), false); } /** * @return the rollbackTimeout */ private long getRollbackTimeout() { return rollbackTimeout; } /** * Not directly configurable, as it is done by JmsTransactedWorkflow. * * @param l the rollbackTimeout to set */ private void setRollbackTimeout(long l) { rollbackTimeout = l; } @Override public long rollbackTimeout() { return rollbackTimeout; } @Override public boolean isManagedTransaction() { return false; } protected boolean captureOutgoingMessageDetails() { return BooleanUtils.toBooleanDefaultIfNull(getCaptureOutgoingMessageDetails(), false); } public Boolean getCaptureOutgoingMessageDetails() { return captureOutgoingMessageDetails; } /** * Specify whether or not to capture the outgoing message details as object metadata. * <p> * Some JMS providers may not make information such as {@link Message#getJMSMessageID()} available * until the message is accepted for delivery by the provider. Set this to be true, if you need to * make use of that information later on in the workflow. All information captured is stored * against the object metadata key "javax.jms.Message.{propertyName}" e.g. * "javax.jms.Message.JMSMessageID" where JMSMessageID is derived from the associated * {@link JmsConstants} constant. * </p> * * @param b true to capture standard JMS Headers as object metadata post produce. If unspecified, * defaults to false. */ public void setCaptureOutgoingMessageDetails(Boolean b) { this.captureOutgoingMessageDetails = b; } protected void captureOutgoingMessageDetails(Message jmsMsg, AdaptrisMessage msg) { String objectMetadataPrefix = Message.class.getCanonicalName() + "."; Map<String, String> jmsDetails = new HashMap<String, String>(); for (MetadataHandler.JmsPropertyHandler handler : MetadataHandler.JmsPropertyHandler.values()) { try { jmsDetails.put(objectMetadataPrefix + handler.getKey(), handler.getValue(jmsMsg)); } catch (JMSException ignore) { } } msg.getObjectHeaders().putAll(jmsDetails); } public ProducerSessionFactory getSessionFactory() { return sessionFactory; } /** * Set the behavioural characteristics of the session used by this producer. * * @param s the {@link ProducerSessionFactory} instance, default is * {@link DefaultProducerSessionFactory} */ public void setSessionFactory(ProducerSessionFactory s) { this.sessionFactory = Args.notNull(s, "sessionFactory"); } }