/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tuscany.sca.binding.ejb.util;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.omg.CORBA.ORB;
import org.omg.CosNaming.NamingContextExt;
/**
* CosNaming utility
*
* @version $Rev$ $Date$
*/
class EJBLocator {
/*
* Root Context Initial Reference Key ------------
* ----------------------------------- Server Root NameServiceServerRoot
* Cell Persistent Root NameServiceCellPersistentRoot Cell Root
* NameServiceCellRoot, NameService Node Root NameServiceNodeRoot
*/
public static final String SERVER_ROOT = "NameServiceServerRoot";
public static final String CELL_PERSISTENT_ROOT = "NameServiceCellPersistentRoot";
public static final String CELL_ROOT = "NameServiceCellRoot";
public static final String NODE_ROOT = "NameServiceNodeRoot";
public static final String DEFAULT_ROOT = "NameService"; // Same as
// CELL_ROOT
public static final String DEFAULT_HOST = "127.0.0.1"; // Default host name
// or IP address for
// WebSphere
public static final int DEFAULT_NAMING_PORT = 2809; // Default port
public static final String NAMING_SERVICE = "NameService"; // The name of
// the naming
// service
private static final Set<String> ROOTS =
new HashSet<String>(Arrays.asList(new String[] {SERVER_ROOT, CELL_PERSISTENT_ROOT, CELL_ROOT, DEFAULT_ROOT,
NODE_ROOT}));
// private static final String CHARS_TO_ESCAPE = "\\/.";
private static final String RFC2396 =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789;/:?@&=+$,-_.!~*'()";
private static final String HEX = "0123456789ABCDEF";
private String hostName = DEFAULT_HOST;
private int port = DEFAULT_NAMING_PORT;
private String root = SERVER_ROOT;
private ORB orb = null;
private ObjectLocator locator = null;
private boolean managed = true;
EJBLocator(boolean managed) {
this.managed = managed;
if (!managed) {
String url = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty(Context.PROVIDER_URL);
}
});
processCorbaURL(url);
}
}
EJBLocator(String hostName, int port) {
this.hostName = (hostName == null) ? DEFAULT_HOST : hostName;
this.port = port > 0 ? port : DEFAULT_NAMING_PORT;
this.root = SERVER_ROOT;
}
EJBLocator(String hostName, int port, String root) {
this(hostName, port);
if (ROOTS.contains(root)) {
this.root = root;
} else {
throw new IllegalArgumentException(root + " is not a legal root");
}
}
EJBLocator(String corbaName, boolean managed) {
this.managed = managed;
if (corbaName.startsWith("corbaname:iiop:")) {
processCorbaURL(corbaName);
} else {
throw new IllegalArgumentException(corbaName + " is not a legal corbaname");
}
}
private void processCorbaURL(String url) {
if (url != null && (url.startsWith("corbaname:iiop:") || url.startsWith("corbaloc:iiop:"))) {
/**
* corbaname:iiop:<hostName>:<port>/<root>#name corbaloc:iiop:<hostname>:<port>/<root>
* For example,
* "corbaname:iiop:localhost:2809/NameServiceServerRoot#ejb/MyEJBHome";
* or "corbaloc:iiop:myhost:2809/NameServiceServerRoot"
*/
String[] parts = url.split("(:|/|#)");
if (parts.length > 2 && parts[2].length() > 0) {
hostName = parts[2]; // The host name
int index = hostName.lastIndexOf('@'); // version@hostname
if (index != -1) {
hostName = hostName.substring(index + 1);
}
}
if (parts.length > 3 && parts[3].length() > 0) {
port = Integer.parseInt(parts[3]); // The port number
}
if (parts.length > 4 && parts[4].length() > 0) {
root = parts[4]; // The root of naming
}
}
}
/**
* The corbaloc and corbaname formats enable you to provide a URL to access
* CORBA objects. Use the corbaloc format for resolving to a particular
* CORBAservice without going through a naming service. Use the corbaname
* format to resolve a stringified name from a specific naming context.
*/
/**
* corbaname Syntax The full corbaname BNF is: <corbaname> =
* "corbaname:"<corbaloc_obj>["#"<string_name>]
* <corbaloc_obj> = <obj_addr_list> ["/"<key_string>]
* <obj_addr_list> = as defined in a corbaloc URL <key_string> =
* as defined in a corbaloc URL <string_name>= stringified Name
* empty_string Where:
* <ul>
* <li>corbaloc_obj: portion of a corbaname URL that identifies the naming
* context. The syntax is identical to its use in a corbaloc URL.
* <li>obj_addr_list: as defined in a corbaloc URL
* <li>key_string: as defined in a corbaloc URL.
* <li>string_name: a stringified Name with URL escapes as defined below.
* </ul>
*
* @param hostName The host name or IP address of the naming server
* @param port The port number of the naming service
* @param root The root of the namespace
* @param name The JNDI name
*/
private static String getCorbaname(String hostName, int port, String root, String name) {
if (name == null) {
return "corbaname:iiop:" + hostName + ":" + port + "/" + root;
} else {
return "corbaname:iiop:" + hostName + ":" + port + "/" + root + "#" + toCorbaname(name);
}
}
String getCorbaname(String name) {
return getCorbaname(hostName, port, root, name);
}
/**
* Connect to the ORB.
*/
// FIXME. May need to change the IBM classes if this binding is contributed
// to Tuscany
public ORB connect() {
if (orb == null) {
Properties props = new Properties();
/*
* This code is for IBM JVM props.put("org.omg.CORBA.ORBClass",
* "com.ibm.CORBA.iiop.ORB");
* props.put("com.ibm.CORBA.ORBInitRef.NameService",
* getCorbaloc(NAMING_SERVICE));
* props.put("com.ibm.CORBA.ORBInitRef.NameServiceServerRoot",
* getCorbaloc("NameServiceServerRoot"));
*/
orb = ORB.init((String[])null, props);
}
return orb;
}
/**
* Replace substrings
*
* @param source The source string.
* @param match The string to search for within the source string.
* @param replace The replacement for any matching components.
* @return
*/
private static String replace(String source, String match, String replace) {
int index = source.indexOf(match, 0);
if (index >= 0) {
// We have at least one match, so got to do the
// work...
StringBuffer result = new StringBuffer(source.length() + 16);
int matchLength = match.length();
int startIndex = 0;
while (index >= 0) {
result.append(source.substring(startIndex, index));
result.append(replace);
startIndex = index + matchLength;
index = source.indexOf(match, startIndex);
}
// Grab the last piece, if any...
if (startIndex < source.length()) {
result.append(source.substring(startIndex));
}
return result.toString();
} else {
// No matches, just return the source...
return source;
}
}
/**
* Resolved the JNDI name from the initial CosNaming context
*
* @param jndiName
* @return resolved CORBA object
* @throws NamingException
*/
private static org.omg.CORBA.Object resovleString(NamingContextExt initCtx, String jndiName) throws NamingException {
try {
String name = stringify(jndiName);
return initCtx.resolve_str(name);
} catch (Exception e) {
NamingException ne = new NamingException(e.getMessage());
ne.setRootCause(e);
throw ne;
}
}
/**
* Look up a CORBA object by its JNDI name
*
* @param jndiName
* @return
* @throws NamingException
*/
org.omg.CORBA.Object stringToObject(String jndiName) throws NamingException {
/*
* Using an existing ORB and invoking string_to_object with a CORBA
* object URL with multiple name server addresses to get an initial
* context CORBA object URLs can contain more than one bootstrap server
* address. Use this feature when attempting to obtain an initial
* context from a server cluster. You can specify the bootstrap server
* addresses for all servers in the cluster in the URL. The operation
* will succeed if at least one of the servers is running, eliminating a
* single point of failure. There is no guarantee of any particular
* order in which the address list will be processed. For example, the
* second bootstrap server address may be used to obtain the initial
* context even though the first bootstrap server in the list is
* available. An example of a corbaloc URL with multiple addresses
* follows. obj =
* orb.string_to_object("corbaloc::myhost1:9810,:myhost1:9811,:myhost2:9810/NameService");
*/
String corbaName = null;
if (jndiName.startsWith("corbaloc:") || jndiName.startsWith("corbaname:")) {
// Keep the qualified URL
corbaName = jndiName;
} else {
// Create a corbaname URL
corbaName = getCorbaname(jndiName);
}
connect();
org.omg.CORBA.Object obj = orb.string_to_object(corbaName);
return obj;
}
private boolean isJndiConfigured() {
if (managed)
return true;
Boolean provided = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() {
String initCtxFactory = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
if (initCtxFactory == null) {
URL file = Thread.currentThread().getContextClassLoader().getResource("jndi.properties");
if (file != null) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
} else {
return Boolean.TRUE;
}
}
});
return provided.booleanValue();
}
/**
* The character escape rules for the stringified name portion of an
* corbaname are: US-ASCII alphanumeric characters are not escaped.
* Characters outside this range are escaped, except for the following: ; / : ? @ & = + $ , - _ . ! ~ * ' ( )
* corbaname Escape Mechanism The percent '%' character is used as an
* escape. If a character that requires escaping is present in a name
* component it is encoded as two hexadecimal digits following a "%"
* character to represent the octet. (The first hexadecimal character
* represent the highorder nibble of the octet, the second hexadecimal
* character represents the low-order nibble.) If a '%' is not followed by
* two hex digits, the stringified name is syntactically invalid.
* @param s
* @return RFC2396-encoded stringified name
*/
static String encode2396(String s) {
if (s == null) {
return null;
}
StringBuffer encoded = new StringBuffer(s);
for (int i = 0; i < encoded.length(); i++) {
char c = encoded.charAt(i);
if (RFC2396.indexOf(c) == -1) {
encoded.setCharAt(i, '%');
char[] ac = Integer.toHexString(c).toCharArray();
if (ac.length == 2) {
encoded.insert(i + 1, ac);
} else if (ac.length == 1) {
encoded.insert(i + 1, '0');
encoded.insert(i + 2, ac[0]);
} else {
throw new IllegalArgumentException("Invalid character '" + c + "' in \"" + s + "\"");
}
i += 2; // NOPMD
}
}
return encoded.toString();
}
/**
* Decode an RFC2396-encoded string
*
* @param s
* @return Plain string
*/
static String decode2396(String s) {
if (s == null) {
return null;
}
StringBuffer decoded = new StringBuffer(s);
for (int i = 0; i < decoded.length(); i++) {
char c = decoded.charAt(i);
if (c == '%') {
if (i + 2 >= decoded.length()) {
throw new IllegalArgumentException("Incomplete key_string escape sequence");
}
int j;
j = HEX.indexOf(decoded.charAt(i + 1)) * 16 + HEX.indexOf(decoded.charAt(i + 2));
decoded.setCharAt(i, (char)j);
decoded.delete(i + 1, i + 3);
} else if (RFC2396.indexOf(c) == -1) {
throw new IllegalArgumentException("Invalid key_string character '" + c + "'");
}
}
return decoded.toString();
}
/**
* The backslash '\' character escapes the reserved meaning of '/', '.', and
* '\' in a stringified name.
*
* @param jndiName
* @return Escaped stringified name for CosNaming
*/
private static String stringify(String jndiName) {
// Escape . into \. since it's an INS naming delimiter
return replace(encode2396(jndiName), ".", "\\.");
}
/**
* Escape the "." into "%5C%2E"
*
* @param jndiName
* @return corbaname treating "." as a literal
*/
private static String toCorbaname(String jndiName) {
// Escape . into %5C%2E (\.) since it's an INS naming delimiter
// For example, sca.sample.StockQuote --->
// sca%5C%2Esample%5C%2EStockQuote/StockQuote
return replace(encode2396(jndiName), ".", "%5C%2E");
}
private ObjectLocator getObjectLocator() throws NamingException {
if (locator != null) {
return locator;
}
/*
* For managed env, JNDI is assumed to be configured by default For
* unmanaged environment, JNDI could have configured through
* jndi.properties file
*/
if (isJndiConfigured()) {
locator = new JndiLocator();
} else { // this is definitely JSE env without JNDI configured. Use
// CORBA.
locator = new CosNamingLocator();
}
return locator;
}
public Object locate(String jndiName) throws NamingException {
Object result = getObjectLocator().locate(jndiName);
return result;
}
private static interface ObjectLocator {
Object locate(String name) throws NamingException;
}
private final class JndiLocator implements ObjectLocator {
private Context context;
private JndiLocator() throws NamingException {
/*
final Properties props = AccessController.doPrivileged(new PrivilegedAction<Properties>() {
public Properties run() {
return System.getProperties();
}
});
Properties properties = new Properties();
for (Map.Entry e : props.entrySet()) {
String name = (String)e.getKey();
if (name.startsWith("java.naming.")) {
properties.setProperty(name, (String)e.getValue());
}
}
// System.out.println(properties);
this.context = new InitialContext(properties);
*/
this.context = new InitialContext();
}
public Object locate(String name) throws NamingException {
return context.lookup(name);
}
}
private final class CosNamingLocator implements ObjectLocator {
private NamingContextExt context;
private CosNamingLocator() {
}
public Object locate(String name) throws NamingException {
if (context != null) {
return resovleString(context, name);
} else {
return stringToObject(name);
}
}
}
public void setHostEnv(boolean managed) {
this.managed = managed;
}
}