001: /*
002: * The contents of this file are subject to the Sapient Public License
003: * Version 1.0 (the "License"); you may not use this file except in compliance
004: * with the License. You may obtain a copy of the License at
005: * http://carbon.sf.net/License.html.
006: *
007: * Software distributed under the License is distributed on an "AS IS" basis,
008: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
009: * the specific language governing rights and limitations under the License.
010: *
011: * The Original Code is The Carbon Component Framework.
012: *
013: * The Initial Developer of the Original Code is Sapient Corporation
014: *
015: * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
016: */
017:
018: package org.sape.carbon.services.config.jndi;
019:
020: import java.util.HashSet;
021: import java.util.Set;
022:
023: import javax.naming.Name;
024: import javax.naming.NamingEnumeration;
025: import javax.naming.NamingException;
026: import javax.naming.directory.Attributes;
027: import javax.naming.directory.DirContext;
028: import javax.naming.directory.SearchControls;
029: import javax.naming.directory.SearchResult;
030: import javax.naming.event.EventContext;
031: import javax.naming.event.NamespaceChangeListener;
032: import javax.naming.event.NamingEvent;
033: import javax.naming.event.NamingExceptionEvent;
034: import javax.naming.event.ObjectChangeListener;
035:
036: import org.sape.carbon.core.config.InvalidConfigurationException;
037: import org.sape.carbon.core.config.format.ConfigurationFormatException;
038: import org.sape.carbon.core.config.format.DefaultConfigurationFormatService;
039: import org.sape.carbon.core.config.node.AbstractFolder;
040: import org.sape.carbon.core.config.node.ConfigurationDocument;
041: import org.sape.carbon.core.config.node.Node;
042: import org.sape.carbon.core.config.node.NodeCreationException;
043: import org.sape.carbon.core.config.node.NodeFactory;
044: import org.sape.carbon.core.config.node.NodeIOException;
045: import org.sape.carbon.core.config.node.NodeNotFoundException;
046: import org.sape.carbon.core.config.node.NodeRemovalException;
047: import org.sape.carbon.core.config.node.link.LinkNodeConfiguration;
048: import org.sape.carbon.core.config.node.link.LinkNodeFactory;
049: import org.sape.carbon.core.exception.ExceptionUtility;
050: import org.sape.carbon.core.exception.InvalidParameterException;
051:
052: import org.apache.commons.logging.Log;
053: import org.apache.commons.logging.LogFactory;
054:
055: /**
056: * A node that represents a JNDI context that contains other folders or documents.
057: * This implementation implements NamespaceChangeListener and
058: * ObjectChangeListener from the javax.naming.event package in order to listen
059: * for changes in the backing datastore.
060: *
061: * Copyright 2003 Sapient
062: * @since carbon 2.0
063: * @author Douglas Voet, March 2003
064: * @version $Revision: 1.12 $($Author: dvoet $ / $Date: 2003/07/29 18:52:33 $)
065: */
066: public class JNDIFolder extends AbstractFolder implements
067: NamespaceChangeListener, ObjectChangeListener {
068:
069: private Log log = LogFactory.getLog(this .getClass());
070:
071: /** Filter string used in getAllChildNames to get folders and documents names */
072: private static final String SUBCONTEXT_FILTER = "(|({0}={1})({0}={2}))";
073:
074: private DirContext initialContext;
075: private Name nodeContextName;
076: private JNDILinkNodeConfiguration config;
077:
078: /**
079: * Constructs the JNDIFolder and registers itself as a
080: * JNDI naming listener
081: *
082: * @param parent the parent node of this node
083: * @param name the name of this node
084: * @param subFolderFactory factory used to create sub folders
085: * @param configurationDocumentFactory factory used to create sub documents
086: * @param linkNodeFactory factory used to create sub links
087: * @param initialContext the jndi initial context
088: * @param nodeContextName the name of this node's context
089: * @param config configuration used to get the names of the attributes
090: * that hold node name, node and document type, and document content as well
091: * as valid attribute values for node and document type.
092: */
093: protected JNDIFolder(Node parent, String name,
094: NodeFactory subFolderFactory,
095: NodeFactory configurationDocumentFactory,
096: NodeFactory linkNodeFactory, DirContext initialContext,
097: Name nodeContextName, JNDILinkNodeConfiguration config) {
098:
099: super (parent, name, subFolderFactory,
100: configurationDocumentFactory, linkNodeFactory);
101:
102: // validate parameters
103: if (initialContext == null) {
104: throw new InvalidParameterException(this .getClass(),
105: "initialContext cannot be null in node ["
106: + getAbsoluteName() + "]");
107: }
108: if (nodeContextName == null) {
109: throw new InvalidParameterException(this .getClass(),
110: "nodeContextName cannot be null in node ["
111: + getAbsoluteName() + "]");
112: }
113: if (config == null) {
114: throw new InvalidParameterException(this .getClass(),
115: "config cannot be null in node ["
116: + getAbsoluteName() + "]");
117: }
118:
119: this .initialContext = initialContext;
120: this .nodeContextName = nodeContextName;
121: this .config = config;
122:
123: // register for JNDI events for this context
124: registerNamingListener();
125: }
126:
127: /**
128: * @inherit
129: */
130: protected Set getAllChildNames() {
131: Set childNameSet = new HashSet();
132:
133: try {
134: // get the name attribute of all immediate sub contexts of
135: // either document or folder type
136:
137: // the values of subcontextFilterArgs are plugged into
138: // SUBCONTEXT_FILTER
139: Object[] subcontextFilterArgs = new Object[] {
140: this .config.getNodeTypeAttributeName(),
141: this .config.getFolderNodeTypeAttributeValue(),
142: this .config.getDocumentNodeTypeAttributeValue() };
143:
144: // specify only the name attribute to return
145: SearchControls subcontextSearchControls = new SearchControls();
146: subcontextSearchControls
147: .setReturningAttributes(new String[] { this .config
148: .getNodeNameAttributeName() });
149:
150: NamingEnumeration childrenEnum = this .initialContext
151: .search(nodeContextName,
152: JNDIFolder.SUBCONTEXT_FILTER,
153: subcontextFilterArgs,
154: subcontextSearchControls);
155:
156: // add the name of each sub context to childNameSet
157: while (childrenEnum.hasMoreElements()) {
158: SearchResult result = (SearchResult) childrenEnum
159: .nextElement();
160: childNameSet.add(result.getAttributes().get(
161: this .config.getNodeNameAttributeName()).get());
162: }
163:
164: } catch (NamingException ne) {
165: if (log.isInfoEnabled()) {
166: log
167: .info("Could not get names of children, reason: "
168: + ExceptionUtility
169: .printStackTracesToString(ne));
170: }
171: }
172:
173: return childNameSet;
174: }
175:
176: /**
177: * @inherit
178: */
179: protected Node loadChild(String nodeName)
180: throws NodeNotFoundException {
181:
182: // these constants are defined in order to make the code below readable
183: final String NAME = this .config.getNodeNameAttributeName();
184: final String EQUALS = this .config
185: .getAttributeNameValueSeparator();
186: final String NODE_TYPE = this .config.getNodeTypeAttributeName();
187: final String FOLDER = this .config
188: .getFolderNodeTypeAttributeValue();
189: final String DOCUMENT = this .config
190: .getDocumentNodeTypeAttributeValue();
191: final String DOC_TYPE = this .config
192: .getDocumentTypeAttributeName();
193: final String LINK = this .config
194: .getLinkDocumentTypeAttributeValue();
195: final String CONTENT = this .config
196: .getDocumentDocumentTypeAttributeValue();
197:
198: try {
199: // create the child context name
200: Name childContextName = (Name) this .nodeContextName.clone();
201: childContextName.add(NAME + EQUALS + nodeName);
202:
203: String[] docuemntAttributeIDs = new String[] {
204: this .config.getNodeTypeAttributeName(),
205: this .config.getDocumentTypeAttributeName() };
206:
207: // get the child's attributes
208: Attributes attributes = this .initialContext.getAttributes(
209: childContextName, docuemntAttributeIDs);
210:
211: // figure out what kind of node it is and load it
212: if (attributes.get(NODE_TYPE) != null) {
213: if (attributes.get(NODE_TYPE).contains(FOLDER)) {
214: return loadSubFolder(nodeName);
215: }
216:
217: if (attributes.get(DOC_TYPE) != null
218: && attributes.get(NODE_TYPE).contains(DOCUMENT)) {
219:
220: if (attributes.get(DOC_TYPE).contains(LINK)) {
221: return loadChildLinkNode(nodeName);
222: }
223:
224: if (attributes.get(DOC_TYPE).contains(CONTENT)) {
225: return loadConfigurationDocument(nodeName);
226: }
227: }
228: }
229:
230: // requried attributes were not found or
231: // did not match a folder, document, or link
232: throw new NodeNotFoundException(this .getClass(), this
233: .getAbsoluteName()
234: + Node.DELIMITER + nodeName);
235:
236: } catch (NodeCreationException nce) {
237: throw new NodeNotFoundException(this .getClass(), nodeName,
238: nce);
239:
240: } catch (NamingException ne) {
241: throw new NodeNotFoundException(this .getClass(), nodeName,
242: ne);
243: }
244: }
245:
246: /**
247: * Calls destroySubcontext with this context's name
248: * @throws NodeRemovalException if a NamingException is encountered
249: */
250: protected void destroyBackingData() throws NodeRemovalException {
251: try {
252: this .initialContext.destroySubcontext(this .nodeContextName);
253: } catch (NamingException ne) {
254: throw new NodeRemovalException(this .getClass(), this , ne);
255: }
256: }
257:
258: /**
259: * Gets this nodes initial context. Used by JNDI node factories
260: * to pass onto child nodes
261: * @return DirContext
262: */
263: DirContext getInitialContext() {
264: return this .initialContext;
265: }
266:
267: /**
268: * Gets this nodes context name. Used by JNDI node factories
269: * to construct child node context names
270: * @return Name
271: */
272: Name getNodeContextName() {
273: return this .nodeContextName;
274: }
275:
276: /**
277: * Helper method to create a sub folder
278: *
279: * @param nodeName
280: * @return Node
281: * @throws NodeCreationException
282: */
283: private Node loadSubFolder(String nodeName)
284: throws NodeCreationException {
285: return getSubFolderFactory().getInstance(this , nodeName);
286: }
287:
288: /**
289: * Helper method to create a configuration document
290: *
291: * @param nodeName
292: * @return Node
293: * @throws NodeCreationException
294: */
295: private Node loadConfigurationDocument(String nodeName)
296: throws NodeCreationException {
297:
298: return getConfigurationDocumentFactory().getInstance(this ,
299: nodeName);
300: }
301:
302: /**
303: * Helper method to create a sub link
304: *
305: * @param nodeName
306: * @return Node
307: * @throws NodeCreationException
308: */
309: private Node loadChildLinkNode(String nodeName)
310: throws NodeCreationException {
311:
312: // these constants are defined in order to make the code below readable
313: final String NAME = this .config.getNodeNameAttributeName();
314: final String EQUALS = this .config
315: .getAttributeNameValueSeparator();
316: final String LINK = this .config
317: .getLinkDocumentTypeAttributeValue();
318:
319: try {
320: // load the link configuration
321: Name childContextName = (Name) this .nodeContextName.clone();
322: childContextName.add(NAME + EQUALS + nodeName);
323:
324: ConfigurationDocument linkConfiguraitonDoc = new JNDIConfigurationDocument(
325: this , nodeName,
326: new DefaultConfigurationFormatService(),
327: this .initialContext, childContextName, this .config,
328: LINK);
329:
330: LinkNodeConfiguration config = (LinkNodeConfiguration) linkConfiguraitonDoc
331: .readConfiguration();
332:
333: if (config.getLinkNodeFactoryClass() == null) {
334: throw new InvalidConfigurationException(
335: this .getClass(), config.getConfigurationName(),
336: "LinkNodeFactoryClass", "Cannot be null");
337: }
338:
339: // get the LinkNodeFactory from the configuration
340: LinkNodeFactory factory = (LinkNodeFactory) config
341: .getLinkNodeFactoryClass().newInstance();
342:
343: // use the factory to get a link node instance
344: return factory.getInstance(this , nodeName,
345: linkConfiguraitonDoc);
346:
347: } catch (ConfigurationFormatException e) {
348: throw new NodeCreationException(this .getClass(), this ,
349: nodeName, "exception occured", e);
350: } catch (NodeIOException e) {
351: throw new NodeCreationException(this .getClass(), this ,
352: nodeName, "exception occured", e);
353: } catch (InstantiationException e) {
354: throw new NodeCreationException(this .getClass(), this ,
355: nodeName, "exception occured", e);
356: } catch (IllegalAccessException e) {
357: throw new NodeCreationException(this .getClass(), this ,
358: nodeName, "exception occured", e);
359: } catch (NamingException ne) {
360: throw new NodeCreationException(this .getClass(), this ,
361: nodeName, "NamingException occured", ne);
362: }
363: }
364:
365: /**
366: * Checks the backing directory to see if this nodes context exists and
367: * if it does, has the right attributes.
368: */
369: protected boolean backingDataExists() {
370: final String NODE_TYPE = this .config.getNodeTypeAttributeName();
371: final String FOLDER = this .config
372: .getFolderNodeTypeAttributeValue();
373:
374: boolean backingDataExists;
375: try {
376: Attributes folderAttributes = this .initialContext
377: .getAttributes(this .nodeContextName);
378:
379: if (folderAttributes.get(NODE_TYPE) != null
380: && folderAttributes.get(NODE_TYPE).contains(FOLDER)) {
381:
382: backingDataExists = true;
383: } else {
384: backingDataExists = false;
385: }
386: } catch (NamingException ne) {
387: backingDataExists = false;
388: }
389:
390: return backingDataExists;
391: }
392:
393: /**
394: * This implementation does not do anything if an object is added
395: * because the child could not have been loaded yet could not be cached
396: */
397: public void objectAdded(NamingEvent evt) {
398: // don't care
399: }
400:
401: /**
402: * Causes a refresh of this node
403: */
404: public void objectRemoved(NamingEvent evt) {
405: refresh();
406: }
407:
408: /**
409: * Causes a refresh of this node
410: */
411: public void objectRenamed(NamingEvent evt) {
412: refresh();
413: }
414:
415: /**
416: * Causes a refresh of this node
417: */
418: public void objectChanged(NamingEvent evt) {
419: refresh();
420: }
421:
422: /**
423: * Logs the fact that a NamingExceptionEvent occured
424: */
425: public void namingExceptionThrown(NamingExceptionEvent evt) {
426: if (log.isInfoEnabled()) {
427: log
428: .info("Notified of NamingExceptionEvent, this node is no "
429: + "longer listening for JNDI events, node name: ["
430: + getAbsoluteName()
431: + "], context name: ["
432: + this .nodeContextName
433: + "] naming exception: "
434: + ExceptionUtility
435: .printStackTracesToString(evt
436: .getException()));
437: }
438: }
439:
440: /**
441: * Registers this node as a NamingListener with the backing context
442: */
443: protected void registerNamingListener() {
444: try {
445: // get the event context
446: EventContext this Context = (EventContext) this .initialContext
447: .lookup(this .nodeContextName);
448:
449: // register for changes in object scope (this context only)
450: this Context.addNamingListener("",
451: EventContext.OBJECT_SCOPE, this );
452:
453: } catch (ClassCastException cce) {
454: if (log.isInfoEnabled()) {
455: log
456: .info("Node was not registered as a JNDI NamingListener because "
457: + "events were not supported by directory, node: ["
458: + getAbsoluteName()
459: + "], context name: ["
460: + this .nodeContextName + "]");
461: }
462: } catch (NamingException ne) {
463: if (log.isInfoEnabled()) {
464: log.info(
465: "NamingException caught registering NamingListener, node: ["
466: + getAbsoluteName()
467: + "], context name: ["
468: + this .nodeContextName + "]", ne);
469: }
470: }
471: }
472: }
|