0001: /*
0002: * Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package com.sun.jndi.dns;
0027:
0028: import java.util.Enumeration;
0029: import java.util.Hashtable;
0030:
0031: import javax.naming.*;
0032: import javax.naming.directory.*;
0033: import javax.naming.spi.DirectoryManager;
0034:
0035: import com.sun.jndi.toolkit.ctx.*;
0036:
0037: /**
0038: * A DnsContext is a directory context representing a DNS node.
0039: *
0040: * @author Scott Seligman
0041: * @version 1.30 07/05/05
0042: */
0043:
0044: public class DnsContext extends ComponentDirContext {
0045:
0046: DnsName domain; // fully-qualified domain name of this context,
0047: // with a root (empty) label at position 0
0048: Hashtable environment;
0049: private boolean envShared; // true if environment is possibly shared
0050: // and so must be copied on write
0051: private boolean parentIsDns; // was this DnsContext created by
0052: // another? see composeName()
0053: private String[] servers;
0054: private Resolver resolver;
0055:
0056: private boolean authoritative; // must all responses be authoritative?
0057: private boolean recursion; // request recursion on queries?
0058: private int timeout; // initial timeout on UDP queries in ms
0059: private int retries; // number of UDP retries
0060:
0061: static final NameParser nameParser = new DnsNameParser();
0062:
0063: // Timeouts for UDP queries use exponential backoff: each retry
0064: // is for twice as long as the last. The following constants set
0065: // the defaults for the initial timeout (in ms) and the number of
0066: // retries, and name the environment properties used to override
0067: // these defaults.
0068: private static final int DEFAULT_INIT_TIMEOUT = 1000;
0069: private static final int DEFAULT_RETRIES = 4;
0070: private static final String INIT_TIMEOUT = "com.sun.jndi.dns.timeout.initial";
0071: private static final String RETRIES = "com.sun.jndi.dns.timeout.retries";
0072:
0073: // The resource record type and class to use for lookups, and the
0074: // property used to modify them
0075: private CT lookupCT;
0076: private static final String LOOKUP_ATTR = "com.sun.jndi.dns.lookup.attr";
0077:
0078: // Property used to disallow recursion on queries
0079: private static final String RECURSION = "com.sun.jndi.dns.recursion";
0080:
0081: // ANY == ResourceRecord.QCLASS_STAR == ResourceRecord.QTYPE_STAR
0082: private static final int ANY = ResourceRecord.QTYPE_STAR;
0083:
0084: // The zone tree used for list operations
0085: private static final ZoneNode zoneTree = new ZoneNode(null);
0086:
0087: /**
0088: * Returns a DNS context for a given domain and servers.
0089: * Each server is of the form "server[:port]".
0090: * IPv6 literal host names include delimiting brackets.
0091: * There must be at least one server.
0092: * The environment must not be null; it is cloned before being stored.
0093: */
0094: public DnsContext(String domain, String[] servers,
0095: Hashtable environment) throws NamingException {
0096:
0097: this .domain = new DnsName(domain.endsWith(".") ? domain
0098: : domain + ".");
0099: this .servers = servers;
0100: this .environment = (Hashtable) environment.clone();
0101: envShared = false;
0102: parentIsDns = false;
0103: resolver = null;
0104:
0105: initFromEnvironment();
0106: }
0107:
0108: /*
0109: * Returns a clone of a DNS context, just like DnsContext(DnsContext)
0110: * but with a different domain name and with parentIsDns set to true.
0111: */
0112: DnsContext(DnsContext ctx, DnsName domain) {
0113: this (ctx);
0114: this .domain = domain;
0115: parentIsDns = true;
0116: }
0117:
0118: /*
0119: * Returns a clone of a DNS context. The context's modifiable
0120: * private state is independent of the original's (so closing one
0121: * context, for example, won't close the other). The two contexts
0122: * share <tt>environment</tt>, but it's copy-on-write so there's
0123: * no conflict.
0124: */
0125: private DnsContext(DnsContext ctx) {
0126: environment = ctx.environment;
0127: envShared = ctx.envShared = true;
0128: parentIsDns = ctx.parentIsDns;
0129: domain = ctx.domain;
0130: servers = ctx.servers;
0131: resolver = ctx.resolver;
0132: authoritative = ctx.authoritative;
0133: recursion = ctx.recursion;
0134: timeout = ctx.timeout;
0135: retries = ctx.retries;
0136: lookupCT = ctx.lookupCT;
0137: }
0138:
0139: public void close() {
0140: if (resolver != null) {
0141: resolver.close();
0142: resolver = null;
0143: }
0144: }
0145:
0146: //---------- Environment operations
0147:
0148: /*
0149: * Override default with a noncloning version.
0150: */
0151: protected Hashtable p_getEnvironment() {
0152: return environment;
0153: }
0154:
0155: public Hashtable getEnvironment() throws NamingException {
0156: return (Hashtable) environment.clone();
0157: }
0158:
0159: public Object addToEnvironment(String propName, Object propVal)
0160: throws NamingException {
0161:
0162: if (propName.equals(LOOKUP_ATTR)) {
0163: lookupCT = getLookupCT((String) propVal);
0164: } else if (propName.equals(Context.AUTHORITATIVE)) {
0165: authoritative = "true".equalsIgnoreCase((String) propVal);
0166: } else if (propName.equals(RECURSION)) {
0167: recursion = "true".equalsIgnoreCase((String) propVal);
0168: } else if (propName.equals(INIT_TIMEOUT)) {
0169: int val = Integer.parseInt((String) propVal);
0170: if (timeout != val) {
0171: timeout = val;
0172: resolver = null;
0173: }
0174: } else if (propName.equals(RETRIES)) {
0175: int val = Integer.parseInt((String) propVal);
0176: if (retries != val) {
0177: retries = val;
0178: resolver = null;
0179: }
0180: }
0181:
0182: if (!envShared) {
0183: return environment.put(propName, propVal);
0184: } else if (environment.get(propName) != propVal) {
0185: // copy on write
0186: environment = (Hashtable) environment.clone();
0187: envShared = false;
0188: return environment.put(propName, propVal);
0189: } else {
0190: return propVal;
0191: }
0192: }
0193:
0194: public Object removeFromEnvironment(String propName)
0195: throws NamingException {
0196:
0197: if (propName.equals(LOOKUP_ATTR)) {
0198: lookupCT = getLookupCT(null);
0199: } else if (propName.equals(Context.AUTHORITATIVE)) {
0200: authoritative = false;
0201: } else if (propName.equals(RECURSION)) {
0202: recursion = true;
0203: } else if (propName.equals(INIT_TIMEOUT)) {
0204: if (timeout != DEFAULT_INIT_TIMEOUT) {
0205: timeout = DEFAULT_INIT_TIMEOUT;
0206: resolver = null;
0207: }
0208: } else if (propName.equals(RETRIES)) {
0209: if (retries != DEFAULT_RETRIES) {
0210: retries = DEFAULT_RETRIES;
0211: resolver = null;
0212: }
0213: }
0214:
0215: if (!envShared) {
0216: return environment.remove(propName);
0217: } else if (environment.get(propName) != null) {
0218: // copy-on-write
0219: environment = (Hashtable) environment.clone();
0220: envShared = false;
0221: return environment.remove(propName);
0222: } else {
0223: return null;
0224: }
0225: }
0226:
0227: /*
0228: * Update PROVIDER_URL property. Call this only when environment
0229: * is not being shared.
0230: */
0231: void setProviderUrl(String url) {
0232: // assert !envShared;
0233: environment.put(Context.PROVIDER_URL, url);
0234: }
0235:
0236: /*
0237: * Read environment properties and set parameters.
0238: */
0239: private void initFromEnvironment()
0240: throws InvalidAttributeIdentifierException {
0241:
0242: lookupCT = getLookupCT((String) environment.get(LOOKUP_ATTR));
0243: authoritative = "true".equalsIgnoreCase((String) environment
0244: .get(Context.AUTHORITATIVE));
0245: String val = (String) environment.get(RECURSION);
0246: recursion = ((val == null) || "true".equalsIgnoreCase(val));
0247: val = (String) environment.get(INIT_TIMEOUT);
0248: timeout = (val == null) ? DEFAULT_INIT_TIMEOUT : Integer
0249: .parseInt(val);
0250: val = (String) environment.get(RETRIES);
0251: retries = (val == null) ? DEFAULT_RETRIES : Integer
0252: .parseInt(val);
0253: }
0254:
0255: private CT getLookupCT(String attrId)
0256: throws InvalidAttributeIdentifierException {
0257: return (attrId == null) ? new CT(ResourceRecord.CLASS_INTERNET,
0258: ResourceRecord.TYPE_TXT) : fromAttrId(attrId);
0259: }
0260:
0261: //---------- Naming operations
0262:
0263: public Object c_lookup(Name name, Continuation cont)
0264: throws NamingException {
0265:
0266: cont.setSuccess();
0267: if (name.isEmpty()) {
0268: DnsContext ctx = new DnsContext(this );
0269: ctx.resolver = new Resolver(servers, timeout, retries);
0270: // clone for parallelism
0271: return ctx;
0272: }
0273: try {
0274: DnsName fqdn = fullyQualify(name);
0275: ResourceRecords rrs = getResolver().query(fqdn,
0276: lookupCT.rrclass, lookupCT.rrtype, recursion,
0277: authoritative);
0278: Attributes attrs = rrsToAttrs(rrs, null);
0279: DnsContext ctx = new DnsContext(this , fqdn);
0280: return DirectoryManager.getObjectInstance(ctx, name, this ,
0281: environment, attrs);
0282: } catch (NamingException e) {
0283: cont.setError(this , name);
0284: throw cont.fillInException(e);
0285: } catch (Exception e) {
0286: cont.setError(this , name);
0287: NamingException ne = new NamingException(
0288: "Problem generating object using object factory");
0289: ne.setRootCause(e);
0290: throw cont.fillInException(ne);
0291: }
0292: }
0293:
0294: public Object c_lookupLink(Name name, Continuation cont)
0295: throws NamingException {
0296: return c_lookup(name, cont);
0297: }
0298:
0299: public NamingEnumeration c_list(Name name, Continuation cont)
0300: throws NamingException {
0301: cont.setSuccess();
0302: try {
0303: DnsName fqdn = fullyQualify(name);
0304: NameNode nnode = getNameNode(fqdn);
0305: DnsContext ctx = new DnsContext(this , fqdn);
0306: return new NameClassPairEnumeration(ctx, nnode
0307: .getChildren());
0308:
0309: } catch (NamingException e) {
0310: cont.setError(this , name);
0311: throw cont.fillInException(e);
0312: }
0313: }
0314:
0315: public NamingEnumeration c_listBindings(Name name, Continuation cont)
0316: throws NamingException {
0317: cont.setSuccess();
0318: try {
0319: DnsName fqdn = fullyQualify(name);
0320: NameNode nnode = getNameNode(fqdn);
0321: DnsContext ctx = new DnsContext(this , fqdn);
0322: return new BindingEnumeration(ctx, nnode.getChildren());
0323:
0324: } catch (NamingException e) {
0325: cont.setError(this , name);
0326: throw cont.fillInException(e);
0327: }
0328: }
0329:
0330: public void c_bind(Name name, Object obj, Continuation cont)
0331: throws NamingException {
0332: cont.setError(this , name);
0333: throw cont
0334: .fillInException(new OperationNotSupportedException());
0335: }
0336:
0337: public void c_rebind(Name name, Object obj, Continuation cont)
0338: throws NamingException {
0339: cont.setError(this , name);
0340: throw cont
0341: .fillInException(new OperationNotSupportedException());
0342: }
0343:
0344: public void c_unbind(Name name, Continuation cont)
0345: throws NamingException {
0346: cont.setError(this , name);
0347: throw cont
0348: .fillInException(new OperationNotSupportedException());
0349: }
0350:
0351: public void c_rename(Name oldname, Name newname, Continuation cont)
0352: throws NamingException {
0353: cont.setError(this , oldname);
0354: throw cont
0355: .fillInException(new OperationNotSupportedException());
0356: }
0357:
0358: public Context c_createSubcontext(Name name, Continuation cont)
0359: throws NamingException {
0360: cont.setError(this , name);
0361: throw cont
0362: .fillInException(new OperationNotSupportedException());
0363: }
0364:
0365: public void c_destroySubcontext(Name name, Continuation cont)
0366: throws NamingException {
0367: cont.setError(this , name);
0368: throw cont
0369: .fillInException(new OperationNotSupportedException());
0370: }
0371:
0372: public NameParser c_getNameParser(Name name, Continuation cont)
0373: throws NamingException {
0374: cont.setSuccess();
0375: return nameParser;
0376: }
0377:
0378: //---------- Directory operations
0379:
0380: public void c_bind(Name name, Object obj, Attributes attrs,
0381: Continuation cont) throws NamingException {
0382: cont.setError(this , name);
0383: throw cont
0384: .fillInException(new OperationNotSupportedException());
0385: }
0386:
0387: public void c_rebind(Name name, Object obj, Attributes attrs,
0388: Continuation cont) throws NamingException {
0389: cont.setError(this , name);
0390: throw cont
0391: .fillInException(new OperationNotSupportedException());
0392: }
0393:
0394: public DirContext c_createSubcontext(Name name, Attributes attrs,
0395: Continuation cont) throws NamingException {
0396: cont.setError(this , name);
0397: throw cont
0398: .fillInException(new OperationNotSupportedException());
0399: }
0400:
0401: public Attributes c_getAttributes(Name name, String[] attrIds,
0402: Continuation cont) throws NamingException {
0403:
0404: cont.setSuccess();
0405: try {
0406: DnsName fqdn = fullyQualify(name);
0407: CT[] cts = attrIdsToClassesAndTypes(attrIds);
0408: CT ct = getClassAndTypeToQuery(cts);
0409: ResourceRecords rrs = getResolver().query(fqdn, ct.rrclass,
0410: ct.rrtype, recursion, authoritative);
0411: return rrsToAttrs(rrs, cts);
0412:
0413: } catch (NamingException e) {
0414: cont.setError(this , name);
0415: throw cont.fillInException(e);
0416: }
0417: }
0418:
0419: public void c_modifyAttributes(Name name, int mod_op,
0420: Attributes attrs, Continuation cont) throws NamingException {
0421: cont.setError(this , name);
0422: throw cont
0423: .fillInException(new OperationNotSupportedException());
0424: }
0425:
0426: public void c_modifyAttributes(Name name, ModificationItem[] mods,
0427: Continuation cont) throws NamingException {
0428: cont.setError(this , name);
0429: throw cont
0430: .fillInException(new OperationNotSupportedException());
0431: }
0432:
0433: public NamingEnumeration c_search(Name name,
0434: Attributes matchingAttributes, String[] attributesToReturn,
0435: Continuation cont) throws NamingException {
0436: throw new OperationNotSupportedException();
0437: }
0438:
0439: public NamingEnumeration c_search(Name name, String filter,
0440: SearchControls cons, Continuation cont)
0441: throws NamingException {
0442: throw new OperationNotSupportedException();
0443: }
0444:
0445: public NamingEnumeration c_search(Name name, String filterExpr,
0446: Object[] filterArgs, SearchControls cons, Continuation cont)
0447: throws NamingException {
0448: throw new OperationNotSupportedException();
0449: }
0450:
0451: public DirContext c_getSchema(Name name, Continuation cont)
0452: throws NamingException {
0453: cont.setError(this , name);
0454: throw cont
0455: .fillInException(new OperationNotSupportedException());
0456: }
0457:
0458: public DirContext c_getSchemaClassDefinition(Name name,
0459: Continuation cont) throws NamingException {
0460: cont.setError(this , name);
0461: throw cont
0462: .fillInException(new OperationNotSupportedException());
0463: }
0464:
0465: //---------- Name-related operations
0466:
0467: public String getNameInNamespace() {
0468: return domain.toString();
0469: }
0470:
0471: public Name composeName(Name name, Name prefix)
0472: throws NamingException {
0473: Name result;
0474:
0475: // Any name that's not a CompositeName is assumed to be a DNS
0476: // compound name. Convert each to a DnsName for syntax checking.
0477: if (!(prefix instanceof DnsName || prefix instanceof CompositeName)) {
0478: prefix = (new DnsName()).addAll(prefix);
0479: }
0480: if (!(name instanceof DnsName || name instanceof CompositeName)) {
0481: name = (new DnsName()).addAll(name);
0482: }
0483:
0484: // Each of prefix and name is now either a DnsName or a CompositeName.
0485:
0486: // If we have two DnsNames, simply join them together.
0487: if ((prefix instanceof DnsName) && (name instanceof DnsName)) {
0488: result = (DnsName) (prefix.clone());
0489: result.addAll(name);
0490: return new CompositeName().add(result.toString());
0491: }
0492:
0493: // Wrap compound names in composite names.
0494: Name prefixC = (prefix instanceof CompositeName) ? prefix
0495: : new CompositeName().add(prefix.toString());
0496: Name nameC = (name instanceof CompositeName) ? name
0497: : new CompositeName().add(name.toString());
0498: int prefixLast = prefixC.size() - 1;
0499:
0500: // Let toolkit do the work at namespace boundaries.
0501: if (nameC.isEmpty() || nameC.get(0).equals("")
0502: || prefixC.isEmpty()
0503: || prefixC.get(prefixLast).equals("")) {
0504: return super .composeName(nameC, prefixC);
0505: }
0506:
0507: result = (prefix == prefixC) ? (CompositeName) prefixC.clone()
0508: : prefixC; // prefixC is already a clone
0509: result.addAll(nameC);
0510:
0511: if (parentIsDns) {
0512: DnsName dnsComp = (prefix instanceof DnsName) ? (DnsName) prefix
0513: .clone()
0514: : new DnsName(prefixC.get(prefixLast));
0515: dnsComp.addAll((name instanceof DnsName) ? name
0516: : new DnsName(nameC.get(0)));
0517: result.remove(prefixLast + 1);
0518: result.remove(prefixLast);
0519: result.add(prefixLast, dnsComp.toString());
0520: }
0521: return result;
0522: }
0523:
0524: //---------- Helper methods
0525:
0526: /*
0527: * Resolver is not created until needed, to allow time for updates
0528: * to the environment.
0529: */
0530: private synchronized Resolver getResolver() throws NamingException {
0531: if (resolver == null) {
0532: resolver = new Resolver(servers, timeout, retries);
0533: }
0534: return resolver;
0535: }
0536:
0537: /*
0538: * Returns the fully-qualified domain name of a name given
0539: * relative to this context. Result includes a root label (an
0540: * empty component at position 0).
0541: */
0542: DnsName fullyQualify(Name name) throws NamingException {
0543: if (name.isEmpty()) {
0544: return domain;
0545: }
0546: DnsName dnsName = (name instanceof CompositeName) ? new DnsName(
0547: name.get(0)) // parse name
0548: : (DnsName) (new DnsName()).addAll(name); // clone & check syntax
0549:
0550: if (dnsName.hasRootLabel()) {
0551: // Be overly generous and allow root label if we're in root domain.
0552: if (domain.size() == 1) {
0553: return dnsName;
0554: } else {
0555: throw new InvalidNameException("DNS name " + dnsName
0556: + " not relative to " + domain);
0557: }
0558: }
0559: return (DnsName) dnsName.addAll(0, domain);
0560: }
0561:
0562: /*
0563: * Converts resource records to an attribute set. Only resource
0564: * records in the answer section are used, and only those that
0565: * match the classes and types in cts (see classAndTypeMatch()
0566: * for matching rules).
0567: */
0568: private static Attributes rrsToAttrs(ResourceRecords rrs, CT[] cts) {
0569:
0570: BasicAttributes attrs = new BasicAttributes(true);
0571:
0572: for (int i = 0; i < rrs.answer.size(); i++) {
0573: ResourceRecord rr = (ResourceRecord) rrs.answer
0574: .elementAt(i);
0575: int rrtype = rr.getType();
0576: int rrclass = rr.getRrclass();
0577:
0578: if (!classAndTypeMatch(rrclass, rrtype, cts)) {
0579: continue;
0580: }
0581:
0582: String attrId = toAttrId(rrclass, rrtype);
0583: Attribute attr = attrs.get(attrId);
0584: if (attr == null) {
0585: attr = new BasicAttribute(attrId);
0586: attrs.put(attr);
0587: }
0588: attr.add(rr.getRdata());
0589: }
0590: return attrs;
0591: }
0592:
0593: /*
0594: * Returns true if rrclass and rrtype match some element of cts.
0595: * A match occurs if corresponding classes and types are equal,
0596: * or if the array value is ANY. If cts is null, then any class
0597: * and type match.
0598: */
0599: private static boolean classAndTypeMatch(int rrclass, int rrtype,
0600: CT[] cts) {
0601: if (cts == null) {
0602: return true;
0603: }
0604: for (int i = 0; i < cts.length; i++) {
0605: CT ct = cts[i];
0606: boolean classMatch = (ct.rrclass == ANY)
0607: || (ct.rrclass == rrclass);
0608: boolean typeMatch = (ct.rrtype == ANY)
0609: || (ct.rrtype == rrtype);
0610: if (classMatch && typeMatch) {
0611: return true;
0612: }
0613: }
0614: return false;
0615: }
0616:
0617: /*
0618: * Returns the attribute ID for a resource record given its class
0619: * and type. If the record is in the internet class, the
0620: * corresponding attribute ID is the record's type name (or the
0621: * integer type value if the name is not known). If the record is
0622: * not in the internet class, the class name (or integer class
0623: * value) is prepended to the attribute ID, separated by a space.
0624: *
0625: * A class or type value of ANY represents an indeterminate class
0626: * or type, and is represented within the attribute ID by "*".
0627: * For example, the attribute ID "IN *" represents
0628: * any type in the internet class, and "* NS" represents an NS
0629: * record of any class.
0630: */
0631: private static String toAttrId(int rrclass, int rrtype) {
0632: String attrId = ResourceRecord.getTypeName(rrtype);
0633: if (rrclass != ResourceRecord.CLASS_INTERNET) {
0634: attrId = ResourceRecord.getRrclassName(rrclass) + " "
0635: + attrId;
0636: }
0637: return attrId;
0638: }
0639:
0640: /*
0641: * Returns the class and type values corresponding to an attribute
0642: * ID. An indeterminate class or type is represented by ANY. See
0643: * toAttrId() for the format of attribute IDs.
0644: *
0645: * @throws InvalidAttributeIdentifierException
0646: * if class or type is unknown
0647: */
0648: private static CT fromAttrId(String attrId)
0649: throws InvalidAttributeIdentifierException {
0650:
0651: if (attrId.equals("")) {
0652: throw new InvalidAttributeIdentifierException(
0653: "Attribute ID cannot be empty");
0654: }
0655: int rrclass;
0656: int rrtype;
0657: int space = attrId.indexOf(' ');
0658:
0659: // class
0660: if (space < 0) {
0661: rrclass = ResourceRecord.CLASS_INTERNET;
0662: } else {
0663: String className = attrId.substring(0, space);
0664: rrclass = ResourceRecord.getRrclass(className);
0665: if (rrclass < 0) {
0666: throw new InvalidAttributeIdentifierException(
0667: "Unknown resource record class '" + className
0668: + '\'');
0669: }
0670: }
0671:
0672: // type
0673: String typeName = attrId.substring(space + 1);
0674: rrtype = ResourceRecord.getType(typeName);
0675: if (rrtype < 0) {
0676: throw new InvalidAttributeIdentifierException(
0677: "Unknown resource record type '" + typeName + '\'');
0678: }
0679:
0680: return new CT(rrclass, rrtype);
0681: }
0682:
0683: /*
0684: * Returns an array of the classes and types corresponding to a
0685: * set of attribute IDs. See toAttrId() for the format of
0686: * attribute IDs, and classAndTypeMatch() for the format of the
0687: * array returned.
0688: */
0689: private static CT[] attrIdsToClassesAndTypes(String[] attrIds)
0690: throws InvalidAttributeIdentifierException {
0691: if (attrIds == null) {
0692: return null;
0693: }
0694: CT[] cts = new CT[attrIds.length];
0695:
0696: for (int i = 0; i < attrIds.length; i++) {
0697: cts[i] = fromAttrId(attrIds[i]);
0698: }
0699: return cts;
0700: }
0701:
0702: /*
0703: * Returns the most restrictive resource record class and type
0704: * that may be used to query for records matching cts.
0705: * See classAndTypeMatch() for matching rules.
0706: */
0707: private static CT getClassAndTypeToQuery(CT[] cts) {
0708: int rrclass;
0709: int rrtype;
0710:
0711: if (cts == null) {
0712: // Query all records.
0713: rrclass = ANY;
0714: rrtype = ANY;
0715: } else if (cts.length == 0) {
0716: // No records are requested, but we need to ask for something.
0717: rrclass = ResourceRecord.CLASS_INTERNET;
0718: rrtype = ANY;
0719: } else {
0720: rrclass = cts[0].rrclass;
0721: rrtype = cts[0].rrtype;
0722: for (int i = 1; i < cts.length; i++) {
0723: if (rrclass != cts[i].rrclass) {
0724: rrclass = ANY;
0725: }
0726: if (rrtype != cts[i].rrtype) {
0727: rrtype = ANY;
0728: }
0729: }
0730: }
0731: return new CT(rrclass, rrtype);
0732: }
0733:
0734: //---------- Support for list operations
0735:
0736: /*
0737: * Synchronization notes:
0738: *
0739: * Any access to zoneTree that walks the tree, whether it modifies
0740: * the tree or not, is synchronized on zoneTree.
0741: * [%%% Note: a read/write lock would allow increased concurrency.]
0742: * The depth of a ZoneNode can thereafter be accessed without
0743: * further synchronization. Access to other fields and methods
0744: * should be synchronized on the node itself.
0745: *
0746: * A zone's contents is a NameNode tree that, once created, is never
0747: * modified. The only synchronization needed is to ensure that it
0748: * gets flushed into shared memory after being created, which is
0749: * accomplished by ZoneNode.populate(). The contents are accessed
0750: * via a soft reference, so a ZoneNode may be seen to be populated
0751: * one moment and unpopulated the next.
0752: */
0753:
0754: /*
0755: * Returns the node in the zone tree corresponding to a
0756: * fully-qualified domain name. If the desired portion of the
0757: * tree has not yet been populated or has been outdated, a zone
0758: * transfer is done to populate the tree.
0759: */
0760: private NameNode getNameNode(DnsName fqdn) throws NamingException {
0761: dprint("getNameNode(" + fqdn + ")");
0762:
0763: // Find deepest related zone in zone tree.
0764: ZoneNode znode;
0765: DnsName zone;
0766: synchronized (zoneTree) {
0767: znode = zoneTree.getDeepestPopulated(fqdn);
0768: }
0769: dprint("Deepest related zone in zone tree: "
0770: + ((znode != null) ? znode.getLabel() : "[none]"));
0771:
0772: NameNode topOfZone;
0773: NameNode nnode;
0774:
0775: if (znode != null) {
0776: synchronized (znode) {
0777: topOfZone = znode.getContents();
0778: }
0779: // If fqdn is in znode's zone, is not at a zone cut, and
0780: // is current, we're done.
0781: if (topOfZone != null) {
0782: nnode = topOfZone.get(fqdn, znode.depth() + 1); // +1 for root
0783:
0784: if ((nnode != null) && !nnode.isZoneCut()) {
0785: dprint("Found node " + fqdn + " in zone tree");
0786: zone = (DnsName) fqdn.getPrefix(znode.depth() + 1); // +1 for root
0787: boolean current = isZoneCurrent(znode, zone);
0788: boolean restart = false;
0789:
0790: synchronized (znode) {
0791: if (topOfZone != znode.getContents()) {
0792: // Zone was modified while we were examining it.
0793: // All bets are off.
0794: restart = true;
0795: } else if (!current) {
0796: znode.depopulate();
0797: } else {
0798: return nnode; // cache hit!
0799: }
0800: }
0801: dprint("Zone not current; discarding node");
0802: if (restart) {
0803: return getNameNode(fqdn);
0804: }
0805: }
0806: }
0807: }
0808:
0809: // Cache miss... do it the expensive way.
0810: dprint("Adding node " + fqdn + " to zone tree");
0811:
0812: // Find fqdn's zone and add it to the tree.
0813: zone = getResolver().findZoneName(fqdn,
0814: ResourceRecord.CLASS_INTERNET, recursion);
0815: dprint("Node's zone is " + zone);
0816: synchronized (zoneTree) {
0817: znode = (ZoneNode) zoneTree.add(zone, 1); // "1" to skip root
0818: }
0819:
0820: // If znode is now populated we know -- because the first half of
0821: // getNodeName() didn't find it -- that it was populated by another
0822: // thread during this method call. Assume then that it's current.
0823:
0824: synchronized (znode) {
0825: topOfZone = znode.isPopulated() ? znode.getContents()
0826: : populateZone(znode, zone);
0827: }
0828: // Desired node should now be in znode's populated zone. Find it.
0829: nnode = topOfZone.get(fqdn, zone.size());
0830: if (nnode == null) {
0831: throw new ConfigurationException(
0832: "DNS error: node not found in its own zone");
0833: }
0834: dprint("Found node in newly-populated zone");
0835: return nnode;
0836: }
0837:
0838: /*
0839: * Does a zone transfer to [re]populate a zone in the zone tree.
0840: * Returns the zone's new contents.
0841: */
0842: private NameNode populateZone(ZoneNode znode, DnsName zone)
0843: throws NamingException {
0844: dprint("Populating zone " + zone);
0845: // assert Thread.holdsLock(znode);
0846: ResourceRecords rrs = getResolver().queryZone(zone,
0847: ResourceRecord.CLASS_INTERNET, recursion);
0848: dprint("zone xfer complete: " + rrs.answer.size() + " records");
0849: return znode.populate(zone, rrs);
0850: }
0851:
0852: /*
0853: * Determine if a ZoneNode's data is current.
0854: * We base this on a comparison between the cached serial
0855: * number and the latest SOA record.
0856: *
0857: * If there is no SOA record, znode is not (or is no longer) a zone:
0858: * depopulate znode and return false.
0859: *
0860: * Since this method may perform a network operation, it is best
0861: * to call it with znode unlocked. Caller must then note that the
0862: * result may be outdated by the time this method returns.
0863: */
0864: private boolean isZoneCurrent(ZoneNode znode, DnsName zone)
0865: throws NamingException {
0866: // former version: return !znode.isExpired();
0867:
0868: if (!znode.isPopulated()) {
0869: return false;
0870: }
0871: ResourceRecord soa = getResolver().findSoa(zone,
0872: ResourceRecord.CLASS_INTERNET, recursion);
0873: synchronized (znode) {
0874: if (soa == null) {
0875: znode.depopulate();
0876: }
0877: return (znode.isPopulated() && znode
0878: .compareSerialNumberTo(soa) >= 0);
0879: }
0880: }
0881:
0882: //---------- Debugging
0883:
0884: public static boolean debug = false;
0885:
0886: private static final void dprint(String msg) {
0887: if (debug) {
0888: System.err.println("** " + msg);
0889: }
0890: }
0891: }
0892:
0893: //----------
0894:
0895: /*
0896: * A pairing of a resource record class and a resource record type.
0897: * A value of ANY in either field represents an indeterminate value.
0898: */
0899: class CT {
0900: int rrclass;
0901: int rrtype;
0902:
0903: CT(int rrclass, int rrtype) {
0904: this .rrclass = rrclass;
0905: this .rrtype = rrtype;
0906: }
0907: }
0908:
0909: //----------
0910:
0911: /*
0912: * An enumeration of name/classname pairs.
0913: *
0914: * Nodes that have children or that are zone cuts are returned with
0915: * classname DirContext. Other nodes are returned with classname
0916: * Object even though they are DirContexts as well, since this might
0917: * make the namespace easier to browse.
0918: */
0919: class NameClassPairEnumeration implements NamingEnumeration {
0920:
0921: protected Enumeration nodes; // nodes to be enumerated, or null if none
0922: protected DnsContext ctx; // context being enumerated
0923:
0924: NameClassPairEnumeration(DnsContext ctx, Hashtable nodes) {
0925: this .ctx = ctx;
0926: this .nodes = (nodes != null) ? nodes.elements() : null;
0927: }
0928:
0929: /*
0930: * ctx will be closed when no longer needed by the enumeration.
0931: */
0932: public void close() {
0933: nodes = null;
0934: if (ctx != null) {
0935: ctx.close();
0936: ctx = null;
0937: }
0938: }
0939:
0940: public boolean hasMore() {
0941: boolean more = ((nodes != null) && nodes.hasMoreElements());
0942: if (!more) {
0943: close();
0944: }
0945: return more;
0946: }
0947:
0948: public Object next() throws NamingException {
0949: if (!hasMore()) {
0950: throw new java.util.NoSuchElementException();
0951: }
0952: NameNode nnode = (NameNode) nodes.nextElement();
0953: String className = (nnode.isZoneCut() || (nnode.getChildren() != null)) ? "javax.naming.directory.DirContext"
0954: : "java.lang.Object";
0955:
0956: String label = nnode.getLabel();
0957: Name compName = (new DnsName()).add(label);
0958: Name cname = (new CompositeName()).add(compName.toString());
0959:
0960: NameClassPair ncp = new NameClassPair(cname.toString(),
0961: className);
0962: ncp.setNameInNamespace(ctx.fullyQualify(cname).toString());
0963: return ncp;
0964: }
0965:
0966: public boolean hasMoreElements() {
0967: return hasMore();
0968: }
0969:
0970: public Object nextElement() {
0971: try {
0972: return next();
0973: } catch (NamingException e) {
0974: throw (new java.util.NoSuchElementException(
0975: "javax.naming.NamingException was thrown: "
0976: + e.getMessage()));
0977: }
0978: }
0979: }
0980:
0981: /*
0982: * An enumeration of Bindings.
0983: */
0984: class BindingEnumeration extends NameClassPairEnumeration {
0985:
0986: BindingEnumeration(DnsContext ctx, Hashtable nodes) {
0987: super (ctx, nodes);
0988: }
0989:
0990: // Finalizer not needed since it's safe to leave ctx unclosed.
0991: // protected void finalize() {
0992: // close();
0993: // }
0994:
0995: public Object next() throws NamingException {
0996: if (!hasMore()) {
0997: throw (new java.util.NoSuchElementException());
0998: }
0999: NameNode nnode = (NameNode) nodes.nextElement();
1000:
1001: String label = nnode.getLabel();
1002: Name compName = (new DnsName()).add(label);
1003: String compNameStr = compName.toString();
1004: Name cname = (new CompositeName()).add(compNameStr);
1005: String cnameStr = cname.toString();
1006:
1007: DnsName fqdn = ctx.fullyQualify(compName);
1008:
1009: // Clone ctx to create the child context.
1010: DnsContext child = new DnsContext(ctx, fqdn);
1011:
1012: try {
1013: Object obj = DirectoryManager.getObjectInstance(child,
1014: cname, ctx, child.environment, null);
1015: Binding binding = new Binding(cnameStr, obj);
1016: binding.setNameInNamespace(ctx.fullyQualify(cname)
1017: .toString());
1018: return binding;
1019: } catch (Exception e) {
1020: NamingException ne = new NamingException(
1021: "Problem generating object using object factory");
1022: ne.setRootCause(e);
1023: throw ne;
1024: }
1025: }
1026: }
|