001: // THIS SOFTWARE IS PROVIDED BY SOFTARIS PTY.LTD. AND OTHER METABOSS
002: // CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
003: // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
004: // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTARIS PTY.LTD.
005: // OR OTHER METABOSS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
006: // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
007: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
008: // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
009: // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
010: // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
011: // EVEN IF SOFTARIS PTY.LTD. OR OTHER METABOSS CONTRIBUTORS ARE ADVISED OF THE
012: // POSSIBILITY OF SUCH DAMAGE.
013: //
014: // Copyright 2000-2005 © Softaris Pty.Ltd. All Rights Reserved.
015: package com.metaboss.naming.component;
016:
017: import java.lang.reflect.InvocationHandler;
018: import java.lang.reflect.Proxy;
019: import java.util.Collections;
020: import java.util.Comparator;
021: import java.util.HashMap;
022: import java.util.Hashtable;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.SortedMap;
026: import java.util.TreeMap;
027: import java.util.regex.Pattern;
028:
029: import javax.naming.CompositeName;
030: import javax.naming.ConfigurationException;
031: import javax.naming.Context;
032: import javax.naming.InvalidNameException;
033: import javax.naming.Name;
034: import javax.naming.NameNotFoundException;
035: import javax.naming.NameParser;
036: import javax.naming.NamingEnumeration;
037: import javax.naming.NamingException;
038: import javax.naming.OperationNotSupportedException;
039: import javax.naming.spi.ObjectFactory;
040:
041: import org.apache.commons.logging.Log;
042: import org.apache.commons.logging.LogFactory;
043:
044: /** A sample service provider that implements interface factories lookup */
045: class componentContext implements Context {
046: private static final Log sLogger = LogFactory
047: .getLog(componentContext.class);
048: // This map keps mapping keys to match
049: private SortedMap mMappingInstructions = new TreeMap(
050: new Comparator() {
051: // Compares two keys and places more detailed key on top. The more detailed
052: // key is the key which has as more non-wild card characters at the front and
053: // (if the same number at the front) at the back.
054: public int compare(Object pObject1, Object pObject2) {
055: String lKey1 = (String) pObject1;
056: String lKey2 = (String) pObject2;
057: int lExplicitPrefixLength1 = lKey1.indexOf("*");
058: int lExplicitPrefixLength2 = lKey2.indexOf("*");
059: if (lExplicitPrefixLength1 < 0
060: && lExplicitPrefixLength2 < 0)
061: return lKey1.compareTo(lKey2); // Both keys do not have a wild card it does not really matter which one is used first. Use natural string comparison
062: if (lExplicitPrefixLength1 < 0)
063: return -1; // Object1 should be on top of Object2 because Object1 is explicit and Object2 is not
064: if (lExplicitPrefixLength2 < 0)
065: return 1; // Object2 should be on top of Object1 because Object2 is explicit and Object1 is not
066: // Both of the objects are not explicit
067: if (lExplicitPrefixLength1 > lExplicitPrefixLength2)
068: return -1; // Object1 should be on top of Object2 because Object1 has longer explicit prefix than Object2
069: if (lExplicitPrefixLength1 < lExplicitPrefixLength2)
070: return 1; // Object2 should be on top of Object1 because Object2 has longer explicit prefix than Object1
071: // Both of the objects have same length of the explicit prefix
072: int lExplicitSuffixLength1 = lKey1.length()
073: - lKey1.lastIndexOf("*") - 1;
074: int lExplicitSuffixLength2 = lKey2.length()
075: - lKey2.lastIndexOf("*") - 1;
076: if (lExplicitSuffixLength1 == lExplicitSuffixLength2)
077: return lKey1.compareTo(lKey2); // Same length of explicit suffixes and prefixes - for now just return natural string comparison
078: return lExplicitSuffixLength1 > lExplicitSuffixLength2 ? -1
079: : 1; // The key with the longer suffix is on top
080: }
081: });
082: private Hashtable mEnvironmentProperties;
083: private static Map sCustomEnvironmentFactoryCache = Collections
084: .synchronizedMap(new HashMap());
085: private static Map sComponentFactoryCache = Collections
086: .synchronizedMap(new HashMap());
087: protected static final NameParser myParser = new componentNameParser();
088:
089: // If this key is present in the context environment this mechanism will try to instantiate
090: // the object factory with the value taken as the class name. It will then call getObjectInstance() method
091: // of this factory to obtain the java.util.Map object which contains the additional environment properties map
092: // during this call to the getObjectInstance() method, the name parameter will be set to the 'component'
093: // and the environment parameter will be set to the Context Environment Properties passed in the constructor.
094: // This allows to do a number of things:
095: // 1. In the future when we are providing some other contexts, the single custom factory
096: // will be able to provide additional environment properties for different classes of URLs.
097: // All the generic factory will need to do is to base the logic of which environment settings to return on the
098: // value of the name parameter.
099: // 2. The factory may check the context environment and fix it by returning the Map with the same keys (which will
100: // override it) and different values. If property needs to be removed - the returned property value may be set to null
101: // The main need for this feature is to allow customisation of the place where jndi properties are stored.
102: // The mappings obtained from the CustomEnvironmentFactory take precedence over the ones found in the context environment
103: private static final String sCustomEnvironmentPropertiesFactoryEnvKey = "com.metaboss.naming.componentCustomEnvironmentPropertiesFactory";
104:
105: // Name of the system property which controlls if we should cache the factories once they are found for the particular caller
106: private static final String sEnableComponentFactoryCachingSystemPropertyKey = "com.metaboss.naming.component.EnableComponentFactoryCaching";
107: private static boolean sEnableComponentFactoryCaching = true;
108:
109: static {
110: // Find out if context caching is disabled
111: sEnableComponentFactoryCaching = Boolean
112: .valueOf(
113: System
114: .getProperty(
115: sEnableComponentFactoryCachingSystemPropertyKey,
116: "true")).booleanValue();
117: if (sLogger.isDebugEnabled())
118: sLogger.debug("Component factory caching is "
119: + (sEnableComponentFactoryCaching ? "enabled"
120: : "disabled"));
121: }
122:
123: public componentContext(Hashtable pEnvironmentProperties)
124: throws NamingException {
125: mEnvironmentProperties = (pEnvironmentProperties != null) ? (Hashtable) (pEnvironmentProperties
126: .clone())
127: : null;
128: // Check if we need to load custom mappings
129: if (mEnvironmentProperties != null) {
130: String lCustomEnvironmentPropertiesFactoryClassName = (String) mEnvironmentProperties
131: .get(sCustomEnvironmentPropertiesFactoryEnvKey);
132: if (lCustomEnvironmentPropertiesFactoryClassName != null) {
133: // Obtain custom environment object factory
134: ObjectFactory lCustomEnvironmentPropertiesFactory = (ObjectFactory) sCustomEnvironmentFactoryCache
135: .get(lCustomEnvironmentPropertiesFactoryClassName);
136: if (lCustomEnvironmentPropertiesFactory == null)
137: sCustomEnvironmentFactoryCache
138: .put(
139: lCustomEnvironmentPropertiesFactoryClassName,
140: lCustomEnvironmentPropertiesFactory = loadCustomMappingsFactory(lCustomEnvironmentPropertiesFactoryClassName));
141:
142: // Obtain custom environment map from this object factory and combine if necessary
143: try {
144: Map lCustomEnvironmentMap = (Map) lCustomEnvironmentPropertiesFactory
145: .getObjectInstance(null, new CompositeName(
146: "component"), null,
147: mEnvironmentProperties);
148: if (lCustomEnvironmentMap != null) {
149: // Iterate through the map. putAll() is not good enough as we want to delete the properties where value is set to null
150: for (Iterator lIter = lCustomEnvironmentMap
151: .entrySet().iterator(); lIter.hasNext();) {
152: Map.Entry lEntry = (Map.Entry) lIter.next();
153: String lPropertyName = (String) lEntry
154: .getKey();
155: String lPropertyValue = (String) lEntry
156: .getValue();
157: if (lPropertyValue != null)
158: mEnvironmentProperties.put(
159: lPropertyName, lPropertyValue);
160: else
161: mEnvironmentProperties
162: .remove(lPropertyName);
163: }
164:
165: }
166: } catch (Exception e) {
167: sLogger
168: .error(
169: "Unable to use "
170: + lCustomEnvironmentPropertiesFactoryClassName
171: + " factory class.", e);
172: throw new ConfigurationException(
173: "Unable to use "
174: + lCustomEnvironmentPropertiesFactoryClassName
175: + " factory class. Original exception class: "
176: + e.getClass().getName());
177: }
178: }
179: }
180: rebuildMappingInstructionsTree();
181: }
182:
183: private static final String sMappingEnvKeyPrefix = "com.metaboss.naming.component.";
184: private static final int sMappingEnvKeyPrefixLength = sMappingEnvKeyPrefix
185: .length();
186:
187: // Helper. Reads the environment hashtable, gets all setting with 'com.metaboss.naming.component.' prefix,
188: // prepare them to be used for mapping and store in the mappling instructions tree
189: private void rebuildMappingInstructionsTree() {
190: mMappingInstructions.clear();
191: if (mEnvironmentProperties != null
192: && mEnvironmentProperties.size() > 0) {
193: for (Iterator lEnvKeysIterator = mEnvironmentProperties
194: .keySet().iterator(); lEnvKeysIterator.hasNext();) {
195: String lEnvKey = (String) lEnvKeysIterator.next();
196: addEnvToMappingInstructionsTreeIfNecessary(lEnvKey);
197: }
198: }
199: }
200:
201: // Helper. Adds a single environment variable to the mapping instructions set if necessary
202: private void addEnvToMappingInstructionsTreeIfNecessary(
203: String pEnvKey) {
204: if (pEnvKey.startsWith(sMappingEnvKeyPrefix)) {
205: // This is our mapping key.
206: pEnvKey = pEnvKey.substring(sMappingEnvKeyPrefixLength);
207: // Find out if it has wild cards and create the regular expression if it has
208: if (pEnvKey.indexOf("*") >= 0) {
209: // This is the wildcard mapping for which we should have a reqular expression
210: // below is a bit hard to understand, but very simple piece of code which
211: // makes regular expression out of string with wildcards. It replaces
212: // replaces '.' with '\.' ; '**' with ([\w\.]*) and '*' with '([\w]*)
213: // The complexity here is because the '*' regex itself, so it is not easy to replace
214: // '**' and '*' in one go - we need to tokenise and loop through
215:
216: // Here is an example of the old code it is simpler, but it did not work because while replacing '*' we were
217: // damaging our own prior work
218: // String lRegExp = pEnvKey.replaceAll("\\.","\\\\."); // Dots have a special meaning in regexps
219: //lRegExp = lRegExp.replaceAll("\\*\\*","([\\\\w\\\\.]*)"); // Two starts consume all characters including dots
220: //lRegExp = lRegExp.replaceAll("\\*","([\\\\w]*)"); // One star consumes all characters excluding dots
221:
222: String lTemp = pEnvKey.replaceAll("\\.", "\\\\."); // Dots have a special meaning in regexps
223: // Tokenise at '**' boundary - replace '*' in every token seprately and than add the replacement for '**'
224: StringBuffer lRegExpBuffer = new StringBuffer();
225: String[] lDoubleDotTokens = lTemp.split("\\*\\*");
226: for (int i = 0; i < lDoubleDotTokens.length; i++) {
227: if (i > 0)
228: lRegExpBuffer.append("([\\w\\.]*)"); // Two stars consume all characters including dots
229: lRegExpBuffer.append(lDoubleDotTokens[i]
230: .replaceAll("\\*", "([\\\\w]*)")); // One star consumes all characters excluding dots
231: }
232: if (sLogger.isDebugEnabled())
233: sLogger
234: .debug("JNDI Mapping instruction '"
235: + pEnvKey
236: + "' will be matched using regular expression '"
237: + lRegExpBuffer.toString() + "'.");
238: mMappingInstructions.put(pEnvKey, Pattern
239: .compile(lRegExpBuffer.toString()));
240: } else {
241: // This is straight mapping which should just use .equals()
242: if (sLogger.isDebugEnabled())
243: sLogger
244: .debug("JNDI Mapping instruction '"
245: + pEnvKey
246: + "' will be matched as using simple string comparison.");
247: mMappingInstructions.put(pEnvKey, null);
248: }
249: }
250: }
251:
252: // Helper. This method returns matching env key or null if this location does not match any instructions
253: private String getMatchingMappingInstructionKey(
254: String pLocationToMatch) {
255: for (Iterator lMappingInstructionsIterator = mMappingInstructions
256: .entrySet().iterator(); lMappingInstructionsIterator
257: .hasNext();) {
258: Map.Entry lMappingInstruction = (Map.Entry) lMappingInstructionsIterator
259: .next();
260: Pattern lPattern = (Pattern) lMappingInstruction.getValue();
261: if (lPattern != null) {
262: // There is a pattern - we wiil need to use reg ex
263: if (lPattern.matcher(pLocationToMatch).matches())
264: return sMappingEnvKeyPrefix
265: + (String) lMappingInstruction.getKey();
266: } else {
267: // No pattern specified - will use simple compare
268: String lKey = (String) lMappingInstruction.getKey();
269: if (lKey.equals(pLocationToMatch))
270: return sMappingEnvKeyPrefix
271: + (String) lMappingInstruction.getKey();
272: }
273: }
274: return null;
275: }
276:
277: // This helper interprets the mapping instructions text like factory(factory(factory(factory())) and
278: // returns the list of fuly resolved names. The zero element contains the leftmost maping instruction
279: // (the one to be used last) and the last element contains the rightmost instruction (the one which should be used first
280: private String[] interpretTheMappingInstructionText(
281: String pMappingInstructionKey,
282: String pMappingInstructionText,
283: NameDetails pInterfaceNameDetails,
284: StackTraceElement pClientStackElement)
285: throws NamingException {
286: // TODO: Finish implementation of the subset definitions
287: // ${component} - full name of the component interface
288: // ${componentName} - name of the component interface
289: // ${componentPackage[,x[,x]]} - name of the component interface package with optional sections select
290: // ${client} - full name of the client class
291: // ${clientName} - name of the client class
292: // ${clientPackage[,x[,x]]} - name of the client class package with optional sections select
293: if (pMappingInstructionText.indexOf("${component") >= 0) {
294: // Replace ${component... expressions with parts of the component name
295: pMappingInstructionText = pMappingInstructionText
296: .replaceAll("\\$\\{component\\}",
297: pInterfaceNameDetails.FullName);
298: pMappingInstructionText = pMappingInstructionText
299: .replaceAll("\\$\\{componentName\\}",
300: pInterfaceNameDetails.InterfaceName);
301: pMappingInstructionText = pMappingInstructionText
302: .replaceAll("\\$\\{componentPackage\\}",
303: pInterfaceNameDetails.PackageName);
304: }
305: if (pMappingInstructionText.indexOf("${client") >= 0
306: && pClientStackElement != null) {
307: // Replace ${client... expressions with parts of the client name
308: String lClientFullName = pClientStackElement.getClassName();
309: String lClientPackageName;
310: String lClientClassName;
311: int lClassNameDotPos = lClientFullName.lastIndexOf(".");
312: if (lClassNameDotPos >= 1) {
313: // We have got a package name
314: lClientPackageName = lClientFullName.substring(0,
315: lClassNameDotPos);
316: lClientClassName = lClientFullName
317: .substring(lClassNameDotPos + 1);
318: } else {
319: // Very unlikely, but it is still valid to have an interface at the top level
320: lClientPackageName = "";
321: lClientClassName = lClientFullName;
322: }
323: pMappingInstructionText = pMappingInstructionText
324: .replaceAll("\\$\\{client\\}", lClientFullName);
325: pMappingInstructionText = pMappingInstructionText
326: .replaceAll("\\$\\{clientName\\}", lClientClassName);
327: pMappingInstructionText = pMappingInstructionText
328: .replaceAll("\\$\\{clientPackage\\}",
329: lClientPackageName);
330: }
331: int lMappingInstructionTextLength = pMappingInstructionText
332: .length();
333: if (lMappingInstructionTextLength == 0)
334: throw new ConfigurationException(
335: "Component mapping instruction can not be empty. Mapping key : '"
336: + pMappingInstructionKey + "'");
337: // Look a the closing brackets and calculate how many of them are there
338: int lNumberOfClosingBrackets = 0;
339: for (; lNumberOfClosingBrackets < lMappingInstructionTextLength
340: && pMappingInstructionText
341: .charAt(lMappingInstructionTextLength - 1
342: - lNumberOfClosingBrackets) == ')'; lNumberOfClosingBrackets++)
343: ;
344: if (lNumberOfClosingBrackets == 0)
345: return new String[] { pMappingInstructionText }; // Simplest case - no brackets
346: // We have some brackets, so lets split the text
347: String[] lMappingInstructionsParts = pMappingInstructionText
348: .substring(
349: 0,
350: lMappingInstructionTextLength
351: - lNumberOfClosingBrackets)
352: .split("\\(");
353: if (lMappingInstructionsParts.length != (lNumberOfClosingBrackets + 1))
354: throw new ConfigurationException(
355: " Illegal component mapping instruction (number of open and closed brackets mismatch) : '"
356: + pMappingInstructionText
357: + "' Mapping key : '"
358: + pMappingInstructionKey + "'");
359: return lMappingInstructionsParts;
360: }
361:
362: // This helper creates factories as per supplied factory names in the given order
363: private ObjectFactory[] loadObjectFactories(
364: String pMappingInstructionKey, String[] pFactoryNames,
365: NameDetails pInterfaceNameDetails) throws NamingException {
366: ObjectFactory[] lObjectFactories = new ObjectFactory[pFactoryNames.length];
367: for (int i = 0; i < pFactoryNames.length; i++) {
368: // This caters for the fact when factory class has been specified explicitly
369: String lPossibleFactoryName = pFactoryNames[i];
370: // This caters for the fact when the specified name is the package and we have to build factory name by using an interface name and suffix - 'Factory'
371: String lAnotherPossibleFactoryName = lPossibleFactoryName
372: + "." + pInterfaceNameDetails.InterfaceName
373: + "Factory";
374: // WARNING. THIS IS THE OLD WAY FOR BACKWARDS COMPATIBILITY ONLY
375: // This caters for the fact when the specified name is the package and we have to build factory name by using an interface name and suffix - 'FactoryImpl'
376: String lYetAnotherPossibleFactoryName = lPossibleFactoryName
377: + "."
378: + pInterfaceNameDetails.InterfaceName
379: + "FactoryImpl";
380: // First try the name as is
381: ObjectFactory lFactory = tryLoadingComponentImplementationFactory(lPossibleFactoryName);
382: if (lFactory == null) {
383: if ((lFactory = tryLoadingComponentImplementationFactory(lAnotherPossibleFactoryName)) == null) {
384: if ((lFactory = tryLoadingComponentImplementationFactory(lYetAnotherPossibleFactoryName)) == null)
385: throw new NamingException(
386: "Unable to load the factory class. Neither '"
387: + lPossibleFactoryName
388: + "' nor '"
389: + lAnotherPossibleFactoryName
390: + "' nor '"
391: + lYetAnotherPossibleFactoryName
392: + "' class was found. Mapping key : '"
393: + pMappingInstructionKey + "'");
394: else
395: sLogger
396: .debug("Will use "
397: + lYetAnotherPossibleFactoryName
398: + " as per "
399: + pMappingInstructionKey
400: + " jndi property to create instance of "
401: + pInterfaceNameDetails.FullName
402: + " component.");
403: } else
404: sLogger.debug("Will use "
405: + lAnotherPossibleFactoryName + " as per "
406: + pMappingInstructionKey
407: + " jndi property to create instance of "
408: + pInterfaceNameDetails.FullName
409: + " component.");
410: } else
411: sLogger.debug("Will use " + lPossibleFactoryName
412: + " as per " + pMappingInstructionKey
413: + " jndi property to create instance of "
414: + pInterfaceNameDetails.FullName
415: + " component.");
416: lObjectFactories[i] = lFactory;
417: }
418: return lObjectFactories;
419: }
420:
421: // Helper. Atempts to load single component mlementation factory class
422: private ObjectFactory tryLoadingComponentImplementationFactory(
423: String pFactoryClassName) throws NamingException {
424: // Instantiate the factory
425: try {
426: // Obtain the factory class. Use following strategy to load the class
427: // 1. Try jvm's classes via Class.forName(....)
428: // 2. Try special componentImplementation loader
429:
430: // 2. See if we have loaded the jar for this implementation package already and load class from there if necessary
431: // 3. See if jar with the package name.jar exists, load it in the dynamic jar loader and try step 2 once more
432: Class lObjectFactoryClass = null;
433: try {
434: // Try context classloader to load the factory
435: ClassLoader lContextClassLoader = Thread
436: .currentThread().getContextClassLoader();
437: if (lContextClassLoader != null) {
438: try {
439: lObjectFactoryClass = lContextClassLoader
440: .loadClass(pFactoryClassName);
441: } catch (ClassNotFoundException e1) {
442: // Ignore for now - whole thing will fall through to the next try
443: }
444: }
445: if (lObjectFactoryClass == null)
446: lObjectFactoryClass = Class
447: .forName(pFactoryClassName);
448: } catch (ClassNotFoundException e) {
449: ClassLoader lClassLoader = componentImplementationLoader
450: .getComponentImplementationClassLoader(pFactoryClassName);
451: if (lClassLoader != null) {
452: try {
453: lObjectFactoryClass = lClassLoader
454: .loadClass(pFactoryClassName);
455: } catch (ClassNotFoundException e1) {
456: // Ignore for now - whole thing will fall through if no factory was found
457: }
458: }
459: }
460: if (lObjectFactoryClass == null)
461: return null;
462: return (ObjectFactory) lObjectFactoryClass.newInstance();
463: } catch (IllegalAccessException e) {
464: throw new ConfigurationException(
465: "Unable to access constructor for instance of "
466: + pFactoryClassName
467: + " class. Original exception : " + e);
468: } catch (InstantiationException e) {
469: throw new ConfigurationException("Unable to instantiate "
470: + pFactoryClassName
471: + " class. Original exception : " + e);
472: }
473: }
474:
475: // Helper. Atempts to load single component mlementation factory class
476: private ObjectFactory loadCustomMappingsFactory(
477: String pFactoryClassName) throws NamingException {
478: // Instantiate the factory
479: try {
480: // Obtain the factory class. Use following strategy to load the class
481: // 1. Try context class loader
482: // 2. System class loader
483: Class lObjectFactoryClass = null;
484: try {
485: // Try context classloader to load the factory
486: ClassLoader lContextClassLoader = Thread
487: .currentThread().getContextClassLoader();
488: if (lContextClassLoader != null) {
489: try {
490: lObjectFactoryClass = lContextClassLoader
491: .loadClass(pFactoryClassName);
492: } catch (ClassNotFoundException e1) {
493: // Ignore for now - whole thing will fall through to the next try
494: }
495: }
496: if (lObjectFactoryClass == null)
497: lObjectFactoryClass = Class
498: .forName(pFactoryClassName);
499:
500: if (lObjectFactoryClass == null)
501: throw new ConfigurationException(
502: "Unable to find the " + pFactoryClassName
503: + " factory class.");
504:
505: return (ObjectFactory) lObjectFactoryClass
506: .newInstance();
507: } catch (ClassNotFoundException e) {
508: throw new ConfigurationException("Unable to find the "
509: + pFactoryClassName
510: + " factory class. Original exception : " + e);
511: }
512: } catch (IllegalAccessException e) {
513: throw new ConfigurationException(
514: "Unable to access constructor for instance of "
515: + pFactoryClassName
516: + " class. Original exception : " + e);
517: } catch (InstantiationException e) {
518: throw new ConfigurationException("Unable to instantiate "
519: + pFactoryClassName
520: + " class. Original exception : " + e);
521: }
522: }
523:
524: // Helper. Atempts to load an interface class being looked up
525: private Class loadInterfaceClassBeingLookedup(
526: NameDetails pInterfaceNameDetails) throws NamingException {
527: // Obtain the class. Use thread's classloader first
528: Class lInterfaceClass = null;
529: try {
530: // Try context classloader to load the factory
531: ClassLoader lContextClassLoader = Thread.currentThread()
532: .getContextClassLoader();
533: if (lContextClassLoader != null) {
534: try {
535: lInterfaceClass = lContextClassLoader
536: .loadClass(pInterfaceNameDetails.FullName);
537: } catch (ClassNotFoundException e1) {
538: // Ignore for now - whole thing will fall through to the next try
539: }
540: }
541: if (lInterfaceClass == null)
542: lInterfaceClass = Class
543: .forName(pInterfaceNameDetails.FullName);
544: } catch (ClassNotFoundException e) {
545: throw new ConfigurationException(
546: "Unable to load class for the '"
547: + pInterfaceNameDetails.FullName
548: + "' interface which is being looked up. Original exception : "
549: + e);
550: }
551: if (lInterfaceClass == null)
552: throw new ConfigurationException(
553: "Unable to load class for the '"
554: + pInterfaceNameDetails.FullName
555: + "' interface which is being looked up.");
556: if (!lInterfaceClass.isInterface())
557: throw new OperationNotSupportedException(
558: "The component:/ namespace is reserved for the component interfaces. Unable to lookup the '"
559: + pInterfaceNameDetails.FullName
560: + "' class because it is not an interface.");
561: return lInterfaceClass;
562: }
563:
564: private componentContext cloneCtx() throws NamingException {
565: return new componentContext(mEnvironmentProperties);
566: }
567:
568: private class NameDetails {
569: public String FullName;
570: public String PackageName;
571: public String InterfaceName;
572: }
573:
574: /**
575: * Utility method for disassempbling the name onto its components
576: * @param name The non-null composite or compound name to process.
577: * @return The structure which contains the various parts of the name
578: */
579: protected NameDetails getMyComponents(Name name)
580: throws NamingException {
581: if (name.size() < 2
582: || name.get(0).equals("component:") == false)
583: throw new InvalidNameException(
584: name.toString()
585: + " is not valid component naming URL. Expected \"component:/<interface full name>[/<reserved for future use>]\"");
586: NameDetails lNameDetails = new NameDetails();
587: lNameDetails.FullName = name.get(1);
588: int lClassNameDotPos = name.get(1).lastIndexOf(".");
589: if (lClassNameDotPos >= 1) {
590: // We have got a package name
591: lNameDetails.PackageName = lNameDetails.FullName.substring(
592: 0, lClassNameDotPos);
593: lNameDetails.InterfaceName = lNameDetails.FullName
594: .substring(lClassNameDotPos + 1);
595: } else {
596: // Very unlikely, but it is still valid to have an interface at the top level
597: lNameDetails.PackageName = "";
598: lNameDetails.InterfaceName = lNameDetails.FullName;
599: }
600: return lNameDetails;
601: }
602:
603: public Object lookup(String name) throws NamingException {
604: return lookup(new CompositeName(name));
605: }
606:
607: public Object lookup(Name name) throws NamingException {
608: if (name.isEmpty()) {
609: // Asking to look up this context itself. Create and return
610: // a new instance with its own independent environment.
611: return (cloneCtx());
612: }
613:
614: // Extract components that belong to this namespace
615: NameDetails lNameDetails = getMyComponents(name);
616: Class lInterfaceClass = loadInterfaceClassBeingLookedup(lNameDetails);
617:
618: // Try to find object for this originator in internal hashtable
619: StackTraceElement lStackElement = findCallOriginatorFrame();
620: String lLookupOriginatorPackage = null;
621: String lBindingsKey = lNameDetails.FullName;
622: if (lStackElement != null) {
623: String lOriginatorLocation = lStackElement.getClassName()
624: + "." + lStackElement.getMethodName();
625: sLogger.debug("Received jndi lookup request for "
626: + lNameDetails.FullName + " component from within "
627: + lOriginatorLocation + "() method body.");
628: String lClassName = lStackElement.getClassName();
629: int lPackageNameEnd = lClassName.lastIndexOf(".");
630: if (lPackageNameEnd > 0) {
631: lLookupOriginatorPackage = lClassName.substring(0,
632: lPackageNameEnd);
633: }
634: lBindingsKey += "/" + lOriginatorLocation;
635: } else
636: sLogger.debug("Received jndi lookup request for "
637: + lNameDetails.FullName
638: + " component from within unrecognised location.");
639: ObjectFactory[] lObjectFactories;
640: if ((sEnableComponentFactoryCaching == false)
641: || ((lObjectFactories = (ObjectFactory[]) sComponentFactoryCache
642: .get(lBindingsKey)) == null)) {
643: // Go through the plan - more explicit elements are checked first
644: String lMappingInstructionKey = null;
645: if (lMappingInstructionKey == null && lStackElement != null)
646: lMappingInstructionKey = getMatchingMappingInstructionKey(lNameDetails.FullName
647: + "/"
648: + lStackElement.getClassName()
649: + "."
650: + lStackElement.getMethodName());
651: if (lMappingInstructionKey == null && lStackElement != null)
652: lMappingInstructionKey = getMatchingMappingInstructionKey(lNameDetails.FullName
653: + "/" + lStackElement.getClassName());
654: if (lMappingInstructionKey == null
655: && lLookupOriginatorPackage != null)
656: lMappingInstructionKey = getMatchingMappingInstructionKey(lNameDetails.FullName
657: + "/" + lLookupOriginatorPackage);
658: if (lMappingInstructionKey == null)
659: lMappingInstructionKey = getMatchingMappingInstructionKey(lNameDetails.FullName);
660: if (lMappingInstructionKey == null && lStackElement != null)
661: lMappingInstructionKey = getMatchingMappingInstructionKey(lNameDetails.PackageName
662: + "/"
663: + lStackElement.getClassName()
664: + "."
665: + lStackElement.getMethodName());
666: if (lMappingInstructionKey == null && lStackElement != null)
667: lMappingInstructionKey = getMatchingMappingInstructionKey(lNameDetails.PackageName
668: + "/" + lStackElement.getClassName());
669: if (lMappingInstructionKey == null
670: && lLookupOriginatorPackage != null)
671: lMappingInstructionKey = getMatchingMappingInstructionKey(lNameDetails.PackageName
672: + "/" + lLookupOriginatorPackage);
673: if (lMappingInstructionKey == null)
674: lMappingInstructionKey = getMatchingMappingInstructionKey(lNameDetails.PackageName);
675: if (lMappingInstructionKey == null) {
676: if (sLogger.isDebugEnabled()) {
677: // Dump the whole mapping instructions table so it will be easier to debug what is going on
678: if (mEnvironmentProperties != null
679: && mEnvironmentProperties.size() > 0) {
680: sLogger
681: .debug("-----------------------------------------------------------------");
682: sLogger
683: .debug("Following JNDI mapping instructions are taken into consideration when interfaces are mapped to implementations:");
684: for (Iterator lEnvEntriesIterator = mEnvironmentProperties
685: .entrySet().iterator(); lEnvEntriesIterator
686: .hasNext();) {
687: Map.Entry lEnvEntry = (Map.Entry) lEnvEntriesIterator
688: .next();
689: String lMappingKey = (String) lEnvEntry
690: .getKey();
691: String lMappingValue = (String) lEnvEntry
692: .getValue();
693: if (lMappingKey
694: .startsWith(sMappingEnvKeyPrefix))
695: sLogger.debug(lMappingKey + "="
696: + lMappingValue);
697: }
698: sLogger
699: .debug("-----------------------------------------------------------------");
700: } else
701: sLogger
702: .debug("There are no JNDI mapping instructions found, hence the mapping of interfaces to implementations will fail.");
703: }
704: throw new NameNotFoundException(
705: "Unable to find component implementation mapping for "
706: + name
707: + ". Use com.metaboss.naming.component.<iface name>|<iface package or full class name name>[/<user package or full class or full method name>] environment setting to define mapping.");
708: }
709: // Build the factory name
710: String lMappingInstructionText = (String) mEnvironmentProperties
711: .get(lMappingInstructionKey);
712: // The mapping instructions text may consist from one or more factory location expressions nested
713: // to any depth as follows
714: // factory expression(factory expression(factory expression()))
715: // each factory expression is essentaally a fully qualified name of the factory class with the few enchancements
716: // - the ${componentPackage} keyword gets replaced with the package name of the interface being looked up
717: // - the name is tried as is to do Class.forName()
718: // - if this is not successful the name is appended with ".<InterfaceName>Factory" and Class.forName() is attempted again
719: // Once all factories are obtained, the getObjectInstance() on all of the factories is invoked. With the
720: // deepest factory is invoked first and the first listed factory involved last. The object
721: // obtained from the previous factory is passed in to the getObjectInstance() of the next factory, this allows to
722: // build chains of objects.
723: // The object returned from the factory must implement the interface we are looking up or the
724: // generic java.lang.reflect.InvocationHandler interface
725: String[] lMappingInstructionParts = interpretTheMappingInstructionText(
726: lMappingInstructionKey, lMappingInstructionText,
727: lNameDetails, lStackElement);
728: lObjectFactories = loadObjectFactories(
729: lMappingInstructionKey, lMappingInstructionParts,
730: lNameDetails);
731: // Store the factory in the cache if necessary
732: if (sEnableComponentFactoryCaching)
733: sComponentFactoryCache.put(lBindingsKey,
734: lObjectFactories);
735: } else {
736: if (lObjectFactories.length > 1)
737: sLogger
738: .debug("Will use previously cached list of object factories to create instance of "
739: + lNameDetails.FullName + " component.");
740: else
741: sLogger
742: .debug("Will use previously cached object factory to create instance of "
743: + lNameDetails.FullName + " component.");
744: }
745:
746: // Now attempt to create the object
747: Object lImplementation = null;
748: for (int i = (lObjectFactories.length - 1); i >= 0; i--) {
749: ObjectFactory lObjectFactory = lObjectFactories[i];
750: try {
751: lImplementation = lObjectFactory.getObjectInstance(
752: lImplementation, null, null,
753: mEnvironmentProperties);
754: } catch (Exception e) {
755: sLogger.debug("Unable to instantiate "
756: + lNameDetails.FullName + " component.", e);
757: throw new NamingException(
758: "Unable to instantiate "
759: + lNameDetails.FullName
760: + " component. Object factory has had an exception : "
761: + e);
762: }
763: if (lImplementation == null)
764: throw new NamingException(
765: "Unable to instantiate "
766: + lNameDetails.FullName
767: + " component. Object factory has returned null.");
768: Class lImplementationClass = lImplementation.getClass();
769: // Check if this is an instance of the interface
770: if (!lInterfaceClass.isAssignableFrom(lImplementationClass)) {
771: // It is not an instance of the interface, it can still be the instance of the InvocationHandler
772: if (InvocationHandler.class
773: .isAssignableFrom(lImplementationClass)) {
774: // This is the generic invocation handler, we need to create the
775: // instance of the interface on the fly and return it
776: lImplementation = Proxy.newProxyInstance(
777: lInterfaceClass.getClassLoader(),
778: new Class[] { lInterfaceClass },
779: (InvocationHandler) lImplementation);
780: }
781: }
782: }
783: return lImplementation;
784: }
785:
786: public void bind(String name, Object obj) throws NamingException {
787: bind(new CompositeName(name), obj);
788: }
789:
790: public void bind(Name name, Object obj) throws NamingException {
791: throw new OperationNotSupportedException(
792: "componentContext does not support binding");
793: }
794:
795: public void rebind(String name, Object obj) throws NamingException {
796: rebind(new CompositeName(name), obj);
797: }
798:
799: public void rebind(Name name, Object obj) throws NamingException {
800: throw new OperationNotSupportedException(
801: "componentContext does not support rebinding");
802: }
803:
804: public void unbind(String name) throws NamingException {
805: unbind(new CompositeName(name));
806: }
807:
808: public void unbind(Name name) throws NamingException {
809: throw new OperationNotSupportedException(
810: "componentContext does not support unbinding");
811: }
812:
813: public void rename(String oldname, String newname)
814: throws NamingException {
815: rename(new CompositeName(oldname), new CompositeName(newname));
816: }
817:
818: public void rename(Name oldname, Name newname)
819: throws NamingException {
820: throw new OperationNotSupportedException(
821: "componentContext does not support renaming");
822: }
823:
824: public NamingEnumeration list(String name) throws NamingException {
825: return list(new CompositeName(name));
826: }
827:
828: public NamingEnumeration list(Name name) throws NamingException {
829: throw new OperationNotSupportedException(
830: "componentContext does not support list");
831: }
832:
833: public NamingEnumeration listBindings(String name)
834: throws NamingException {
835: return listBindings(name);
836: }
837:
838: public NamingEnumeration listBindings(Name name)
839: throws NamingException {
840: throw new OperationNotSupportedException(
841: "componentContext does not support listbindings");
842: }
843:
844: public void destroySubcontext(String name) throws NamingException {
845: destroySubcontext(new CompositeName(name));
846: }
847:
848: public void destroySubcontext(Name name) throws NamingException {
849: throw new OperationNotSupportedException(
850: "componentContext does not support subcontexts");
851: }
852:
853: public Context createSubcontext(String name) throws NamingException {
854: return createSubcontext(new CompositeName(name));
855: }
856:
857: public Context createSubcontext(Name name) throws NamingException {
858: throw new OperationNotSupportedException(
859: "componentContext does not support subcontexts");
860: }
861:
862: public Object lookupLink(String name) throws NamingException {
863: return lookupLink(new CompositeName(name));
864: }
865:
866: public Object lookupLink(Name name) throws NamingException {
867: // This flat context does not treat links specially
868: return lookup(name);
869: }
870:
871: public NameParser getNameParser(String name) throws NamingException {
872: return getNameParser(new CompositeName(name));
873: }
874:
875: public NameParser getNameParser(Name name) throws NamingException {
876: // Do lookup to verify name exists
877: Object obj = lookup(name);
878: if (obj instanceof Context) {
879: ((Context) obj).close();
880: }
881: return myParser;
882: }
883:
884: public String composeName(String name, String prefix)
885: throws NamingException {
886: Name result = composeName(new CompositeName(name),
887: new CompositeName(prefix));
888: return result.toString();
889: }
890:
891: public Name composeName(Name name, Name prefix)
892: throws NamingException {
893: Name result = (Name) (prefix.clone());
894: result.addAll(name);
895: return result;
896: }
897:
898: public Object addToEnvironment(String propName, Object propVal)
899: throws NamingException {
900: if (mEnvironmentProperties == null) {
901: mEnvironmentProperties = new Hashtable(5, 0.75f);
902: }
903: // This property is posibly a mapping instruction - add it to the instructions set
904: addEnvToMappingInstructionsTreeIfNecessary(propName);
905: return mEnvironmentProperties.put(propName, propVal);
906: }
907:
908: public Object removeFromEnvironment(String propName)
909: throws NamingException {
910: if (mEnvironmentProperties == null)
911: return null;
912: // This property is posibly a mapping instruction - remove it from the instructions set
913: mMappingInstructions.remove(propName);
914: return mEnvironmentProperties.remove(propName);
915: }
916:
917: public Hashtable getEnvironment() throws NamingException {
918: if (mEnvironmentProperties == null) {
919: // Must return non-null
920: return new Hashtable(3, 0.75f);
921: } else {
922: return (Hashtable) mEnvironmentProperties.clone();
923: }
924: }
925:
926: public String getNameInNamespace() throws NamingException {
927: return "";
928: }
929:
930: public void close() throws NamingException {
931: }
932:
933: // Full names of the classes which should be ignored
934: private static String[] sTransparentClassNames = new String[] { "com.metaboss.naming.component.componentContext", };
935:
936: // Prefixes of the packages which should be ignored
937: private static String[] sTransparentPackagePrefixes = new String[] { "javax.naming." };
938:
939: // Helper. Returns the stack trace element describing the
940: // piece of code which has made a call. Returns null if owner can not be determined
941: private StackTraceElement findCallOriginatorFrame() {
942: // Find out the package, which owns this call
943: Throwable lThrowable = new Throwable();
944: StackTraceElement[] lStackFrameDetails = lThrowable
945: .getStackTrace();
946: for (int lStackFrameIndex = 0; lStackFrameIndex < lStackFrameDetails.length; lStackFrameIndex++) {
947: StackTraceElement lElement = lStackFrameDetails[lStackFrameIndex];
948: // Check if this stack trace element is transparent - which meant that it is not participating in the mappings
949: String lClassName = lElement.getClassName();
950: boolean lIsTransparent = false;
951: for (int i = 0; i < sTransparentClassNames.length; i++) {
952: if ((lIsTransparent = lClassName
953: .equals(sTransparentClassNames[i])) == true)
954: break; // Found match with the transparent class
955: }
956: if (lIsTransparent)
957: continue; // Found transparent element
958: for (int i = 0; i < sTransparentPackagePrefixes.length; i++) {
959: if ((lIsTransparent = lClassName
960: .startsWith(sTransparentPackagePrefixes[i])) == true)
961: break; // Found match with the transparent package
962: }
963: if (lIsTransparent)
964: continue; // Found transparent element
965: // If the control has fallen to here it means that we have hit first not transaparent element
966: return lElement; // Found first non transparent element
967: }
968: return null;
969: }
970: }
|