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