1 /*
   2  * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.dns;
  27 
  28 
  29 import java.lang.ref.SoftReference;
  30 import java.util.Date;
  31 import java.util.Vector;
  32 
  33 
  34 /**
  35  * ZoneNode extends NameNode to represent a tree of the zones in the
  36  * DNS namespace, along with any intermediate nodes between zones.
  37  * A ZoneNode that represents a zone may be "populated" with a
  38  * NameNode tree containing the zone's contents.
  39  *
  40  * <p> A populated zone's contents will be flagged as having expired after
  41  * the time specified by the minimum TTL value in the zone's SOA record.
  42  *
  43  * <p> Since zone cuts aren't directly modeled by a tree of ZoneNodes,
  44  * ZoneNode.isZoneCut() always returns false.
  45  *
  46  * <p> The synchronization strategy is documented in DnsContext.java.
  47  *
  48  * <p> The zone's contents are accessed via a soft reference, so its
  49  * heap space may be reclaimed when necessary.  The zone may be
  50  * repopulated later.
  51  *
  52  * @author Scott Seligman
  53  */
  54 
  55 
  56 class ZoneNode extends NameNode {
  57 
  58     private SoftReference<NameNode> contentsRef = null;   // the zone's namespace
  59     private long serialNumber = -1;     // the zone data's serial number
  60     private Date expiration = null;     // time when the zone's data expires
  61 
  62     ZoneNode(String label) {
  63         super(label);
  64     }
  65 
  66     protected NameNode newNameNode(String label) {
  67         return new ZoneNode(label);
  68     }
  69 
  70     /*
  71      * Clears the contents of this node.  If the node was flagged as
  72      * expired, it remains so.
  73      */
  74     synchronized void depopulate() {
  75         contentsRef = null;
  76         serialNumber = -1;
  77     }
  78 
  79     /*
  80      * Is this node currently populated?
  81      */
  82     synchronized boolean isPopulated() {
  83         return (getContents() != null);
  84     }
  85 
  86     /*
  87      * Returns the zone's contents, or null if the zone is not populated.
  88      */
  89     synchronized NameNode getContents() {
  90         return (contentsRef != null)
  91                 ? contentsRef.get()
  92                 : null;
  93     }
  94 
  95     /*
  96      * Has this zone's data expired?
  97      */
  98     synchronized boolean isExpired() {
  99         return ((expiration != null) && expiration.before(new Date()));
 100     }
 101 
 102     /*
 103      * Returns the deepest populated zone on the path specified by a
 104      * fully-qualified domain name, or null if there is no populated
 105      * zone on that path.  Note that a node may be depopulated after
 106      * being returned.
 107      */
 108     ZoneNode getDeepestPopulated(DnsName fqdn) {
 109         ZoneNode znode = this;
 110         ZoneNode popNode = isPopulated() ? this : null;
 111         for (int i = 1; i < fqdn.size(); i++) { //     "i=1" to skip root label
 112             znode = (ZoneNode) znode.get(fqdn.getKey(i));
 113             if (znode == null) {
 114                 break;
 115             } else if (znode.isPopulated()) {
 116                 popNode = znode;
 117             }
 118         }
 119         return popNode;
 120     }
 121 
 122     /*
 123      * Populates (or repopulates) a zone given its own fully-qualified
 124      * name and its resource records.  Returns the zone's new contents.
 125      */
 126     NameNode populate(DnsName zone, ResourceRecords rrs) {
 127         // assert zone.get(0).isEmpty();               // zone has root label
 128         // assert (zone.size() == (depth() + 1));       // +1 due to root label
 129 
 130         NameNode newContents = new NameNode(null);
 131 
 132         for (int i = 0; i < rrs.answer.size(); i++) {
 133             ResourceRecord rr = rrs.answer.elementAt(i);
 134             DnsName n = rr.getName();
 135 
 136             // Ignore resource records whose names aren't within the zone's
 137             // domain.  Also skip records of the zone's top node, since
 138             // the zone's root NameNode is already in place.
 139             if ((n.size() > zone.size()) && n.startsWith(zone)) {
 140                 NameNode nnode = newContents.add(n, zone.size());
 141                 if (rr.getType() == ResourceRecord.TYPE_NS) {
 142                     nnode.setZoneCut(true);
 143                 }
 144             }
 145         }
 146         // The zone's SOA record is the first record in the answer section.
 147         ResourceRecord soa = rrs.answer.firstElement();
 148         synchronized (this) {
 149             contentsRef = new SoftReference<NameNode>(newContents);
 150             serialNumber = getSerialNumber(soa);
 151             setExpiration(getMinimumTtl(soa));
 152             return newContents;
 153         }
 154     }
 155 
 156     /*
 157      * Set this zone's data to expire in {@code secsToExpiration} seconds.
 158      */
 159     private void setExpiration(long secsToExpiration) {
 160         expiration = new Date(System.currentTimeMillis() +
 161                               1000 * secsToExpiration);
 162     }
 163 
 164     /*
 165      * Returns an SOA record's minimum TTL field.
 166      */
 167     private static long getMinimumTtl(ResourceRecord soa) {
 168         String rdata = (String) soa.getRdata();
 169         int pos = rdata.lastIndexOf(' ') + 1;
 170         return Long.parseLong(rdata.substring(pos));
 171     }
 172 
 173     /*
 174      * Compares this zone's serial number with that of an SOA record.
 175      * Zone must be populated.
 176      * Returns a negative, zero, or positive integer as this zone's
 177      * serial number is less than, equal to, or greater than the SOA
 178      * record's.
 179      * See ResourceRecord.compareSerialNumbers() for a description of
 180      * serial number arithmetic.
 181      */
 182     int compareSerialNumberTo(ResourceRecord soa) {
 183         // assert isPopulated();
 184         return ResourceRecord.compareSerialNumbers(serialNumber,
 185                                                    getSerialNumber(soa));
 186     }
 187 
 188     /*
 189      * Returns an SOA record's serial number.
 190      */
 191     private static long getSerialNumber(ResourceRecord soa) {
 192         String rdata = (String) soa.getRdata();
 193 
 194         // An SOA record ends with:  serial refresh retry expire minimum.
 195         // Set "beg" to the space before serial, and "end" to the space after.
 196         // We go "backward" to avoid dealing with escaped spaces in names.
 197         int beg = rdata.length();
 198         int end = -1;
 199         for (int i = 0; i < 5; i++) {
 200             end = beg;
 201             beg = rdata.lastIndexOf(' ', end - 1);
 202         }
 203         return Long.parseLong(rdata.substring(beg + 1, end));
 204     }
 205 }