001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jms.listener.endpoint; 018 019import java.util.Map; 020 021import javax.jms.JMSException; 022import javax.jms.Queue; 023import javax.jms.Session; 024import javax.jms.Topic; 025import javax.resource.spi.ActivationSpec; 026import javax.resource.spi.ResourceAdapter; 027 028import org.springframework.beans.BeanUtils; 029import org.springframework.beans.BeanWrapper; 030import org.springframework.beans.PropertyAccessorFactory; 031import org.springframework.jms.support.destination.DestinationResolutionException; 032import org.springframework.jms.support.destination.DestinationResolver; 033import org.springframework.lang.Nullable; 034 035/** 036 * Standard implementation of the {@link JmsActivationSpecFactory} interface. 037 * Supports the standard JMS properties as defined by the JMS 1.5 specification 038 * (Appendix B); ignores Spring's "maxConcurrency" and "prefetchSize" settings. 039 * 040 * <p>The 'activationSpecClass' property is required, explicitly defining 041 * the fully-qualified class name of the provider's ActivationSpec class 042 * (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec"). 043 * 044 * <p>Check out {@link DefaultJmsActivationSpecFactory} for an extended variant 045 * of this class, supporting some further default conventions beyond the plain 046 * JMS 1.5 specification. 047 * 048 * @author Juergen Hoeller 049 * @since 2.5 050 * @see #setActivationSpecClass 051 * @see DefaultJmsActivationSpecFactory 052 */ 053public class StandardJmsActivationSpecFactory implements JmsActivationSpecFactory { 054 055 @Nullable 056 private Class<?> activationSpecClass; 057 058 @Nullable 059 private Map<String, String> defaultProperties; 060 061 @Nullable 062 private DestinationResolver destinationResolver; 063 064 065 /** 066 * Specify the fully-qualified ActivationSpec class name for the target 067 * provider (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec"). 068 */ 069 public void setActivationSpecClass(Class<?> activationSpecClass) { 070 this.activationSpecClass = activationSpecClass; 071 } 072 073 /** 074 * Specify custom default properties, with String keys and String values. 075 * <p>Applied to each ActivationSpec object before it gets populated with 076 * listener-specific settings. Allows for configuring vendor-specific properties 077 * beyond the Spring-defined settings in {@link JmsActivationSpecConfig}. 078 */ 079 public void setDefaultProperties(Map<String, String> defaultProperties) { 080 this.defaultProperties = defaultProperties; 081 } 082 083 /** 084 * Set the DestinationResolver to use for resolving destination names 085 * into the JCA 1.5 ActivationSpec "destination" property. 086 * <p>If not specified, destination names will simply be passed in as Strings. 087 * If specified, destination names will be resolved into Destination objects first. 088 * <p>Note that a DestinationResolver for use with this factory must be 089 * able to work <i>without</i> an active JMS Session: e.g. 090 * {@link org.springframework.jms.support.destination.JndiDestinationResolver} 091 * or {@link org.springframework.jms.support.destination.BeanFactoryDestinationResolver} 092 * but not {@link org.springframework.jms.support.destination.DynamicDestinationResolver}. 093 */ 094 public void setDestinationResolver(@Nullable DestinationResolver destinationResolver) { 095 this.destinationResolver = destinationResolver; 096 } 097 098 /** 099 * Return the {@link DestinationResolver} to use for resolving destinations names. 100 */ 101 @Nullable 102 public DestinationResolver getDestinationResolver() { 103 return this.destinationResolver; 104 } 105 106 107 @Override 108 public ActivationSpec createActivationSpec(ResourceAdapter adapter, JmsActivationSpecConfig config) { 109 Class<?> activationSpecClassToUse = this.activationSpecClass; 110 if (activationSpecClassToUse == null) { 111 activationSpecClassToUse = determineActivationSpecClass(adapter); 112 if (activationSpecClassToUse == null) { 113 throw new IllegalStateException("Property 'activationSpecClass' is required"); 114 } 115 } 116 117 ActivationSpec spec = (ActivationSpec) BeanUtils.instantiateClass(activationSpecClassToUse); 118 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(spec); 119 if (this.defaultProperties != null) { 120 bw.setPropertyValues(this.defaultProperties); 121 } 122 populateActivationSpecProperties(bw, config); 123 return spec; 124 } 125 126 /** 127 * Determine the ActivationSpec class for the given ResourceAdapter, 128 * if possible. Called if no 'activationSpecClass' has been set explicitly 129 * @param adapter the ResourceAdapter to check 130 * @return the corresponding ActivationSpec class, or {@code null} 131 * if not determinable 132 * @see #setActivationSpecClass 133 */ 134 @Nullable 135 protected Class<?> determineActivationSpecClass(ResourceAdapter adapter) { 136 return null; 137 } 138 139 /** 140 * Populate the given ApplicationSpec object with the settings 141 * defined in the given configuration object. 142 * <p>This implementation applies all standard JMS settings, but ignores 143 * "maxConcurrency" and "prefetchSize" - not supported in standard JCA 1.5. 144 * @param bw the BeanWrapper wrapping the ActivationSpec object 145 * @param config the configured object holding common JMS settings 146 */ 147 protected void populateActivationSpecProperties(BeanWrapper bw, JmsActivationSpecConfig config) { 148 String destinationName = config.getDestinationName(); 149 if (destinationName != null) { 150 boolean pubSubDomain = config.isPubSubDomain(); 151 Object destination = destinationName; 152 if (this.destinationResolver != null) { 153 try { 154 destination = this.destinationResolver.resolveDestinationName(null, destinationName, pubSubDomain); 155 } 156 catch (JMSException ex) { 157 throw new DestinationResolutionException( 158 "Cannot resolve destination name [" + destinationName + "]", ex); 159 } 160 } 161 bw.setPropertyValue("destination", destination); 162 bw.setPropertyValue("destinationType", pubSubDomain ? Topic.class.getName() : Queue.class.getName()); 163 } 164 165 if (bw.isWritableProperty("subscriptionDurability")) { 166 bw.setPropertyValue("subscriptionDurability", config.isSubscriptionDurable() ? "Durable" : "NonDurable"); 167 } 168 else if (config.isSubscriptionDurable()) { 169 // Standard JCA 1.5 "subscriptionDurability" apparently not supported... 170 throw new IllegalArgumentException("Durable subscriptions not supported by underlying provider"); 171 } 172 if (config.isSubscriptionShared()) { 173 throw new IllegalArgumentException("Shared subscriptions not supported for JCA-driven endpoints"); 174 } 175 176 if (config.getSubscriptionName() != null) { 177 bw.setPropertyValue("subscriptionName", config.getSubscriptionName()); 178 } 179 if (config.getClientId() != null) { 180 bw.setPropertyValue("clientId", config.getClientId()); 181 } 182 if (config.getMessageSelector() != null) { 183 bw.setPropertyValue("messageSelector", config.getMessageSelector()); 184 } 185 applyAcknowledgeMode(bw, config.getAcknowledgeMode()); 186 } 187 188 /** 189 * Apply the specified acknowledge mode to the ActivationSpec object. 190 * <p>This implementation applies the standard JCA 1.5 acknowledge modes 191 * "Auto-acknowledge" and "Dups-ok-acknowledge". It throws an exception in 192 * case of {@code CLIENT_ACKNOWLEDGE} or {@code SESSION_TRANSACTED} 193 * having been requested. 194 * @param bw the BeanWrapper wrapping the ActivationSpec object 195 * @param ackMode the configured acknowledge mode 196 * (according to the constants in {@link javax.jms.Session} 197 * @see javax.jms.Session#AUTO_ACKNOWLEDGE 198 * @see javax.jms.Session#DUPS_OK_ACKNOWLEDGE 199 * @see javax.jms.Session#CLIENT_ACKNOWLEDGE 200 * @see javax.jms.Session#SESSION_TRANSACTED 201 */ 202 protected void applyAcknowledgeMode(BeanWrapper bw, int ackMode) { 203 if (ackMode == Session.SESSION_TRANSACTED) { 204 throw new IllegalArgumentException("No support for SESSION_TRANSACTED: Only \"Auto-acknowledge\" " + 205 "and \"Dups-ok-acknowledge\" supported in standard JCA 1.5"); 206 } 207 else if (ackMode == Session.CLIENT_ACKNOWLEDGE) { 208 throw new IllegalArgumentException("No support for CLIENT_ACKNOWLEDGE: Only \"Auto-acknowledge\" " + 209 "and \"Dups-ok-acknowledge\" supported in standard JCA 1.5"); 210 } 211 else if (bw.isWritableProperty("acknowledgeMode")) { 212 bw.setPropertyValue("acknowledgeMode", 213 ackMode == Session.DUPS_OK_ACKNOWLEDGE ? "Dups-ok-acknowledge" : "Auto-acknowledge"); 214 } 215 else if (ackMode == Session.DUPS_OK_ACKNOWLEDGE) { 216 // Standard JCA 1.5 "acknowledgeMode" apparently not supported (e.g. WebSphere MQ 6.0.2.1) 217 throw new IllegalArgumentException("Dups-ok-acknowledge not supported by underlying provider"); 218 } 219 } 220 221}