Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/strangeberry/rendezvous/ServiceInfo.java


1   // Copyright (C) 2002  Strangeberry Inc.
2   // @(#)ServiceInfo.java, 1.21, 11/29/2002
3   //
4   // This library is free software; you can redistribute it and/or
5   // modify it under the terms of the GNU Lesser General Public
6   // License as published by the Free Software Foundation; either
7   // version 2.1 of the License, or (at your option) any later version.
8   // 
9   // This library is distributed in the hope that it will be useful,
10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  // Lesser General Public License for more details.
13  // 
14  // You should have received a copy of the GNU Lesser General Public
15  // License along with this library; if not, write to the Free Software
16  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  
18  package com.strangeberry.rendezvous;
19  
20  import java.io.*;
21  import java.net.*;
22  import java.util.*;
23  
24  /**
25   * Rendezvous service information.
26   *
27   * @author  Arthur van Hoff
28   * @version   1.21, 11/29/2002
29   */
30  public class ServiceInfo extends Rendezvous.Listener
31  {
32      public final static byte[] NO_VALUE = new byte[0];
33      
34      String type;
35      String name;
36      String server;
37      int port;
38      int weight;
39      int priority;
40      byte text[];
41      Hashtable props;
42      InetAddress addr;
43  
44      /**
45       * Construct a service description for registrating with Rendezvous.
46       * @param type fully qualified service type name
47       * @param name fully qualified service name
48       * @param addr the address to which the service is bound
49       * @param port the local port on which the service runs
50       * @param weight weight of the service
51       * @param priority priority of the service
52       * @param text string describing the service
53       */
54      public ServiceInfo(String type, String name, InetAddress addr, int port, int weight, int priority, String text)
55      {
56    this(type, name, addr, port, weight, priority, (byte[])null);
57    try {
58        ByteArrayOutputStream out = new ByteArrayOutputStream(text.length());
59        writeUTF(out, text);
60        this.text = out.toByteArray();
61    } catch (IOException e) {
62        throw new RuntimeException("unexpected exception: " + e);
63    }
64      }
65  
66      /**
67       * Construct a service description for registrating with Rendezvous. The properties hashtable must
68       * map property names to either Strings or byte arrays describing the property values.
69       * @param type fully qualified service type name
70       * @param name fully qualified service name
71       * @param addr the address to which the service is bound
72       * @param port the local port on which the service runs
73       * @param weight weight of the service
74       * @param priority priority of the service
75       * @param props properties describing the service
76       */
77      public ServiceInfo(String type, String name, InetAddress addr, int port, int weight, int priority, Hashtable props)
78      {
79    this(type, name, addr, port, weight, priority, (byte [])null);
80    try {
81        ByteArrayOutputStream out = new ByteArrayOutputStream(256);
82        for (Enumeration e = props.keys() ; e.hasMoreElements() ;) {
83      String key = (String)e.nextElement();
84      Object val = props.get(key);
85      ByteArrayOutputStream out2 = new ByteArrayOutputStream(100);
86      writeUTF(out2, key);
87      if (val instanceof String) {
88          out2.write('=');
89          writeUTF(out2, (String)val);
90      } else if (val instanceof byte[]) {
91          out2.write('=');
92          byte[] bval = (byte[])val;
93          out2.write(bval, 0, bval.length);
94      } else if (val != NO_VALUE) {
95          throw new IllegalArgumentException("invalid property value: " + val);
96      }
97      byte data[] = out2.toByteArray();
98      out.write(data.length);
99      out.write(data, 0, data.length);
100       }
101       this.text = out.toByteArray();
102   } catch (IOException e) {
103       throw new RuntimeException("unexpected exception: " + e);
104   }
105     }
106 
107     /**
108      * Construct a service description for registrating with Rendezvous.
109      * @param type fully qualified service type name
110      * @param name fully qualified service name
111      * @param addr the address to which the service is bound
112      * @param port the local port on which the service runs
113      * @param weight weight of the service
114      * @param priority priority of the service
115      * @param text bytes describing the service
116      */
117     public ServiceInfo(String type, String name, InetAddress addr, int port, int weight, int priority, byte text[])
118     {
119   this.type = type;
120   this.name = name;
121   this.port = port;
122   this.weight = weight;
123   this.priority = priority;
124   this.server = name;
125   this.text = text;
126   this.addr = addr;
127     }
128 
129     /**
130      * Construct a serive record during service discovery.
131      */
132     ServiceInfo(String type, String name)
133     {
134   this.type = type;
135   this.name = name;
136     }
137 
138     /**
139      * Fully qualified service type name, such as <code>_http._tcp.local.</code>.
140      */
141     public String getType()
142     {
143   return type;
144     }
145     /**
146      * Service name, such as <code>foobar</code>.
147      */
148     public String getName()
149     {
150   if ((type != null) && name.endsWith("." + type)) {
151       return name.substring(0, name.length() - (type.length() + 1));
152   }
153   return name;
154     }
155     /**
156      * Get the host address of the service (ie X.X.X.X).
157      */
158     public String getAddress()
159     {
160   byte data[] = addr.getAddress();
161   return (data[0] & 0xFF) + "." + (data[1] & 0xFF) + "." + (data[2] & 0xFF) + "." + (data[3] & 0xFF);
162     }
163     /**
164      * Get the port for the service.
165      */
166     public int getPort()
167     {
168   return port;
169     }
170     /**
171      * Get the priority of the service.
172      */
173     public int getPriority()
174     {
175   return priority;
176     }
177     /**
178      * Get the weight of the service.
179      */
180     public int getWeight()
181     {
182   return weight;
183     }
184 
185     /**
186      * Get the text for the serivce as raw bytes.
187      */
188     public byte[] getTextBytes()
189     {
190   return text;
191     }
192 
193     /**
194      * Get the text for the service. This will interpret the text bytes
195      * as a UTF8 encoded string. Will return null if the bytes are not
196      * a valid UTF8 encoded string.
197      */
198     public String getTextString()
199     {
200   if ((text == null) || (text.length == 0) || ((text.length == 1) && (text[0] == 0))) {
201       return null;
202   }
203   return readUTF(text, 0, text.length);
204     }
205 
206     /**
207      * Get a property of the service. This involves decoding the
208      * text bytes into a property list. Returns null if the property
209      * is not found or the text data could not be decoded correctly.
210      */
211     public synchronized byte[] getPropertyBytes(String name)
212     {
213   return (byte [])getProperties().get(name);
214     }
215 
216     /**
217      * Get a property of the service. This involves decoding the
218      * text bytes into a property list. Returns null if the property
219      * is not found, the text data could not be decoded correctly, or
220      * the resulting bytes are not a valid UTF8 string.
221      */
222     public synchronized String getPropertyString(String name)
223     {
224   byte data[] = (byte [])getProperties().get(name);
225   if (data == null) {
226       return null;
227   }
228   if (data == NO_VALUE) {
229       return "true";
230   }
231   return readUTF(data, 0, data.length);
232     }
233 
234     /**
235      * Enumeration of the property names.
236      */
237     public Enumeration getPropertyNames()
238     {
239   Hashtable props = getProperties();
240   return (props != null) ? props.keys() : new Vector().elements();
241     }
242 
243     /**
244      * Write a UTF string with a length to a stream.
245      */
246     void writeUTF(OutputStream out, String str) throws IOException
247     {
248   for (int i = 0, len = str.length() ; i < len ; i++) {
249       int c = str.charAt(i);
250       if ((c >= 0x0001) && (c <= 0x007F)) {
251     out.write(c);
252       } else if (c > 0x07FF) {
253     out.write(0xE0 | ((c >> 12) & 0x0F));
254     out.write(0x80 | ((c >>  6) & 0x3F));
255     out.write(0x80 | ((c >>  0) & 0x3F));
256       } else {
257     out.write(0xC0 | ((c >>  6) & 0x1F));
258     out.write(0x80 | ((c >>  0) & 0x3F));
259       }
260   }
261     }
262 
263     /**
264      * Read data bytes as a UTF stream.
265      */
266     String readUTF(byte data[], int off, int len)
267     {
268   StringBuffer buf = new StringBuffer();
269   for (int end = off + len ; off < end ; ) {
270       int ch = data[off++] & 0xFF;
271       switch (ch >> 4) {
272         case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
273     // 0xxxxxxx
274     break;
275         case 12: case 13:
276     if (off >= len) {
277         return null;
278     }
279     // 110x xxxx   10xx xxxx
280     ch = ((ch & 0x1F) << 6) | (data[off++] & 0x3F);
281     break;
282         case 14:
283     if (off + 2 >= len) {
284         return null;
285     }
286     // 1110 xxxx  10xx xxxx  10xx xxxx
287     ch = ((ch & 0x0f) << 12) | ((data[off++] & 0x3F) << 6) | (data[off++] & 0x3F);
288     break;
289         default:
290     if (off + 1 >= len) {
291         return null;
292     }
293     // 10xx xxxx,  1111 xxxx
294     ch = ((ch & 0x3F) << 4) | (data[off++] & 0x0f);
295     break;
296       }
297       buf.append((char)ch);
298   }
299   return buf.toString();
300     }
301 
302     synchronized Hashtable getProperties()
303     {
304   if ((props == null) && (text != null)) {
305       Hashtable props = new Hashtable();
306       int off = 0;
307       while (off < text.length) {
308     // length of the next key value pair
309     int len = text[off++] & 0xFF;
310     if ((len == 0) || (off + len > text.length)) {
311         props.clear();
312         break;
313     }
314     // look for the '='
315     int i = 0;
316     for (; (i < len) && (text[off + i] != '=') ; i++);
317 
318     // get the property name
319     String name = readUTF(text, off, i);
320     if (name == null) {
321         props.clear();
322         break;
323     }
324     if (i == len) {
325         props.put(name, NO_VALUE);
326     } else {
327         byte value[] = new byte[len - i];
328         System.arraycopy(text, off + i, value, 0, len - i);
329         props.put(name, value);
330         off += len;
331     }
332       }
333   }
334   return props;
335     }
336 
337     /**
338      * Get the ip address of the service.
339      */
340     int getIPAddress()
341     {
342   byte data[] = addr.getAddress();
343   return ((data[0] & 0xFF) << 24) | ((data[1] & 0xFF) << 16) | ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
344     }
345 
346     /**
347      * Rendezvous callback to update a DNS record.
348      */
349     void updateRecord(Rendezvous rendezvous, long now, DNSRecord rec)
350     {
351   if ((rec != null) && !rec.isExpired(now)) {
352       switch (rec.type) {
353         case TYPE_A:
354     if (rec.name.equals(server)) {
355         addr = ((DNSRecord.Address)rec).getAddress();
356     }
357     break;
358         case TYPE_SRV:
359     if (rec.name.equals(name)) {
360         DNSRecord.Service srv = (DNSRecord.Service)rec;
361         server = srv.server;
362         port = srv.port;
363         weight = srv.weight;
364         priority = srv.priority;
365         addr = null;
366         updateRecord(rendezvous, now, (DNSRecord)rendezvous.cache.get(server, TYPE_A, CLASS_IN));
367     }
368     break;
369         case TYPE_TXT:
370     if (rec.name.equals(name)) {
371         DNSRecord.Text txt = (DNSRecord.Text)rec;
372         text = txt.text;
373     }
374     break;
375       }
376   }
377     }
378 
379     /**
380      * Update the server information from the cache, send out
381      * repeated DNS queries for updated information.
382      */
383     boolean request(Rendezvous rendezvous, long timeout)
384     {
385   long now = System.currentTimeMillis();
386   int delay = 200;
387   long next = now + delay;
388   long last = now + timeout;
389   try {
390       rendezvous.addListener(this, new DNSQuestion(name, TYPE_ANY, CLASS_IN));
391       while (server == null || addr == null || text == null) {
392     // check if timeout was reached
393     if (last <= now) {
394         return false;
395     }
396     // check if we need to send out another request
397     if (next <= now) {
398         DNSOutgoing out = new DNSOutgoing(FLAGS_QR_QUERY);
399         out.addQuestion(new DNSQuestion(name, TYPE_SRV, CLASS_IN));
400         out.addQuestion(new DNSQuestion(name, TYPE_TXT, CLASS_IN));
401         if (server != null) {
402       out.addQuestion(new DNSQuestion(server, TYPE_A, CLASS_IN));
403         }
404         out.addAnswer((DNSRecord)rendezvous.cache.get(name, TYPE_SRV, CLASS_IN), now);
405         out.addAnswer((DNSRecord)rendezvous.cache.get(name, TYPE_TXT, CLASS_IN), now);
406         if (server != null) {
407       out.addAnswer((DNSRecord)rendezvous.cache.get(server, TYPE_A, CLASS_IN), now);
408         }
409         rendezvous.send(out);
410 
411         next = now + delay;
412         delay = delay * 2;
413     }
414     // wait for an update or a timeout
415     synchronized (rendezvous) {
416         rendezvous.wait(Math.min(next, last) - now);
417     }
418     now = System.currentTimeMillis();
419       }
420       return true;
421   } catch (IOException e) {
422       return false;
423   } catch (InterruptedException e) {
424       return false;
425   } finally {
426       rendezvous.removeListener(this);
427   }
428     }
429 
430     public int hashCode()
431     {
432   return name.hashCode();
433     }
434 
435     public boolean equals(Object obj)
436     {
437   return (obj instanceof ServiceInfo) && name.equals(((ServiceInfo)obj).name);
438     }
439 
440     public String toString()
441     {
442   StringBuffer buf = new StringBuffer();
443   buf.append("service[");
444   buf.append(name);
445   buf.append(',');
446   buf.append(getAddress());
447   buf.append(':');
448   buf.append(port);
449   buf.append(',');
450   buf.append((text.length < 20) ? new String(text) : new String(text, 0, 17) + "...");
451   buf.append(']');
452   return buf.toString();
453     }
454 }