View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.log4j.net;
19  
20  import java.io.FileInputStream;
21  import java.util.Properties;
22  
23  import javax.jms.Message;
24  import javax.jms.MessageListener;
25  import javax.jms.TopicConnection;
26  import javax.jms.Topic;
27  import javax.jms.TopicConnectionFactory;
28  import javax.jms.TopicSubscriber;
29  import javax.jms.Session;
30  import javax.jms.TopicSession;
31  import javax.jms.ObjectMessage;
32  
33  import javax.naming.InitialContext;
34  import javax.naming.Context;
35  import javax.naming.NameNotFoundException;
36  import javax.naming.NamingException;
37  
38  import org.apache.log4j.spi.LoggingEvent;
39  import org.apache.log4j.plugins.Plugin;
40  import org.apache.log4j.plugins.Receiver;
41  
42  /**
43    JMSReceiver receives a remote logging event on a configured
44    JSM topic and "posts" it to a LoggerRepository as if the event was 
45    generated locally. This class is designed to receive events from 
46    the JMSAppender class (or classes that send compatible events).
47    
48    <p>Once the event has been "posted", it will be handled by the 
49    appenders currently configured in the LoggerRespository.
50    
51    <p>This implementation borrows heavily from the JMSSink
52    implementation.
53    
54    @author Mark Womack
55    @author Paul Smith
56    @author Stephen Pain
57  */
58  public class JMSReceiver extends Receiver implements MessageListener {
59  
60    private boolean active = false;
61  
62    protected String topicFactoryName;
63    protected String topicName;
64    protected String userId;
65    protected String password;
66    protected TopicConnection topicConnection;
67    protected String jndiPath;
68    
69    private String remoteInfo;
70    private String providerUrl;
71  
72    public JMSReceiver() { }
73  
74    public JMSReceiver(String _topicFactoryName, String _topicName,
75            String _userId, String _password, String _jndiPath) {      
76        topicFactoryName = _topicFactoryName;
77        topicName = _topicName;
78        userId = _userId;
79        password = _password;
80        jndiPath = _jndiPath;
81    }
82  
83    /**
84           * Sets the path to a properties file containing
85           * the initial context and jndi provider url
86           */
87      public void setJndiPath(String _jndiPath) {
88            jndiPath = _jndiPath;
89      }
90    
91       /**
92           * Gets the path to a properties file containing
93           * the initial context and jndi provider url
94           */
95       public String getJndiPath() {
96            return jndiPath;
97       }
98    
99    /**
100     Sets the JMS topic factory name to use when creating the 
101     JMS connection. */
102   public void setTopicFactoryName(String _topicFactoryName) {
103     topicFactoryName = _topicFactoryName;
104   }
105   
106   /**
107     Gets the curernt JMS topic factory name property. */
108   public String getTopicFactoryName() {
109     return topicFactoryName;
110   }
111   
112   /**
113    * Sets the JMS topic name to use when creating the
114    * JMS connection.
115    */
116   public void setTopicName(String _topicName) {
117     topicName = _topicName;
118   }
119   
120   /**
121    * Gets the curernt JMS topic name property.
122    */
123   public String getTopicName() {
124     return topicName;
125   }
126 
127   /**
128     Sets the user id to use when creating the 
129     JMS connection. */
130   public void setUserId(String _userId) {
131     userId = _userId;
132   }
133   
134   /**
135    * Gets the current user id property.
136    */
137   public String getUserId() {
138     return userId;
139   }
140 
141   /**
142    * Sets the password to use when creating the
143    * JMS connection.
144    */
145   public void setPassword(String _password) {
146     password = _password;
147   }
148   
149   /**
150    * Gets the curernt password property.
151    */
152   public String getPassword() {
153     return password;
154   }
155  
156   /**
157    * Returns true if the receiver is the same class and they are
158    * configured for the same properties, and super class also considers
159    * them to be equivalent. This is used by PluginRegistry when determining
160    * if the a similarly configured receiver is being started.
161    * 
162    * @param testPlugin The plugin to test equivalency against.
163    * @return boolean True if the testPlugin is equivalent to this plugin.
164    */
165   public boolean isEquivalent(Plugin testPlugin) {
166     // only do full check if an instance of this class
167     if (testPlugin instanceof JMSReceiver) {
168  
169       JMSReceiver receiver = (JMSReceiver)testPlugin;
170       
171       // check for same topic name and super class equivalency
172       return (
173             topicFactoryName.equals(receiver.getTopicFactoryName()) && 
174             (jndiPath == null || jndiPath.equals(receiver.getJndiPath())) && 
175             super.isEquivalent(testPlugin)
176             );
177     }
178     
179     return false;
180   }
181   
182   /**
183     Returns true if this receiver is active. */
184   public synchronized boolean isActive() {
185     return active;
186   }
187   
188   /**
189     Sets the flag to indicate if receiver is active or not. */
190   protected synchronized void setActive(boolean _active) {
191     active = _active;
192   }
193   
194   /**
195     Starts the JMSReceiver with the current options. */
196   public void activateOptions() {
197     if (!isActive()) {
198       try {
199         remoteInfo = topicFactoryName + ":" + topicName;
200 
201         Context ctx = null;
202         if (jndiPath == null || jndiPath.equals("")) {
203                 ctx = new InitialContext();
204         } else {
205                 FileInputStream is = new FileInputStream(jndiPath);
206                 Properties p = new Properties();
207                 p.load(is);
208                 is.close();
209                 ctx = new InitialContext(p);
210         }
211 
212         // give some more flexibility about the choice of a tab name
213         providerUrl = (String)ctx.getEnvironment().get(Context.PROVIDER_URL);
214         TopicConnectionFactory topicConnectionFactory;
215         topicConnectionFactory = 
216           (TopicConnectionFactory) lookup(ctx, topicFactoryName);
217         
218         if (userId != null && password != null) {
219           topicConnection =
220     	       topicConnectionFactory.createTopicConnection(userId, password);
221         } else {
222           topicConnection =
223     	       topicConnectionFactory.createTopicConnection();
224         }
225   	       
226         TopicSession topicSession =
227           topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
228   
229         Topic topic = (Topic)ctx.lookup(topicName);
230   
231         TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);
232       
233         topicSubscriber.setMessageListener(this);
234  
235         topicConnection.start();
236  
237         setActive(true);
238       } catch(Exception e) {
239         setActive(false);
240         if (topicConnection != null) {
241           try {
242             topicConnection.close();
243           } catch (Exception e2) {
244             // do nothing
245           }
246           topicConnection = null;
247         }
248         getLogger().error("Could not start JMSReceiver.", e);
249       }
250     }
251   }
252   
253   /**
254     Called when the receiver should be stopped. */
255   public synchronized void shutdown() {
256     if (isActive()) {
257       // mark this as no longer running
258       setActive(false);
259     
260       if (topicConnection != null) {
261         try {
262           topicConnection.close();
263         } catch (Exception e) {
264           // do nothing
265         }
266         topicConnection = null;
267       }
268     }
269   }
270 
271   public void onMessage(Message message) {
272     try {
273       if(message instanceof  ObjectMessage) {
274         // get the logging event and post it to the repository
275       	ObjectMessage objectMessage = (ObjectMessage) message;
276       	LoggingEvent event = (LoggingEvent) objectMessage.getObject();
277       	
278       	// store the known remote info in an event property
279       	event.setProperty("log4j.remoteSourceInfo", remoteInfo);
280         event.setProperty("log4j.jmsProviderUrl", providerUrl);
281         
282       	doPost(event);
283       } else {
284       	getLogger().warn("Received message is of type "+message.getJMSType()
285 		    +", was expecting ObjectMessage.");
286       }      
287     } catch(Exception e) {
288       getLogger().error("Exception thrown while processing incoming message.", e);
289     }
290   }
291 
292   protected Object lookup(Context ctx, String name) throws NamingException {
293     try {
294       return ctx.lookup(name);
295     } catch(NameNotFoundException e) {
296       getLogger().error("Could not find name ["+name+"].");
297       throw e;
298     }
299   }
300 
301 }