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 */
017package org.apache.activemq.ra;
018
019import java.beans.IntrospectionException;
020import java.beans.PropertyDescriptor;
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.Iterator;
024import java.util.List;
025
026import javax.jms.Queue;
027import javax.jms.Session;
028import javax.jms.Topic;
029import javax.resource.ResourceException;
030import javax.resource.spi.InvalidPropertyException;
031import javax.resource.spi.ResourceAdapter;
032
033import org.apache.activemq.RedeliveryPolicy;
034import org.apache.activemq.command.ActiveMQDestination;
035import org.apache.activemq.command.ActiveMQQueue;
036import org.apache.activemq.command.ActiveMQTopic;
037import 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 */
046public 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}