View Javadoc
1   /*
2    * JBoss, Home of Professional Open Source
3    * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
4    * contributors by the @authors tag. See the copyright.txt in the
5    * distribution for a full listing of individual contributors.
6    *
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * http://www.apache.org/licenses/LICENSE-2.0
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  package org.jboss.as.quickstarts.ejb.multi.server.app;
18  
19  import java.security.Principal;
20  import java.util.Properties;
21  
22  import javax.annotation.PostConstruct;
23  import javax.annotation.PreDestroy;
24  import javax.annotation.Resource;
25  import javax.ejb.SessionContext;
26  import javax.ejb.Stateless;
27  import javax.naming.Context;
28  import javax.naming.InitialContext;
29  import javax.naming.NamingException;
30  
31  import org.jboss.logging.Logger;
32  
33  /**
34   * <p>
35   * An example how to use the new features 'scoped context' introduced with EJBCLIENT-34 in AS7.2.0 or EAP6.1.<br/>
36   * If a server without that feature is used the outbound-connection will be used
37   * and the behavior is different.
38   * </p>
39   * <p>
40   * The functionality will be the same as MainAppBean provide, the interface
41   * @{link MainApp} will be shared to use both.<br/>
42   * The sub applications, deployed in different servers are called direct by
43   * using the ejb-client scoped context properties.
44   * </p>
45   * 
46   * @author <a href="mailto:wfink@redhat.com">Wolf-Dieter Fink</a>
47   */
48  @Stateless
49  public class MainAppSContextBean implements MainApp {
50      private static final Logger LOGGER = Logger.getLogger(MainAppSContextBean.class);
51      
52      private static Integer activeInstances = 0;
53      private static Context appOneScopedEjbContext;
54      private static Context appTwoScopedEjbContextA;
55      private static Context appTwoScopedEjbContextB;
56      
57      @Resource
58      SessionContext context;
59  
60      private void createAppOneContext() {
61          final Properties ejbClientContextProps = new Properties();
62          ejbClientContextProps.put("endpoint.name", "appMain->appOne_endpoint");
63          // --- Property to enable scoped EJB client context which will be tied
64          // to the JNDI context ---
65          ejbClientContextProps.put("org.jboss.ejb.client.scoped.context", true);
66          // Property which will handle the ejb: namespace during JNDI lookup
67          ejbClientContextProps.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
68          ejbClientContextProps.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED","false");
69          // add a property which lists the connections that we are configuring.
70          // In this example, we are just configuring a single connection.
71          final String connectionName = "appOneConnection";
72          ejbClientContextProps.put("remote.connections", connectionName);
73          // add the properties to connect the app-one host 
74          ejbClientContextProps.put("remote.connection." + connectionName + ".host", "localhost");
75          ejbClientContextProps.put("remote.connection." + connectionName + ".port", "4547");
76          ejbClientContextProps.put("remote.connection." + connectionName + ".username", "quickuser1");
77          ejbClientContextProps.put("remote.connection." + connectionName + ".password", "quick123+");
78          ejbClientContextProps.put("remote.clusters", "ejb");
79          ejbClientContextProps.put("remote.cluster.ejb.username", "quickuser2");
80          ejbClientContextProps.put("remote.cluster.ejb.password", "quick+123");
81  
82          try {
83              appOneScopedEjbContext = (Context)new InitialContext(ejbClientContextProps).lookup("ejb:");
84          } catch (NamingException e) {LOGGER.error("Could not create InitialContext('appOne')", e);}
85      }
86      
87      private void createAppTwoContext() {
88          final Properties ejbClientContextProps = new Properties();
89          ejbClientContextProps.put("endpoint.name", "appMain->appTwoA_endpoint");
90          // Property to enable scoped EJB client context which will be tied to
91          // the JNDI context
92          ejbClientContextProps.put("org.jboss.ejb.client.scoped.context", true);
93          // Property which will handle the ejb: namespace during JNDI lookup
94          ejbClientContextProps.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
95  
96          final String connectionName = "appTwoConnection";
97          ejbClientContextProps.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
98          // add the properties to connect the app-one host
99          ejbClientContextProps.put("remote.connections", connectionName);
100         ejbClientContextProps.put("remote.connection." + connectionName + ".host", "localhost");
101         ejbClientContextProps.put("remote.connection." + connectionName + ".port", "4647");
102         ejbClientContextProps.put("remote.connection." + connectionName + ".username", "quickuser1");
103         ejbClientContextProps.put("remote.connection." + connectionName + ".password", "quick123+");
104         
105         try {
106             appTwoScopedEjbContextA = (Context) new InitialContext(ejbClientContextProps).lookup("ejb:");
107         } catch (NamingException e) {LOGGER.error("Could not create InitialContext('appTwoA')", e);}
108 
109         // change the necessary properties to call the other server
110         ejbClientContextProps.put("endpoint.name", "appMain->appTwoB_endpoint");
111         ejbClientContextProps.put("remote.connection." + connectionName + ".port", "5247");
112         ejbClientContextProps.put("remote.connection." + connectionName + ".username", "quickuser2");
113         ejbClientContextProps.put("remote.connection." + connectionName + ".password", "quick+123");
114 
115         try {
116             appTwoScopedEjbContextB = (Context) new InitialContext(ejbClientContextProps).lookup("ejb:");
117         } catch (NamingException e) {LOGGER.error("Could not create InitialContext('appTwoB')", e);}
118     }
119     
120     @PostConstruct
121     private void createScopedContext() {
122         synchronized (activeInstances) {
123             if(activeInstances == 0) {
124                 LOGGER.info("First instance of " + this.getClass().getSimpleName() + " initializing scoped-context");
125                 createAppOneContext();
126                 createAppTwoContext();
127             }
128             activeInstances++;
129         }
130     }
131     
132     @PreDestroy
133     private void destroyScopedContext() {
134         synchronized (activeInstances) {
135             activeInstances--;
136             if(activeInstances == 0) {
137                 LOGGER.info("Last active instance of " + this.getClass().getSimpleName() + " closing scoped-context");
138                 try {
139                     appOneScopedEjbContext.close();
140                 } catch (NamingException e) {
141                     LOGGER.error("Could not close the scoped-context of AppOne", e);
142                 }
143                 appOneScopedEjbContext = null;
144                 try {
145                     appTwoScopedEjbContextA.close();
146                 } catch (NamingException e) {
147                     LOGGER.error("Could not close the scoped-context of AppTwo (A)", e);
148                 }
149                 appTwoScopedEjbContextA = null;
150                 try {
151                     appTwoScopedEjbContextB.close();
152                 } catch (NamingException e) {
153                     LOGGER.error("Could not close the scoped-context of AppTwo (B)", e);
154                 }
155                 appTwoScopedEjbContextB = null;
156             }
157         }
158     }
159     
160     @Override
161     public String getJBossNodeName() {
162         return System.getProperty("jboss.node.name");
163     }
164 
165     @Override
166     public String invokeAll(String text) {
167         Principal caller = context.getCallerPrincipal();
168         LOGGER.info("[" + caller.getName() + "] " + text);
169         final StringBuilder result = new StringBuilder("MainAppSContext["+ caller.getName() + "]@" + getJBossNodeName());
170         // Call AppOne with the direct ejb: naming
171         try {
172             result.append("  >  [ " + invokeAppOne(text));
173         } catch (Exception e) {
174             LOGGER.error("Could not invoke AppOne", e);
175         }
176 
177         result.append(" > " + invokeAppTwo(text));
178 
179         result.append(" ]");
180 
181         return result.toString();
182     }
183 
184      /**
185      * Invoke the AppOne with the scoped client context. The initial connection
186      * will use the 'quickuser1', to differentiate the cluster connection it
187      * will be use the user 'quickuser2' to see if the clustered context is used
188      * or not.
189      * 
190      * @param text
191      *            Simple text which will be logged at server side.
192      * @return simple collection of the returned results
193      */
194     private String invokeAppOne(String text) {
195         try {
196         // this context will not use the server configured
197         // 'outbound-connection' and also did not use the
198         // jboss-ejb-client.xml.
199             final AppOne bean = (AppOne) appOneScopedEjbContext.lookup("jboss-ejb-multi-server-app-one/ejb//AppOneBean!" + AppOne.class.getName());
200 
201             StringBuffer result = new StringBuffer("{");
202             for (int i = 0; i < 8; i++) {
203                 // invoke on the bean
204                 final String appOneResult = bean.invoke(text);
205                 if (i > 0) {
206                     result.append(", ");
207                 }
208                 result.append(appOneResult);
209             }
210             result.append("}");
211 
212             LOGGER.info("AppOne (loop) returns : " + result);
213             return result.toString();
214 
215         } catch (NamingException e) {
216             LOGGER.error("Could not invoke appOne", e);
217             return null;
218         }
219     }
220 
221     /**
222     * Close the given context and write a log message which endpoint is closed.
223     * 
224     * @param iCtx
225     *            context to close, <code>null</code> will be ignored.
226     */
227     private static void saveContextClose(Context iCtx) {
228         if (iCtx != null) {
229             try {
230                 LOGGER.info("close Context "+ iCtx.getEnvironment().get("endpoint.name"));
231                 iCtx.close();
232 
233             } catch (NamingException e) {
234                 LOGGER.error("InitialContext can not be closed", e);
235             }
236         }
237     }
238     
239     /**
240     * Invoke the App2 with different ejb-client context. The server AppTwoA
241     * will be called with the user quickuser1. The server AppTwoB will be
242     * called with the user quickuser2. Both invocations are separate, there
243     * will no mix between. Also the outbound-connection is not used.
244     * 
245     * @param text
246     *            Simple text which will be logged at server side.
247     * @return simple collection of the returned results
248     */
249     private String invokeAppTwo(String text) {
250         AppTwo beanA = null;
251         AppTwo beanB = null;
252 
253         try {
254             beanA = (AppTwo) appTwoScopedEjbContextA.lookup("jboss-ejb-multi-server-app-two/ejb//AppTwoBean!" + AppTwo.class.getName());
255         } catch (NamingException e) {LOGGER.error("Could not create InitialContext('appTwoA')");}
256 
257         try {
258             beanB = (AppTwo) appTwoScopedEjbContextB.lookup("jboss-ejb-multi-server-app-two/ejb//AppTwoBean!" + AppTwo.class.getName());
259         } catch (NamingException e) {
260             LOGGER.error("Could not create InitialContext('appTwoB')");
261         }
262 
263         StringBuffer result = new StringBuffer(" appTwo loop(4 time A-B expected){");
264         for (int i = 0; i < 4; i++) {
265             // invoke on the bean
266             String appResult = beanA.invokeSecured(text);  
267             if (i > 0) {
268                 result.append(", ");
269             }
270             result.append(appResult);
271             appResult = beanB.invokeSecured(text);
272             result.append(", ");
273             result.append(appResult);
274         }
275         result.append("}");
276 
277         LOGGER.info("AppTwo (loop) returns : " + result);
278 
279         return result.toString();
280     }
281     
282     /**
283     * Same method as {@link #invokeAppTwo(String)}, but the scoped context is created and destroyed
284     * inside the method. Because of this it is not possbile to use the transaction managed by the container
285     * as the connection must be open until the container send commit/rollback to the foreign server.
286     * Therefore it is necessary to add the annotation @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) 
287     * to the {@link #invokeAll(String)} method.
288     * 
289     * For test you might rename this method.
290     * 
291     * @param text
292     *            Simple text which will be logged at server side.
293     * @return simple collection of the returned results
294     */
295     @SuppressWarnings("unused")
296     private String invokeAppTwoAlternative(String text) {
297         AppTwo beanA = null;
298         AppTwo beanB = null;
299 
300         final Properties ejbClientContextProps = new Properties();
301         ejbClientContextProps.put("endpoint.name", "appMain->appTwoA_endpoint");
302         // Property to enable scoped EJB client context which will be tied to
303         // the JNDI context
304         ejbClientContextProps.put("org.jboss.ejb.client.scoped.context", true);
305         // Property which will handle the ejb: namespace during JNDI lookup
306         ejbClientContextProps.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
307 
308         final String connectionName = "appTwoConnection";
309         ejbClientContextProps.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
310         // add the properties to connect the app-one host
311         ejbClientContextProps.put("remote.connections", connectionName);
312         ejbClientContextProps.put("remote.connection." + connectionName + ".host", "localhost");
313         ejbClientContextProps.put("remote.connection." + connectionName + ".port", "4647");
314         ejbClientContextProps.put("remote.connection." + connectionName + ".username", "quickuser1");
315         ejbClientContextProps.put("remote.connection." + connectionName + ".password", "quick123+");
316         
317         Context iCtxA = null;
318         try {
319             iCtxA = (Context) new InitialContext(ejbClientContextProps).lookup("ejb:");
320             beanA = (AppTwo) iCtxA.lookup("jboss-ejb-multi-server-app-two/ejb//AppTwoBean!" + AppTwo.class.getName());
321         } catch (NamingException e) {LOGGER.error("Could not create InitialContext('appTwoA')");}
322 
323         // change the necessary properties to call the other server
324         ejbClientContextProps.put("endpoint.name", "appMain->appTwoB_endpoint");
325         ejbClientContextProps.put("remote.connection." + connectionName + ".port", "5247");
326         ejbClientContextProps.put("remote.connection." + connectionName + ".username", "quickuser2");
327         ejbClientContextProps.put("remote.connection." + connectionName + ".password", "quick+123");
328         Context iCtxB = null;
329         try {
330             iCtxB = (Context) new InitialContext(ejbClientContextProps).lookup("ejb:");
331             beanB = (AppTwo) iCtxB.lookup("jboss-ejb-multi-server-app-two/ejb//AppTwoBean!" + AppTwo.class.getName());
332         } catch (NamingException e) {
333             LOGGER.error("Could not create InitialContext('appTwoB')");
334         }
335 
336         StringBuffer result = new StringBuffer(" appTwo loop(4 time A-B expected){");
337         for (int i = 0; i < 4; i++) {
338             // invoke on the bean
339             String appResult = beanA.invokeSecured(text);  
340             if (i > 0) {
341                 result.append(", ");
342             }
343             result.append(appResult);
344             appResult = beanB.invokeSecured(text);
345             result.append(", ");
346             result.append(appResult);
347         }
348         result.append("}");
349 
350         LOGGER.info("AppTwo (loop) returns : " + result);
351 
352         // should be closed and null the reference to close the connection
353         saveContextClose(iCtxA);
354         iCtxA = null;
355         saveContextClose(iCtxB);
356         iCtxB = null;
357 
358         return result.toString();
359     }
360 }