0N/A/*
2362N/A * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage com.sun.jndi.dns;
0N/A
0N/A
0N/Aimport java.util.Enumeration;
0N/Aimport java.util.Hashtable;
0N/A
0N/Aimport javax.naming.*;
0N/Aimport javax.naming.directory.*;
0N/Aimport javax.naming.spi.DirectoryManager;
0N/A
0N/Aimport com.sun.jndi.toolkit.ctx.*;
0N/A
0N/A
0N/A/**
0N/A * A DnsContext is a directory context representing a DNS node.
0N/A *
0N/A * @author Scott Seligman
0N/A */
0N/A
0N/A
0N/Apublic class DnsContext extends ComponentDirContext {
0N/A
0N/A DnsName domain; // fully-qualified domain name of this context,
0N/A // with a root (empty) label at position 0
0N/A Hashtable environment;
0N/A private boolean envShared; // true if environment is possibly shared
0N/A // and so must be copied on write
0N/A private boolean parentIsDns; // was this DnsContext created by
0N/A // another? see composeName()
0N/A private String[] servers;
0N/A private Resolver resolver;
0N/A
0N/A private boolean authoritative; // must all responses be authoritative?
0N/A private boolean recursion; // request recursion on queries?
0N/A private int timeout; // initial timeout on UDP queries in ms
0N/A private int retries; // number of UDP retries
0N/A
0N/A static final NameParser nameParser = new DnsNameParser();
0N/A
0N/A // Timeouts for UDP queries use exponential backoff: each retry
0N/A // is for twice as long as the last. The following constants set
0N/A // the defaults for the initial timeout (in ms) and the number of
0N/A // retries, and name the environment properties used to override
0N/A // these defaults.
0N/A private static final int DEFAULT_INIT_TIMEOUT = 1000;
0N/A private static final int DEFAULT_RETRIES = 4;
0N/A private static final String INIT_TIMEOUT =
0N/A "com.sun.jndi.dns.timeout.initial";
0N/A private static final String RETRIES = "com.sun.jndi.dns.timeout.retries";
0N/A
0N/A // The resource record type and class to use for lookups, and the
0N/A // property used to modify them
0N/A private CT lookupCT;
0N/A private static final String LOOKUP_ATTR = "com.sun.jndi.dns.lookup.attr";
0N/A
0N/A // Property used to disallow recursion on queries
0N/A private static final String RECURSION = "com.sun.jndi.dns.recursion";
0N/A
0N/A // ANY == ResourceRecord.QCLASS_STAR == ResourceRecord.QTYPE_STAR
0N/A private static final int ANY = ResourceRecord.QTYPE_STAR;
0N/A
0N/A // The zone tree used for list operations
0N/A private static final ZoneNode zoneTree = new ZoneNode(null);
0N/A
0N/A
0N/A /**
0N/A * Returns a DNS context for a given domain and servers.
0N/A * Each server is of the form "server[:port]".
0N/A * IPv6 literal host names include delimiting brackets.
0N/A * There must be at least one server.
0N/A * The environment must not be null; it is cloned before being stored.
0N/A */
0N/A public DnsContext(String domain, String[] servers, Hashtable environment)
0N/A throws NamingException {
0N/A
0N/A this.domain = new DnsName(domain.endsWith(".")
0N/A ? domain
0N/A : domain + ".");
0N/A this.servers = servers;
0N/A this.environment = (Hashtable) environment.clone();
0N/A envShared = false;
0N/A parentIsDns = false;
0N/A resolver = null;
0N/A
0N/A initFromEnvironment();
0N/A }
0N/A
0N/A /*
0N/A * Returns a clone of a DNS context, just like DnsContext(DnsContext)
0N/A * but with a different domain name and with parentIsDns set to true.
0N/A */
0N/A DnsContext(DnsContext ctx, DnsName domain) {
0N/A this(ctx);
0N/A this.domain = domain;
0N/A parentIsDns = true;
0N/A }
0N/A
0N/A /*
0N/A * Returns a clone of a DNS context. The context's modifiable
0N/A * private state is independent of the original's (so closing one
0N/A * context, for example, won't close the other). The two contexts
0N/A * share <tt>environment</tt>, but it's copy-on-write so there's
0N/A * no conflict.
0N/A */
0N/A private DnsContext(DnsContext ctx) {
0N/A environment = ctx.environment;
0N/A envShared = ctx.envShared = true;
0N/A parentIsDns = ctx.parentIsDns;
0N/A domain = ctx.domain;
0N/A servers = ctx.servers;
0N/A resolver = ctx.resolver;
0N/A authoritative = ctx.authoritative;
0N/A recursion = ctx.recursion;
0N/A timeout = ctx.timeout;
0N/A retries = ctx.retries;
0N/A lookupCT = ctx.lookupCT;
0N/A }
0N/A
0N/A public void close() {
0N/A if (resolver != null) {
0N/A resolver.close();
0N/A resolver = null;
0N/A }
0N/A }
0N/A
0N/A
0N/A //---------- Environment operations
0N/A
0N/A /*
0N/A * Override default with a noncloning version.
0N/A */
0N/A protected Hashtable p_getEnvironment() {
0N/A return environment;
0N/A }
0N/A
0N/A public Hashtable getEnvironment() throws NamingException {
0N/A return (Hashtable) environment.clone();
0N/A }
0N/A
0N/A public Object addToEnvironment(String propName, Object propVal)
0N/A throws NamingException {
0N/A
0N/A if (propName.equals(LOOKUP_ATTR)) {
0N/A lookupCT = getLookupCT((String) propVal);
0N/A } else if (propName.equals(Context.AUTHORITATIVE)) {
0N/A authoritative = "true".equalsIgnoreCase((String) propVal);
0N/A } else if (propName.equals(RECURSION)) {
0N/A recursion = "true".equalsIgnoreCase((String) propVal);
0N/A } else if (propName.equals(INIT_TIMEOUT)) {
0N/A int val = Integer.parseInt((String) propVal);
0N/A if (timeout != val) {
0N/A timeout = val;
0N/A resolver = null;
0N/A }
0N/A } else if (propName.equals(RETRIES)) {
0N/A int val = Integer.parseInt((String) propVal);
0N/A if (retries != val) {
0N/A retries = val;
0N/A resolver = null;
0N/A }
0N/A }
0N/A
0N/A if (!envShared) {
0N/A return environment.put(propName, propVal);
0N/A } else if (environment.get(propName) != propVal) {
0N/A // copy on write
0N/A environment = (Hashtable) environment.clone();
0N/A envShared = false;
0N/A return environment.put(propName, propVal);
0N/A } else {
0N/A return propVal;
0N/A }
0N/A }
0N/A
0N/A public Object removeFromEnvironment(String propName)
0N/A throws NamingException {
0N/A
0N/A if (propName.equals(LOOKUP_ATTR)) {
0N/A lookupCT = getLookupCT(null);
0N/A } else if (propName.equals(Context.AUTHORITATIVE)) {
0N/A authoritative = false;
0N/A } else if (propName.equals(RECURSION)) {
0N/A recursion = true;
0N/A } else if (propName.equals(INIT_TIMEOUT)) {
0N/A if (timeout != DEFAULT_INIT_TIMEOUT) {
0N/A timeout = DEFAULT_INIT_TIMEOUT;
0N/A resolver = null;
0N/A }
0N/A } else if (propName.equals(RETRIES)) {
0N/A if (retries != DEFAULT_RETRIES) {
0N/A retries = DEFAULT_RETRIES;
0N/A resolver = null;
0N/A }
0N/A }
0N/A
0N/A if (!envShared) {
0N/A return environment.remove(propName);
0N/A } else if (environment.get(propName) != null) {
0N/A // copy-on-write
0N/A environment = (Hashtable) environment.clone();
0N/A envShared = false;
0N/A return environment.remove(propName);
0N/A } else {
0N/A return null;
0N/A }
0N/A }
0N/A
0N/A /*
0N/A * Update PROVIDER_URL property. Call this only when environment
0N/A * is not being shared.
0N/A */
0N/A void setProviderUrl(String url) {
0N/A // assert !envShared;
0N/A environment.put(Context.PROVIDER_URL, url);
0N/A }
0N/A
0N/A /*
0N/A * Read environment properties and set parameters.
0N/A */
0N/A private void initFromEnvironment()
0N/A throws InvalidAttributeIdentifierException {
0N/A
0N/A lookupCT = getLookupCT((String) environment.get(LOOKUP_ATTR));
0N/A authoritative = "true".equalsIgnoreCase((String)
0N/A environment.get(Context.AUTHORITATIVE));
0N/A String val = (String) environment.get(RECURSION);
0N/A recursion = ((val == null) ||
0N/A "true".equalsIgnoreCase(val));
0N/A val = (String) environment.get(INIT_TIMEOUT);
0N/A timeout = (val == null)
0N/A ? DEFAULT_INIT_TIMEOUT
0N/A : Integer.parseInt(val);
0N/A val = (String) environment.get(RETRIES);
0N/A retries = (val == null)
0N/A ? DEFAULT_RETRIES
0N/A : Integer.parseInt(val);
0N/A }
0N/A
0N/A private CT getLookupCT(String attrId)
0N/A throws InvalidAttributeIdentifierException {
0N/A return (attrId == null)
0N/A ? new CT(ResourceRecord.CLASS_INTERNET, ResourceRecord.TYPE_TXT)
0N/A : fromAttrId(attrId);
0N/A }
0N/A
0N/A
0N/A //---------- Naming operations
0N/A
0N/A public Object c_lookup(Name name, Continuation cont)
0N/A throws NamingException {
0N/A
0N/A cont.setSuccess();
0N/A if (name.isEmpty()) {
0N/A DnsContext ctx = new DnsContext(this);
0N/A ctx.resolver = new Resolver(servers, timeout, retries);
0N/A // clone for parallelism
0N/A return ctx;
0N/A }
0N/A try {
0N/A DnsName fqdn = fullyQualify(name);
0N/A ResourceRecords rrs =
0N/A getResolver().query(fqdn, lookupCT.rrclass, lookupCT.rrtype,
0N/A recursion, authoritative);
0N/A Attributes attrs = rrsToAttrs(rrs, null);
0N/A DnsContext ctx = new DnsContext(this, fqdn);
0N/A return DirectoryManager.getObjectInstance(ctx, name, this,
0N/A environment, attrs);
0N/A } catch (NamingException e) {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(e);
0N/A } catch (Exception e) {
0N/A cont.setError(this, name);
0N/A NamingException ne = new NamingException(
0N/A "Problem generating object using object factory");
0N/A ne.setRootCause(e);
0N/A throw cont.fillInException(ne);
0N/A }
0N/A }
0N/A
0N/A public Object c_lookupLink(Name name, Continuation cont)
0N/A throws NamingException {
0N/A return c_lookup(name, cont);
0N/A }
0N/A
0N/A public NamingEnumeration c_list(Name name, Continuation cont)
0N/A throws NamingException {
0N/A cont.setSuccess();
0N/A try {
0N/A DnsName fqdn = fullyQualify(name);
0N/A NameNode nnode = getNameNode(fqdn);
0N/A DnsContext ctx = new DnsContext(this, fqdn);
0N/A return new NameClassPairEnumeration(ctx, nnode.getChildren());
0N/A
0N/A } catch (NamingException e) {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(e);
0N/A }
0N/A }
0N/A
0N/A public NamingEnumeration c_listBindings(Name name, Continuation cont)
0N/A throws NamingException {
0N/A cont.setSuccess();
0N/A try {
0N/A DnsName fqdn = fullyQualify(name);
0N/A NameNode nnode = getNameNode(fqdn);
0N/A DnsContext ctx = new DnsContext(this, fqdn);
0N/A return new BindingEnumeration(ctx, nnode.getChildren());
0N/A
0N/A } catch (NamingException e) {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(e);
0N/A }
0N/A }
0N/A
0N/A public void c_bind(Name name, Object obj, Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public void c_rebind(Name name, Object obj, Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public void c_unbind(Name name, Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public void c_rename(Name oldname, Name newname, Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, oldname);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public Context c_createSubcontext(Name name, Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public void c_destroySubcontext(Name name, Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public NameParser c_getNameParser(Name name, Continuation cont)
0N/A throws NamingException {
0N/A cont.setSuccess();
0N/A return nameParser;
0N/A }
0N/A
0N/A
0N/A //---------- Directory operations
0N/A
0N/A public void c_bind(Name name,
0N/A Object obj,
0N/A Attributes attrs,
0N/A Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public void c_rebind(Name name,
0N/A Object obj,
0N/A Attributes attrs,
0N/A Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public DirContext c_createSubcontext(Name name,
0N/A Attributes attrs,
0N/A Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public Attributes c_getAttributes(Name name,
0N/A String[] attrIds,
0N/A Continuation cont)
0N/A throws NamingException {
0N/A
0N/A cont.setSuccess();
0N/A try {
0N/A DnsName fqdn = fullyQualify(name);
0N/A CT[] cts = attrIdsToClassesAndTypes(attrIds);
0N/A CT ct = getClassAndTypeToQuery(cts);
0N/A ResourceRecords rrs =
0N/A getResolver().query(fqdn, ct.rrclass, ct.rrtype,
0N/A recursion, authoritative);
0N/A return rrsToAttrs(rrs, cts);
0N/A
0N/A } catch (NamingException e) {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(e);
0N/A }
0N/A }
0N/A
0N/A public void c_modifyAttributes(Name name,
0N/A int mod_op,
0N/A Attributes attrs,
0N/A Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public void c_modifyAttributes(Name name,
0N/A ModificationItem[] mods,
0N/A Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public NamingEnumeration c_search(Name name,
0N/A Attributes matchingAttributes,
0N/A String[] attributesToReturn,
0N/A Continuation cont)
0N/A throws NamingException {
0N/A throw new OperationNotSupportedException();
0N/A }
0N/A
0N/A public NamingEnumeration c_search(Name name,
0N/A String filter,
0N/A SearchControls cons,
0N/A Continuation cont)
0N/A throws NamingException {
0N/A throw new OperationNotSupportedException();
0N/A }
0N/A
0N/A public NamingEnumeration c_search(Name name,
0N/A String filterExpr,
0N/A Object[] filterArgs,
0N/A SearchControls cons,
0N/A Continuation cont)
0N/A throws NamingException {
0N/A throw new OperationNotSupportedException();
0N/A }
0N/A
0N/A public DirContext c_getSchema(Name name, Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A public DirContext c_getSchemaClassDefinition(Name name, Continuation cont)
0N/A throws NamingException {
0N/A cont.setError(this, name);
0N/A throw cont.fillInException(
0N/A new OperationNotSupportedException());
0N/A }
0N/A
0N/A
0N/A //---------- Name-related operations
0N/A
0N/A public String getNameInNamespace() {
0N/A return domain.toString();
0N/A }
0N/A
0N/A public Name composeName(Name name, Name prefix) throws NamingException {
0N/A Name result;
0N/A
0N/A // Any name that's not a CompositeName is assumed to be a DNS
0N/A // compound name. Convert each to a DnsName for syntax checking.
0N/A if (!(prefix instanceof DnsName || prefix instanceof CompositeName)) {
0N/A prefix = (new DnsName()).addAll(prefix);
0N/A }
0N/A if (!(name instanceof DnsName || name instanceof CompositeName)) {
0N/A name = (new DnsName()).addAll(name);
0N/A }
0N/A
0N/A // Each of prefix and name is now either a DnsName or a CompositeName.
0N/A
0N/A // If we have two DnsNames, simply join them together.
0N/A if ((prefix instanceof DnsName) && (name instanceof DnsName)) {
0N/A result = (DnsName) (prefix.clone());
0N/A result.addAll(name);
0N/A return new CompositeName().add(result.toString());
0N/A }
0N/A
0N/A // Wrap compound names in composite names.
0N/A Name prefixC = (prefix instanceof CompositeName)
0N/A ? prefix
0N/A : new CompositeName().add(prefix.toString());
0N/A Name nameC = (name instanceof CompositeName)
0N/A ? name
0N/A : new CompositeName().add(name.toString());
0N/A int prefixLast = prefixC.size() - 1;
0N/A
0N/A // Let toolkit do the work at namespace boundaries.
0N/A if (nameC.isEmpty() || nameC.get(0).equals("") ||
0N/A prefixC.isEmpty() || prefixC.get(prefixLast).equals("")) {
0N/A return super.composeName(nameC, prefixC);
0N/A }
0N/A
0N/A result = (prefix == prefixC)
0N/A ? (CompositeName) prefixC.clone()
0N/A : prefixC; // prefixC is already a clone
0N/A result.addAll(nameC);
0N/A
0N/A if (parentIsDns) {
0N/A DnsName dnsComp = (prefix instanceof DnsName)
0N/A ? (DnsName) prefix.clone()
0N/A : new DnsName(prefixC.get(prefixLast));
0N/A dnsComp.addAll((name instanceof DnsName)
0N/A ? name
0N/A : new DnsName(nameC.get(0)));
0N/A result.remove(prefixLast + 1);
0N/A result.remove(prefixLast);
0N/A result.add(prefixLast, dnsComp.toString());
0N/A }
0N/A return result;
0N/A }
0N/A
0N/A
0N/A //---------- Helper methods
0N/A
0N/A /*
0N/A * Resolver is not created until needed, to allow time for updates
0N/A * to the environment.
0N/A */
0N/A private synchronized Resolver getResolver() throws NamingException {
0N/A if (resolver == null) {
0N/A resolver = new Resolver(servers, timeout, retries);
0N/A }
0N/A return resolver;
0N/A }
0N/A
0N/A /*
0N/A * Returns the fully-qualified domain name of a name given
0N/A * relative to this context. Result includes a root label (an
0N/A * empty component at position 0).
0N/A */
0N/A DnsName fullyQualify(Name name) throws NamingException {
0N/A if (name.isEmpty()) {
0N/A return domain;
0N/A }
0N/A DnsName dnsName = (name instanceof CompositeName)
0N/A ? new DnsName(name.get(0)) // parse name
0N/A : (DnsName) (new DnsName()).addAll(name); // clone & check syntax
0N/A
0N/A if (dnsName.hasRootLabel()) {
0N/A // Be overly generous and allow root label if we're in root domain.
0N/A if (domain.size() == 1) {
0N/A return dnsName;
0N/A } else {
0N/A throw new InvalidNameException(
0N/A "DNS name " + dnsName + " not relative to " + domain);
0N/A }
0N/A }
0N/A return (DnsName) dnsName.addAll(0, domain);
0N/A }
0N/A
0N/A /*
0N/A * Converts resource records to an attribute set. Only resource
0N/A * records in the answer section are used, and only those that
0N/A * match the classes and types in cts (see classAndTypeMatch()
0N/A * for matching rules).
0N/A */
0N/A private static Attributes rrsToAttrs(ResourceRecords rrs, CT[] cts) {
0N/A
0N/A BasicAttributes attrs = new BasicAttributes(true);
0N/A
0N/A for (int i = 0; i < rrs.answer.size(); i++) {
0N/A ResourceRecord rr = (ResourceRecord) rrs.answer.elementAt(i);
0N/A int rrtype = rr.getType();
0N/A int rrclass = rr.getRrclass();
0N/A
0N/A if (!classAndTypeMatch(rrclass, rrtype, cts)) {
0N/A continue;
0N/A }
0N/A
0N/A String attrId = toAttrId(rrclass, rrtype);
0N/A Attribute attr = attrs.get(attrId);
0N/A if (attr == null) {
0N/A attr = new BasicAttribute(attrId);
0N/A attrs.put(attr);
0N/A }
0N/A attr.add(rr.getRdata());
0N/A }
0N/A return attrs;
0N/A }
0N/A
0N/A /*
0N/A * Returns true if rrclass and rrtype match some element of cts.
0N/A * A match occurs if corresponding classes and types are equal,
0N/A * or if the array value is ANY. If cts is null, then any class
0N/A * and type match.
0N/A */
0N/A private static boolean classAndTypeMatch(int rrclass, int rrtype,
0N/A CT[] cts) {
0N/A if (cts == null) {
0N/A return true;
0N/A }
0N/A for (int i = 0; i < cts.length; i++) {
0N/A CT ct = cts[i];
0N/A boolean classMatch = (ct.rrclass == ANY) ||
0N/A (ct.rrclass == rrclass);
0N/A boolean typeMatch = (ct.rrtype == ANY) ||
0N/A (ct.rrtype == rrtype);
0N/A if (classMatch && typeMatch) {
0N/A return true;
0N/A }
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A /*
0N/A * Returns the attribute ID for a resource record given its class
0N/A * and type. If the record is in the internet class, the
0N/A * corresponding attribute ID is the record's type name (or the
0N/A * integer type value if the name is not known). If the record is
0N/A * not in the internet class, the class name (or integer class
0N/A * value) is prepended to the attribute ID, separated by a space.
0N/A *
0N/A * A class or type value of ANY represents an indeterminate class
0N/A * or type, and is represented within the attribute ID by "*".
0N/A * For example, the attribute ID "IN *" represents
0N/A * any type in the internet class, and "* NS" represents an NS
0N/A * record of any class.
0N/A */
0N/A private static String toAttrId(int rrclass, int rrtype) {
0N/A String attrId = ResourceRecord.getTypeName(rrtype);
0N/A if (rrclass != ResourceRecord.CLASS_INTERNET) {
0N/A attrId = ResourceRecord.getRrclassName(rrclass) + " " + attrId;
0N/A }
0N/A return attrId;
0N/A }
0N/A
0N/A /*
0N/A * Returns the class and type values corresponding to an attribute
0N/A * ID. An indeterminate class or type is represented by ANY. See
0N/A * toAttrId() for the format of attribute IDs.
0N/A *
0N/A * @throws InvalidAttributeIdentifierException
0N/A * if class or type is unknown
0N/A */
0N/A private static CT fromAttrId(String attrId)
0N/A throws InvalidAttributeIdentifierException {
0N/A
0N/A if (attrId.equals("")) {
0N/A throw new InvalidAttributeIdentifierException(
0N/A "Attribute ID cannot be empty");
0N/A }
0N/A int rrclass;
0N/A int rrtype;
0N/A int space = attrId.indexOf(' ');
0N/A
0N/A // class
0N/A if (space < 0) {
0N/A rrclass = ResourceRecord.CLASS_INTERNET;
0N/A } else {
0N/A String className = attrId.substring(0, space);
0N/A rrclass = ResourceRecord.getRrclass(className);
0N/A if (rrclass < 0) {
0N/A throw new InvalidAttributeIdentifierException(
0N/A "Unknown resource record class '" + className + '\'');
0N/A }
0N/A }
0N/A
0N/A // type
0N/A String typeName = attrId.substring(space + 1);
0N/A rrtype = ResourceRecord.getType(typeName);
0N/A if (rrtype < 0) {
0N/A throw new InvalidAttributeIdentifierException(
0N/A "Unknown resource record type '" + typeName + '\'');
0N/A }
0N/A
0N/A return new CT(rrclass, rrtype);
0N/A }
0N/A
0N/A /*
0N/A * Returns an array of the classes and types corresponding to a
0N/A * set of attribute IDs. See toAttrId() for the format of
0N/A * attribute IDs, and classAndTypeMatch() for the format of the
0N/A * array returned.
0N/A */
0N/A private static CT[] attrIdsToClassesAndTypes(String[] attrIds)
0N/A throws InvalidAttributeIdentifierException {
0N/A if (attrIds == null) {
0N/A return null;
0N/A }
0N/A CT[] cts = new CT[attrIds.length];
0N/A
0N/A for (int i = 0; i < attrIds.length; i++) {
0N/A cts[i] = fromAttrId(attrIds[i]);
0N/A }
0N/A return cts;
0N/A }
0N/A
0N/A /*
0N/A * Returns the most restrictive resource record class and type
0N/A * that may be used to query for records matching cts.
0N/A * See classAndTypeMatch() for matching rules.
0N/A */
0N/A private static CT getClassAndTypeToQuery(CT[] cts) {
0N/A int rrclass;
0N/A int rrtype;
0N/A
0N/A if (cts == null) {
0N/A // Query all records.
0N/A rrclass = ANY;
0N/A rrtype = ANY;
0N/A } else if (cts.length == 0) {
0N/A // No records are requested, but we need to ask for something.
0N/A rrclass = ResourceRecord.CLASS_INTERNET;
0N/A rrtype = ANY;
0N/A } else {
0N/A rrclass = cts[0].rrclass;
0N/A rrtype = cts[0].rrtype;
0N/A for (int i = 1; i < cts.length; i++) {
0N/A if (rrclass != cts[i].rrclass) {
0N/A rrclass = ANY;
0N/A }
0N/A if (rrtype != cts[i].rrtype) {
0N/A rrtype = ANY;
0N/A }
0N/A }
0N/A }
0N/A return new CT(rrclass, rrtype);
0N/A }
0N/A
0N/A
0N/A //---------- Support for list operations
0N/A
0N/A /*
0N/A * Synchronization notes:
0N/A *
0N/A * Any access to zoneTree that walks the tree, whether it modifies
0N/A * the tree or not, is synchronized on zoneTree.
0N/A * [%%% Note: a read/write lock would allow increased concurrency.]
0N/A * The depth of a ZoneNode can thereafter be accessed without
0N/A * further synchronization. Access to other fields and methods
0N/A * should be synchronized on the node itself.
0N/A *
0N/A * A zone's contents is a NameNode tree that, once created, is never
0N/A * modified. The only synchronization needed is to ensure that it
0N/A * gets flushed into shared memory after being created, which is
0N/A * accomplished by ZoneNode.populate(). The contents are accessed
0N/A * via a soft reference, so a ZoneNode may be seen to be populated
0N/A * one moment and unpopulated the next.
0N/A */
0N/A
0N/A /*
0N/A * Returns the node in the zone tree corresponding to a
0N/A * fully-qualified domain name. If the desired portion of the
0N/A * tree has not yet been populated or has been outdated, a zone
0N/A * transfer is done to populate the tree.
0N/A */
0N/A private NameNode getNameNode(DnsName fqdn) throws NamingException {
0N/A dprint("getNameNode(" + fqdn + ")");
0N/A
0N/A // Find deepest related zone in zone tree.
0N/A ZoneNode znode;
0N/A DnsName zone;
0N/A synchronized (zoneTree) {
0N/A znode = zoneTree.getDeepestPopulated(fqdn);
0N/A }
0N/A dprint("Deepest related zone in zone tree: " +
0N/A ((znode != null) ? znode.getLabel() : "[none]"));
0N/A
0N/A NameNode topOfZone;
0N/A NameNode nnode;
0N/A
0N/A if (znode != null) {
0N/A synchronized (znode) {
0N/A topOfZone = znode.getContents();
0N/A }
0N/A // If fqdn is in znode's zone, is not at a zone cut, and
0N/A // is current, we're done.
0N/A if (topOfZone != null) {
0N/A nnode = topOfZone.get(fqdn, znode.depth() + 1); // +1 for root
0N/A
0N/A if ((nnode != null) && !nnode.isZoneCut()) {
0N/A dprint("Found node " + fqdn + " in zone tree");
0N/A zone = (DnsName)
0N/A fqdn.getPrefix(znode.depth() + 1); // +1 for root
0N/A boolean current = isZoneCurrent(znode, zone);
0N/A boolean restart = false;
0N/A
0N/A synchronized (znode) {
0N/A if (topOfZone != znode.getContents()) {
0N/A // Zone was modified while we were examining it.
0N/A // All bets are off.
0N/A restart = true;
0N/A } else if (!current) {
0N/A znode.depopulate();
0N/A } else {
0N/A return nnode; // cache hit!
0N/A }
0N/A }
0N/A dprint("Zone not current; discarding node");
0N/A if (restart) {
0N/A return getNameNode(fqdn);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A // Cache miss... do it the expensive way.
0N/A dprint("Adding node " + fqdn + " to zone tree");
0N/A
0N/A // Find fqdn's zone and add it to the tree.
0N/A zone = getResolver().findZoneName(fqdn, ResourceRecord.CLASS_INTERNET,
0N/A recursion);
0N/A dprint("Node's zone is " + zone);
0N/A synchronized (zoneTree) {
0N/A znode = (ZoneNode) zoneTree.add(zone, 1); // "1" to skip root
0N/A }
0N/A
0N/A // If znode is now populated we know -- because the first half of
0N/A // getNodeName() didn't find it -- that it was populated by another
0N/A // thread during this method call. Assume then that it's current.
0N/A
0N/A synchronized (znode) {
0N/A topOfZone = znode.isPopulated()
0N/A ? znode.getContents()
0N/A : populateZone(znode, zone);
0N/A }
0N/A // Desired node should now be in znode's populated zone. Find it.
0N/A nnode = topOfZone.get(fqdn, zone.size());
0N/A if (nnode == null) {
0N/A throw new ConfigurationException(
0N/A "DNS error: node not found in its own zone");
0N/A }
0N/A dprint("Found node in newly-populated zone");
0N/A return nnode;
0N/A }
0N/A
0N/A /*
0N/A * Does a zone transfer to [re]populate a zone in the zone tree.
0N/A * Returns the zone's new contents.
0N/A */
0N/A private NameNode populateZone(ZoneNode znode, DnsName zone)
0N/A throws NamingException {
0N/A dprint("Populating zone " + zone);
0N/A // assert Thread.holdsLock(znode);
0N/A ResourceRecords rrs =
0N/A getResolver().queryZone(zone,
0N/A ResourceRecord.CLASS_INTERNET, recursion);
0N/A dprint("zone xfer complete: " + rrs.answer.size() + " records");
0N/A return znode.populate(zone, rrs);
0N/A }
0N/A
0N/A /*
0N/A * Determine if a ZoneNode's data is current.
0N/A * We base this on a comparison between the cached serial
0N/A * number and the latest SOA record.
0N/A *
0N/A * If there is no SOA record, znode is not (or is no longer) a zone:
0N/A * depopulate znode and return false.
0N/A *
0N/A * Since this method may perform a network operation, it is best
0N/A * to call it with znode unlocked. Caller must then note that the
0N/A * result may be outdated by the time this method returns.
0N/A */
0N/A private boolean isZoneCurrent(ZoneNode znode, DnsName zone)
0N/A throws NamingException {
0N/A // former version: return !znode.isExpired();
0N/A
0N/A if (!znode.isPopulated()) {
0N/A return false;
0N/A }
0N/A ResourceRecord soa =
0N/A getResolver().findSoa(zone, ResourceRecord.CLASS_INTERNET,
0N/A recursion);
0N/A synchronized (znode) {
0N/A if (soa == null) {
0N/A znode.depopulate();
0N/A }
0N/A return (znode.isPopulated() &&
0N/A znode.compareSerialNumberTo(soa) >= 0);
0N/A }
0N/A }
0N/A
0N/A
0N/A //---------- Debugging
0N/A
1524N/A private static final boolean debug = false;
0N/A
0N/A private static final void dprint(String msg) {
0N/A if (debug) {
0N/A System.err.println("** " + msg);
0N/A }
0N/A }
0N/A}
0N/A
0N/A
0N/A//----------
0N/A
0N/A/*
0N/A * A pairing of a resource record class and a resource record type.
0N/A * A value of ANY in either field represents an indeterminate value.
0N/A */
0N/Aclass CT {
0N/A int rrclass;
0N/A int rrtype;
0N/A
0N/A CT(int rrclass, int rrtype) {
0N/A this.rrclass = rrclass;
0N/A this.rrtype = rrtype;
0N/A }
0N/A}
0N/A
0N/A
0N/A//----------
0N/A
0N/A/*
0N/A * An enumeration of name/classname pairs.
0N/A *
0N/A * Nodes that have children or that are zone cuts are returned with
0N/A * classname DirContext. Other nodes are returned with classname
0N/A * Object even though they are DirContexts as well, since this might
0N/A * make the namespace easier to browse.
0N/A */
0N/Aclass NameClassPairEnumeration implements NamingEnumeration {
0N/A
0N/A protected Enumeration nodes; // nodes to be enumerated, or null if none
0N/A protected DnsContext ctx; // context being enumerated
0N/A
0N/A NameClassPairEnumeration(DnsContext ctx, Hashtable nodes) {
0N/A this.ctx = ctx;
0N/A this.nodes = (nodes != null)
0N/A ? nodes.elements()
0N/A : null;
0N/A }
0N/A
0N/A /*
1538N/A * ctx will be set to null when no longer needed by the enumeration.
0N/A */
1538N/A public void close() {
0N/A nodes = null;
1538N/A ctx = null;
0N/A }
0N/A
0N/A public boolean hasMore() {
0N/A boolean more = ((nodes != null) && nodes.hasMoreElements());
0N/A if (!more) {
0N/A close();
0N/A }
0N/A return more;
0N/A }
0N/A
0N/A public Object next() throws NamingException {
0N/A if (!hasMore()) {
0N/A throw new java.util.NoSuchElementException();
0N/A }
0N/A NameNode nnode = (NameNode) nodes.nextElement();
0N/A String className = (nnode.isZoneCut() ||
0N/A (nnode.getChildren() != null))
0N/A ? "javax.naming.directory.DirContext"
0N/A : "java.lang.Object";
0N/A
0N/A String label = nnode.getLabel();
0N/A Name compName = (new DnsName()).add(label);
0N/A Name cname = (new CompositeName()).add(compName.toString());
0N/A
0N/A NameClassPair ncp = new NameClassPair(cname.toString(), className);
0N/A ncp.setNameInNamespace(ctx.fullyQualify(cname).toString());
0N/A return ncp;
0N/A }
0N/A
0N/A public boolean hasMoreElements() {
0N/A return hasMore();
0N/A }
0N/A
0N/A public Object nextElement() {
0N/A try {
0N/A return next();
0N/A } catch (NamingException e) {
0N/A throw (new java.util.NoSuchElementException(
0N/A "javax.naming.NamingException was thrown: " +
0N/A e.getMessage()));
0N/A }
0N/A }
0N/A}
0N/A
0N/A/*
0N/A * An enumeration of Bindings.
0N/A */
0N/Aclass BindingEnumeration extends NameClassPairEnumeration {
0N/A
0N/A BindingEnumeration(DnsContext ctx, Hashtable nodes) {
0N/A super(ctx, nodes);
0N/A }
0N/A
0N/A // Finalizer not needed since it's safe to leave ctx unclosed.
0N/A// protected void finalize() {
0N/A// close();
0N/A// }
0N/A
0N/A public Object next() throws NamingException {
0N/A if (!hasMore()) {
0N/A throw (new java.util.NoSuchElementException());
0N/A }
0N/A NameNode nnode = (NameNode) nodes.nextElement();
0N/A
0N/A String label = nnode.getLabel();
0N/A Name compName = (new DnsName()).add(label);
0N/A String compNameStr = compName.toString();
0N/A Name cname = (new CompositeName()).add(compNameStr);
0N/A String cnameStr = cname.toString();
0N/A
0N/A DnsName fqdn = ctx.fullyQualify(compName);
0N/A
0N/A // Clone ctx to create the child context.
0N/A DnsContext child = new DnsContext(ctx, fqdn);
0N/A
0N/A try {
0N/A Object obj = DirectoryManager.getObjectInstance(
0N/A child, cname, ctx, child.environment, null);
0N/A Binding binding = new Binding(cnameStr, obj);
0N/A binding.setNameInNamespace(ctx.fullyQualify(cname).toString());
0N/A return binding;
0N/A } catch (Exception e) {
0N/A NamingException ne = new NamingException(
0N/A "Problem generating object using object factory");
0N/A ne.setRootCause(e);
0N/A throw ne;
0N/A }
0N/A }
0N/A}