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     */
017    package org.apache.activemq.ra;
018    
019    import java.beans.IntrospectionException;
020    import java.beans.PropertyDescriptor;
021    import java.io.Serializable;
022    import java.util.ArrayList;
023    import java.util.Iterator;
024    import java.util.List;
025    
026    import javax.jms.Queue;
027    import javax.jms.Session;
028    import javax.jms.Topic;
029    import javax.resource.ResourceException;
030    import javax.resource.spi.InvalidPropertyException;
031    import javax.resource.spi.ResourceAdapter;
032    
033    import org.apache.activemq.RedeliveryPolicy;
034    import org.apache.activemq.command.ActiveMQDestination;
035    import org.apache.activemq.command.ActiveMQQueue;
036    import org.apache.activemq.command.ActiveMQTopic;
037    import org.apache.activemq.selector.SelectorParser;
038    
039    /**
040     * Configures the inbound JMS consumer specification using ActiveMQ
041     * 
042     * @org.apache.xbean.XBean element="activationSpec"
043     *  $Date: 2007-08-11 17:29:21 -0400 (Sat, 11 Aug
044     *          2007) $
045     */
046    public class ActiveMQActivationSpec implements MessageActivationSpec, Serializable {
047    
048        /** Auto-acknowledge constant for <code>acknowledgeMode</code> property * */
049        public static final String AUTO_ACKNOWLEDGE_MODE = "Auto-acknowledge";
050        /** Dups-ok-acknowledge constant for <code>acknowledgeMode</code> property * */
051        public static final String DUPS_OK_ACKNOWLEDGE_MODE = "Dups-ok-acknowledge";
052        /** Durable constant for <code>subscriptionDurability</code> property * */
053        public static final String DURABLE_SUBSCRIPTION = "Durable";
054        /** NonDurable constant for <code>subscriptionDurability</code> property * */
055        public static final String NON_DURABLE_SUBSCRIPTION = "NonDurable";
056        public static final int INVALID_ACKNOWLEDGE_MODE = -1;
057    
058        private static final long serialVersionUID = -7153087544100459975L;
059    
060        private transient MessageResourceAdapter resourceAdapter;
061        private String destinationType;
062        private String messageSelector;
063        private String destination;
064        private String acknowledgeMode = AUTO_ACKNOWLEDGE_MODE;
065        private String userName;
066        private String password;
067        private String clientId;
068        private String subscriptionName;
069        private String subscriptionDurability = NON_DURABLE_SUBSCRIPTION;
070        private String noLocal = "false";
071        private String useRAManagedTransaction = "false";
072        private String maxSessions = "10";
073        private String maxMessagesPerSessions = "10";
074        private String enableBatch = "false";
075        private String maxMessagesPerBatch = "10";
076        private RedeliveryPolicy redeliveryPolicy;
077    
078        /**
079         * @see javax.resource.spi.ActivationSpec#validate()
080         */
081        public void validate() throws InvalidPropertyException {
082            List<String> errorMessages = new ArrayList<String>();
083            List<PropertyDescriptor> propsNotSet = new ArrayList<PropertyDescriptor>();
084            try {
085                if (!isValidDestination(errorMessages)) {
086                    propsNotSet.add(new PropertyDescriptor("destination", ActiveMQActivationSpec.class));
087                }
088                if (!isValidDestinationType(errorMessages)) {
089                    propsNotSet.add(new PropertyDescriptor("destinationType", ActiveMQActivationSpec.class));
090                }
091                if (!isValidAcknowledgeMode(errorMessages)) {
092                    propsNotSet.add(new PropertyDescriptor("acknowledgeMode", ActiveMQActivationSpec.class));
093                }
094                if (!isValidSubscriptionDurability(errorMessages)) {
095                    propsNotSet.add(new PropertyDescriptor("subscriptionDurability", ActiveMQActivationSpec.class));
096                }
097                if (!isValidClientId(errorMessages)) {
098                    propsNotSet.add(new PropertyDescriptor("clientId", ActiveMQActivationSpec.class));
099                }
100                if (!isValidSubscriptionName(errorMessages)) {
101                    propsNotSet.add(new PropertyDescriptor("subscriptionName", ActiveMQActivationSpec.class));
102                }
103                if (!isValidMaxMessagesPerSessions(errorMessages)) {
104                    propsNotSet.add(new PropertyDescriptor("maxMessagesPerSessions", ActiveMQActivationSpec.class));
105                }
106                if (!isValidMaxSessions(errorMessages)) {
107                    propsNotSet.add(new PropertyDescriptor("maxSessions", ActiveMQActivationSpec.class));
108                }
109                if (!isValidMessageSelector(errorMessages)) {
110                    propsNotSet.add(new PropertyDescriptor("messageSelector", ActiveMQActivationSpec.class));
111                }
112                if (!isValidNoLocal(errorMessages)) {
113                    propsNotSet.add(new PropertyDescriptor("noLocal", ActiveMQActivationSpec.class));
114                }
115                if (!isValidUseRAManagedTransaction(errorMessages)) {
116                    propsNotSet.add(new PropertyDescriptor("useRAManagedTransaction", ActiveMQActivationSpec.class));
117                }
118                if (!isValidEnableBatch(errorMessages)) {
119                    propsNotSet.add(new PropertyDescriptor("enableBatch", ActiveMQActivationSpec.class));
120                }
121                if (!isValidMaxMessagesPerBatch(errorMessages)) {
122                    propsNotSet.add(new PropertyDescriptor("maxMessagesPerBatch", ActiveMQActivationSpec.class));
123                }
124    
125            } catch (IntrospectionException e) {
126                e.printStackTrace();
127            }
128    
129            if (propsNotSet.size() > 0) {
130                StringBuffer b = new StringBuffer();
131                b.append("Invalid settings:");
132                for (Iterator<String> iter = errorMessages.iterator(); iter.hasNext();) {
133                    b.append(" ");
134                    b.append(iter.next());
135                }
136                InvalidPropertyException e = new InvalidPropertyException(b.toString());
137                final PropertyDescriptor[] descriptors = propsNotSet.toArray(new PropertyDescriptor[propsNotSet.size()]);
138                e.setInvalidPropertyDescriptors(descriptors);
139                throw e;
140            }
141        }
142    
143        public boolean isValidUseRAManagedTransaction(List<String> errorMessages) {
144            try {
145                new Boolean(useRAManagedTransaction);
146                return true;
147            } catch (Throwable e) {
148                //
149            }
150            errorMessages.add("useRAManagedTransaction must be set to: true or false.");
151            return false;
152        }
153    
154        public boolean isValidNoLocal(List<String> errorMessages) {
155            try {
156                new Boolean(noLocal);
157                return true;
158            } catch (Throwable e) {
159                //
160            }
161            errorMessages.add("noLocal must be set to: true or false.");
162            return false;
163        }
164    
165        public boolean isValidMessageSelector(List<String> errorMessages) {
166            try {
167                if (!isEmpty(messageSelector)) {
168                    SelectorParser.parse(messageSelector);
169                }
170                return true;
171            } catch (Throwable e) {
172                errorMessages.add("messageSelector not set to valid message selector: " + e);
173                return false;
174            }
175        }
176    
177        public boolean isValidMaxSessions(List<String> errorMessages) {
178            try {
179                if (Integer.parseInt(maxSessions) > 0) {
180                    return true;
181                }
182            } catch (NumberFormatException e) {
183                //
184            }
185            errorMessages.add("maxSessions must be set to number > 0");
186            return false;
187        }
188    
189        public boolean isValidMaxMessagesPerSessions(List<String> errorMessages) {
190            try {
191                if (Integer.parseInt(maxMessagesPerSessions) > 0) {
192                    return true;
193                }
194            } catch (NumberFormatException e) {
195                //
196            }
197            errorMessages.add("maxMessagesPerSessions must be set to number > 0");
198            return false;
199        }
200    
201        public boolean isValidMaxMessagesPerBatch(List<String> errorMessages) {
202            try {
203                if (Integer.parseInt(maxMessagesPerBatch) > 0) {
204                    return true;
205                }
206            } catch (NumberFormatException e) {
207                //
208            }
209            errorMessages.add("maxMessagesPerBatch must be set to number > 0");
210            return false;
211        }
212    
213        public boolean isValidEnableBatch(List<String> errorMessages) {
214            try {
215                new Boolean(enableBatch);
216                return true;
217            } catch (Throwable e) {
218                //
219            }
220            errorMessages.add("enableBatch must be set to: true or false");
221            return false;
222        }
223    
224        public ResourceAdapter getResourceAdapter() {
225            return resourceAdapter;
226        }
227    
228        /**
229         * @see javax.resource.spi.ResourceAdapterAssociation#setResourceAdapter(javax.resource.spi.ResourceAdapter)
230         */
231        public void setResourceAdapter(ResourceAdapter resourceAdapter) throws ResourceException {
232            // spec section 5.3.3
233            if (this.resourceAdapter != null) {
234                throw new ResourceException("ResourceAdapter already set");
235            }
236            if (!(resourceAdapter instanceof MessageResourceAdapter)) {
237                throw new ResourceException("ResourceAdapter is not of type: " + MessageResourceAdapter.class.getName());
238            }
239            this.resourceAdapter = (MessageResourceAdapter)resourceAdapter;
240        }
241    
242        // ///////////////////////////////////////////////////////////////////////
243        //
244        // Java Bean getters and setters for this ActivationSpec class.
245        //
246        // ///////////////////////////////////////////////////////////////////////
247        public String getDestinationType() {
248            if (!isEmpty(destinationType)) {
249                return destinationType;
250            }
251            return null;
252        }
253    
254        /**
255         * @param destinationType The destinationType to set.
256         */
257        public void setDestinationType(String destinationType) {
258            this.destinationType = destinationType;
259        }
260    
261        public String getPassword() {
262            if (!isEmpty(password)) {
263                return password;
264            }
265            return null;
266        }
267    
268        /**
269         * 
270         */
271        public void setPassword(String password) {
272            this.password = password;
273        }
274    
275        public String getUserName() {
276            if (!isEmpty(userName)) {
277                return userName;
278            }
279            return null;
280        }
281    
282        /**
283         * 
284         */
285        public void setUserName(String userName) {
286            this.userName = userName;
287        }
288    
289        public String getMessageSelector() {
290            if (!isEmpty(messageSelector)) {
291                return messageSelector;
292            }
293            return null;
294        }
295    
296        /**
297         * @param messageSelector The messageSelector to set.
298         */
299        public void setMessageSelector(String messageSelector) {
300            this.messageSelector = messageSelector;
301        }
302    
303        public String getNoLocal() {
304            return noLocal;
305        }
306    
307        /**
308         * @param noLocal The noLocal to set.
309         */
310        public void setNoLocal(String noLocal) {
311            if (noLocal != null) {
312                this.noLocal = noLocal;
313            }
314        }
315    
316        public String getAcknowledgeMode() {
317            if (!isEmpty(acknowledgeMode)) {
318                return acknowledgeMode;
319            }
320            return null;
321        }
322    
323        /**
324         * 
325         */
326        public void setAcknowledgeMode(String acknowledgeMode) {
327            this.acknowledgeMode = acknowledgeMode;
328        }
329    
330        public String getClientId() {
331            if (!isEmpty(clientId)) {
332                return clientId;
333            }
334            return null;
335        }
336    
337        /**
338         * 
339         */
340        public void setClientId(String clientId) {
341            this.clientId = clientId;
342        }
343    
344        public String getDestination() {
345            if (!isEmpty(destination)) {
346                return destination;
347            }
348            return null;
349        }
350    
351        /**
352         * 
353         */
354        public void setDestination(String destination) {
355            this.destination = destination;
356        }
357    
358        public String getSubscriptionDurability() {
359            if (!isEmpty(subscriptionDurability)) {
360                return subscriptionDurability;
361            }
362            return null;
363        }
364    
365        /**
366         * 
367         */
368        public void setSubscriptionDurability(String subscriptionDurability) {
369            this.subscriptionDurability = subscriptionDurability;
370        }
371    
372        public String getSubscriptionName() {
373            if (!isEmpty(subscriptionName)) {
374                return subscriptionName;
375            }
376            return null;
377        }
378    
379        /**
380         * 
381         */
382        public void setSubscriptionName(String subscriptionName) {
383            this.subscriptionName = subscriptionName;
384        }
385    
386        public boolean isValidSubscriptionName(List<String> errorMessages) {
387            if (!isDurableSubscription() ? true : subscriptionName != null && subscriptionName.trim().length() > 0) {
388                return true;
389            }
390            errorMessages.add("subscriptionName must be set since durable subscription was requested.");
391            return false;
392        }
393    
394        public boolean isValidClientId(List<String> errorMessages) {
395            if (!isDurableSubscription() ? true : clientId != null && clientId.trim().length() > 0) {
396                return true;
397            }
398            errorMessages.add("clientId must be set since durable subscription was requested.");
399            return false;
400        }
401    
402        public boolean isDurableSubscription() {
403            return DURABLE_SUBSCRIPTION.equals(subscriptionDurability);
404        }
405    
406        public boolean isValidSubscriptionDurability(List<String> errorMessages) {
407            // subscriptionDurability only applies to Topics
408            if (DURABLE_SUBSCRIPTION.equals(subscriptionDurability) && getDestinationType() != null && !Topic.class.getName().equals(getDestinationType())) {
409                errorMessages.add("subscriptionDurability cannot be set to: " + DURABLE_SUBSCRIPTION + " when destinationType is set to " + Queue.class.getName()
410                                  + " as it is only valid when destinationType is set to " + Topic.class.getName() + ".");
411                return false;
412            }
413            if (NON_DURABLE_SUBSCRIPTION.equals(subscriptionDurability) || DURABLE_SUBSCRIPTION.equals(subscriptionDurability)) {
414                return true;
415            }
416            errorMessages.add("subscriptionDurability must be set to: " + NON_DURABLE_SUBSCRIPTION + " or " + DURABLE_SUBSCRIPTION + ".");
417            return false;
418        }
419    
420        public boolean isValidAcknowledgeMode(List<String> errorMessages) {
421            if (AUTO_ACKNOWLEDGE_MODE.equals(acknowledgeMode) || DUPS_OK_ACKNOWLEDGE_MODE.equals(acknowledgeMode)) {
422                return true;
423            }
424            errorMessages.add("acknowledgeMode must be set to: " + AUTO_ACKNOWLEDGE_MODE + " or " + DUPS_OK_ACKNOWLEDGE_MODE + ".");
425            return false;
426        }
427    
428        public boolean isValidDestinationType(List<String> errorMessages) {
429            if (Queue.class.getName().equals(destinationType) || Topic.class.getName().equals(destinationType)) {
430                return true;
431            }
432            errorMessages.add("destinationType must be set to: " + Queue.class.getName() + " or " + Topic.class.getName() + ".");
433            return false;
434        }
435    
436        public boolean isValidDestination(List<String> errorMessages) {
437            if (!(destination == null || destination.equals(""))) {
438                return true;
439            }
440            errorMessages.add("destination is a required field and must be set to the destination name.");
441            return false;
442        }
443    
444        public boolean isEmpty(String value) {
445            return value == null || "".equals(value.trim());
446        }
447    
448        /**
449         * 
450         */
451        @Override
452        public String toString() {
453            return "ActiveMQActivationSpec{" + "acknowledgeMode='" + acknowledgeMode + "'" + ", destinationType='" + destinationType + "'" + ", messageSelector='" + messageSelector + "'"
454                   + ", destination='" + destination + "'" + ", clientId='" + clientId + "'" + ", subscriptionName='" + subscriptionName + "'" + ", subscriptionDurability='" + subscriptionDurability
455                   + "'" + "}";
456        }
457    
458        public int getAcknowledgeModeForSession() {
459            if (AUTO_ACKNOWLEDGE_MODE.equals(acknowledgeMode)) {
460                return Session.AUTO_ACKNOWLEDGE;
461            } else if (DUPS_OK_ACKNOWLEDGE_MODE.equals(acknowledgeMode)) {
462                return Session.DUPS_OK_ACKNOWLEDGE;
463            } else {
464                return INVALID_ACKNOWLEDGE_MODE;
465            }
466        }
467    
468        /**
469         * A helper method mostly for use in Dependency Injection containers which
470         * allows you to customize the destination and destinationType properties
471         * from a single ActiveMQDestination POJO
472         */
473        public void setActiveMQDestination(ActiveMQDestination destination) {
474            setDestination(destination.getPhysicalName());
475            if (destination instanceof Queue) {
476                setDestinationType(Queue.class.getName());
477            } else {
478                setDestinationType(Topic.class.getName());
479            }
480        }
481    
482        /**
483         * 
484         */
485        public ActiveMQDestination createDestination() {
486            if (isEmpty(destinationType) || isEmpty(destination)) {
487                return null;
488            }
489    
490            ActiveMQDestination dest = null;
491            if (Queue.class.getName().equals(destinationType)) {
492                dest = new ActiveMQQueue(destination);
493            } else if (Topic.class.getName().equals(destinationType)) {
494                dest = new ActiveMQTopic(destination);
495            } else {
496                assert false : "Execution should never reach here";
497            }
498            return dest;
499        }
500    
501        public String getMaxMessagesPerSessions() {
502            return maxMessagesPerSessions;
503        }
504    
505        /**
506         * 
507         */
508        public void setMaxMessagesPerSessions(String maxMessagesPerSessions) {
509            if (maxMessagesPerSessions != null) {
510                this.maxMessagesPerSessions = maxMessagesPerSessions;
511            }
512        }
513    
514        public String getMaxSessions() {
515            return maxSessions;
516        }
517    
518        /**
519         * 
520         */
521        public void setMaxSessions(String maxSessions) {
522            if (maxSessions != null) {
523                this.maxSessions = maxSessions;
524            }
525        }
526    
527        public String getUseRAManagedTransaction() {
528            return useRAManagedTransaction;
529        }
530    
531        /**
532         * 
533         */
534        public void setUseRAManagedTransaction(String useRAManagedTransaction) {
535            if (useRAManagedTransaction != null) {
536                this.useRAManagedTransaction = useRAManagedTransaction;
537            }
538        }
539    
540        public int getMaxMessagesPerSessionsIntValue() {
541            return Integer.parseInt(maxMessagesPerSessions);
542        }
543    
544        public int getMaxSessionsIntValue() {
545            return Integer.parseInt(maxSessions);
546        }
547    
548        public boolean isUseRAManagedTransactionEnabled() {
549            return Boolean.valueOf(useRAManagedTransaction).booleanValue();
550        }
551    
552        public boolean getNoLocalBooleanValue() {
553            return Boolean.valueOf(noLocal).booleanValue();
554        }
555    
556        public String getEnableBatch() {
557            return enableBatch;
558        }
559    
560        /**
561         * 
562         */
563        public void setEnableBatch(String enableBatch) {
564            if (enableBatch != null) {
565                this.enableBatch = enableBatch;
566            }
567        }
568    
569        public boolean getEnableBatchBooleanValue() {
570            return Boolean.valueOf(enableBatch).booleanValue();
571        }
572    
573        public int getMaxMessagesPerBatchIntValue() {
574            return Integer.parseInt(maxMessagesPerBatch);
575        }
576    
577        public String getMaxMessagesPerBatch() {
578            return maxMessagesPerBatch;
579        }
580    
581        /**
582         * 
583         */
584        public void setMaxMessagesPerBatch(String maxMessagesPerBatch) {
585            if (maxMessagesPerBatch != null) {
586                this.maxMessagesPerBatch = maxMessagesPerBatch;
587            }
588        }
589    
590        public double getBackOffMultiplier() {
591            if (redeliveryPolicy == null) {
592                return 0;
593            }
594            return redeliveryPolicy.getBackOffMultiplier();
595        }
596    
597        public long getInitialRedeliveryDelay() {
598            if (redeliveryPolicy == null) {
599                return 0;
600            }
601            return redeliveryPolicy.getInitialRedeliveryDelay();
602        }
603    
604        public int getMaximumRedeliveries() {
605            if (redeliveryPolicy == null) {
606                return 0;
607            }
608            return redeliveryPolicy.getMaximumRedeliveries();
609        }
610    
611        public boolean isUseExponentialBackOff() {
612            if (redeliveryPolicy == null) {
613                return false;
614            }
615            return redeliveryPolicy.isUseExponentialBackOff();
616        }
617    
618        /**
619         * 
620         */
621        public void setBackOffMultiplier(double backOffMultiplier) {
622            lazyCreateRedeliveryPolicy().setBackOffMultiplier(backOffMultiplier);
623        }
624        
625        public long getMaximumRedeliveryDelay() {
626            if (redeliveryPolicy == null) {
627                return 0;
628            }
629            return redeliveryPolicy.getMaximumRedeliveryDelay();
630        }
631        
632        public void setMaximumRedeliveryDelay(long maximumRedeliveryDelay) {
633            lazyCreateRedeliveryPolicy().setMaximumRedeliveryDelay(maximumRedeliveryDelay);
634        }
635    
636        /**
637         * 
638         */
639        public void setInitialRedeliveryDelay(long initialRedeliveryDelay) {
640            lazyCreateRedeliveryPolicy().setInitialRedeliveryDelay(initialRedeliveryDelay);
641        }
642    
643        /**
644         * 
645         */
646        public void setMaximumRedeliveries(int maximumRedeliveries) {
647            lazyCreateRedeliveryPolicy().setMaximumRedeliveries(maximumRedeliveries);
648        }
649    
650        /**
651         * 
652         */
653        public void setUseExponentialBackOff(boolean useExponentialBackOff) {
654            lazyCreateRedeliveryPolicy().setUseExponentialBackOff(useExponentialBackOff);
655        }
656    
657        // don't use getter to avoid causing introspection errors in containers
658        public RedeliveryPolicy redeliveryPolicy() {
659            return redeliveryPolicy;
660        }
661    
662        public RedeliveryPolicy lazyCreateRedeliveryPolicy() {
663            if (redeliveryPolicy == null) {
664                redeliveryPolicy = new RedeliveryPolicy();
665            }
666            return redeliveryPolicy;
667        }
668    }