ServerDATable.java revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* ident "%Z%%M% %I% %E% SMI"
*
* Copyright (c) 1999 by Sun Microsystems, Inc.
* All rights reserved.
*
*/
// SCCS Status: %W% %G%
// ServerDATable.java: Abstract class for DA Table in the DA/SA server.
// Author: James Kempf
// Created On: Wed May 20 08:30:46 1998
// Last Modified By: James Kempf
// Last Modified On: Tue Mar 9 12:36:37 1999
// Update Count: 124
//
package com.sun.slp;
import java.util.*;
import java.net.*;
/**
* ServerDATable is an abstract class that provides the interface for DA
* and scope discovery, storage of DA information from incoming DAAdverts,
* and forwarding of registrations and deregistration to DAs having
* the same scopes. A variety of implementations are possible.
* The getServerDATable() method creates the right one from a subclass.
* We keep separate track of the superclass DA table and the server
* DA table so that these two classes can co-exist in the same JVM.
* Note that the code for forwarding registrations must keep track of
* only those registrations that were done on this host. It does this
* by saving the registrations as they come in. The forwarding code
* is optimized so that forwarding of a new message is fast, while
* forwarding of a message due to discovery of a new DA is somewhat
* slower. This helps assure that SA clients get good service.
*
* The ServerDATable also does active discovery for the SA server/DA,
* in a separate thread.
*
* @version %R%.%L% %D%
* @author James Kempf
*/
abstract class ServerDATable extends DATable {
// The active discovery object.
protected static ActiveDiscoverer activeDiscoverer = null;
// The table of regs to forward. Keys are the reg URL and locale, values
// are the SSrvReg objects.
protected Hashtable forwardRegs = new Hashtable();
// This acts as a guard protecting an non-initialized DA table:
// If the DA Table hasn't been populated by active discovery yet,
// other threads accessing the DA table will block on readyLock.
private static Object readyLock = new Object();
// Keeps track of which DAs support which SPIs. The actual mapping
// is DA InetAddress to LinkedList of SPI Strings. recordNewDA
// populates this.
protected Hashtable daSPIsHash = new Hashtable();
/**
* Get the right server DA table from the subclass.
*
* @return Table for handling DAs in the server.
*/
static ServerDATable getServerDATable()
throws ServiceLocationException {
ServerDATable table = null;
synchronized (readyLock) {
// Note that we are expecting this subclass. We will get a
// cast exception if somebody instantiated with a
// DATable subclass.
if (daTable != null) {
return (ServerDATable)daTable;
}
conf = SLPConfig.getSLPConfig();
// Call the superclass method to link it.
daTable = linkAndInstantiateFromProp();
table = (ServerDATable)daTable;
// Advertise for *all* scopes. This is because we need to
// be able to support clients that do user scoping.
Vector useScopes = new Vector();
activeDiscoverer =
new ActiveDiscoverer(Defaults.version,
table,
useScopes,
conf.getMulticastAddress());
activeDiscoverer.start();
} // readyLock
return table;
}
/**
* Record a new DA.
*
* @param URL The DAAdvert URL.
* @param scopes The scopes.
* @param version DA version number.
* @param attrs Attributes of DA.
* @param spis SPIs supported by DA
* @return True if recorded, false if not.
*/
abstract long
recordNewDA(ServiceURL url,
Vector scopes,
long timestamp,
int version,
Vector attrs,
String spis);
/**
* Return a hashtable in ServiceTable.findServices() format (e.g.
* URL's as keys, scopes as values) for DAs matching the query.
*
* @param query Query for DA attributes.
*/
abstract Hashtable returnMatchingDAs(String query)
throws ServiceLocationException;
/**
* Forward a registration or deregistration to all DAs that have matching
* scopes.
*
* @param msg Registration or deregistration message, server side.
* @param source The address of the source.
*/
void forwardSAMessage(SrvLocMsg msg, InetAddress source)
throws ServiceLocationException {
SrvLocHeader hdr = msg.getHeader();
// If the message is not from this host (on any interface)
// then don't forward it.
if (!conf.isLocalHostSource(source)) {
return;
}
// Record it so we can forward to a new DA.
if (msg instanceof SSrvReg || msg instanceof CSrvReg) {
ServiceURL url;
if (msg instanceof SSrvReg) {
url = ((SSrvReg)msg).URL;
} else {
url = ((CSrvReg)msg).URL;
}
String key = makeKey(url, hdr.locale); // need locale also...
forwardRegs.put(key, msg); // fresh doesn't matter.
} else {
SSrvDereg smsg = (SSrvDereg)msg;
// Only remove if tags are null. Otherwise, the updated record
// will be sought.
if (smsg.tags == null) {
String key = makeKey(smsg.URL, hdr.locale);
forwardRegs.remove(key);
}
}
// We only forward registrations to v2 DAs because we are
// acting as an SA server here. There is no requirement
// for v2 SAs to communicate with v1 DAs.
// Get a hashtable of DAs that match the scopes in the message.
Hashtable daScopeRec = findDAScopes(hdr.scopes);
// We are only concerned with the unicast key, since it contains
// the DAs to which forwarding is required.
Vector daRecs = (Vector)daScopeRec.get(UNICAST_KEY);
// If there are no daRecs, then simply return.
if (daRecs == null) {
return;
}
// Otherwise, forward the registration to all DAs in the vector.
int i, n = daRecs.size();
for (i = 0; i < n; i++) {
DARecord rec = (DARecord)daRecs.elementAt(i);
Vector daAddresses = rec.daAddresses;
int j, m = daAddresses.size();
for (j = 0; j < m; j++) {
InetAddress addr = (InetAddress)daAddresses.elementAt(j);
// Don't forward if it's the host from which the registration
// came. Otherwise, we're hosed.
if (!source.equals(addr)) {
forwardRegOrDereg(addr, msg);
}
}
}
}
// Make a key for the service agent message table.
private String makeKey(ServiceURL url, Locale locale) {
return url.toString() + "/" + locale.toString();
}
/**
* Handle an incoming DAAdvert. Presence must be recorded in the
* implementation specific server DA table and any registrations need
* to be forwarded if the boot timestamp is different from the
* old boot timestamp.
*
* @param advert Incoming DAAdvert.
*/
void handleAdvertIn(CDAAdvert advert) {
SrvLocHeader hdr = advert.getHeader();
// Remove if DA is going down.
if (advert.isGoingDown()) {
InetAddress addr = null;
try {
addr = InetAddress.getByName(advert.URL.getHost());
} catch (UnknownHostException ex) {
conf.writeLog("unknown_da_address",
new Object[] {advert.URL.getHost()});
return;
}
if (removeDA(addr, hdr.scopes)) {
if (conf.traceDATraffic()) {
conf.writeLog("sdat_delete_da",
new Object[] {
advert.URL,
hdr.scopes});
}
}
} else {
// verify the DAAdvert
if (advert.authBlock != null) {
try {
AuthBlock.verifyAll(advert.authBlock);
} catch (ServiceLocationException e) {
if (conf.traceDrop()) {
conf.writeLog("sdat_daadvert_vrfy_failed",
new Object[] {advert.URL});
}
return;
}
}
long timestamp =
recordNewDA(advert.URL,
hdr.scopes,
advert.timestamp,
hdr.version,
advert.attrs,
advert.spis);
// Don't forward if the advert was rejected, or if the
// old timestamp greater than or equal to the new timestamp.
// If the old timestamp is greater than or equal to the new,
// it means that we have already forwarded to this DA.
// IF the old timestamp is less, it means that
// the DA has crashed and come up again since we last saw
// it, so we may have missed forwarding something to it.
if (timestamp >= advert.timestamp) {
if (conf.traceDATraffic()) {
conf.writeLog("sdat_add_da_no_forward",
new Object[] {
advert.URL,
hdr.scopes,
new Long(timestamp)});
}
return;
}
if (conf.traceDATraffic()) {
conf.writeLog("sdat_add_da",
new Object[] {
advert.URL,
hdr.scopes,
new Long(advert.timestamp)});
}
// Forward existing registrations to the new advert.
forwardRegistrations(advert.URL, hdr.scopes,
advert.timestamp, hdr.version);
}
}
//
// Private methods.
//
private void
forwardRegistrations(ServiceURL url,
Vector scopes,
long timestamp,
int version) {
// Wait a random amount of time before forwarding.
try {
Thread.currentThread().sleep(conf.getRandomWait());
} catch (InterruptedException ex) {
}
// Get the registrations to forward.
Enumeration regs = forwardRegs.elements();
// Get the address of the DA.
InetAddress addr = null;
String host = url.getHost();
try {
addr = InetAddress.getByName(host);
} catch (UnknownHostException ex) {
if (conf.traceDrop() || conf.traceDATraffic()) {
conf.writeLog("sdat_drop_fwd",
new Object[] {
host});
}
return;
}
ServiceTable serviceTable = null;
try {
serviceTable = ServiceTable.getServiceTable();
} catch (ServiceLocationException ex) {
// By this time, we should have it.
}
// Forward the registrations. Keep track of any deleted elements.
Vector deleted = new Vector();
while (regs.hasMoreElements()) {
SrvLocMsg reg = (SrvLocMsg)regs.nextElement();
ServiceURL regurl;
if (reg instanceof SSrvReg) {
regurl = ((SSrvReg)reg).URL;
} else {
regurl = ((CSrvReg)reg).URL;
}
SrvLocHeader hdr = reg.getHeader();
// Get the record and modify the reg to reflect the
// record. We must do this because the SA may have
// changed the record since it was first registred
// and we do not keep track of the changes here.
ServiceStore.ServiceRecord rec =
serviceTable.getServiceRecord(regurl, hdr.locale);
// If the record is null, it means that the entry was
// aged out.
if (rec == null) {
deleted.addElement(reg);
} else {
// Check that the scopes match.
Vector sscopes = (Vector)hdr.scopes.clone();
DATable.filterScopes(sscopes, scopes, false);
if (sscopes.size() <= 0) {
continue;
}
if (reg instanceof SSrvReg) {
SSrvReg sreg = (SSrvReg)reg;
hdr.scopes = (Vector)hdr.scopes.clone();
sreg.attrList = (Vector)rec.getAttrList().clone();
sreg.URLSignature = rec.getURLSignature();
sreg.attrSignature = rec.getAttrSignature();
}
forwardRegOrDereg(addr, reg);
}
}
// Remove any deleted elements from the hashtable.
// We do this in a separate loop because enumerations
// aren't synchronized.
int i, n = deleted.size();
for (i = 0; i < n; i++) {
SrvLocMsg reg = (SrvLocMsg)deleted.elementAt(i);
SrvLocHeader hdr = reg.getHeader();
ServiceURL regurl;
if (reg instanceof SSrvReg) {
regurl = ((SSrvReg)reg).URL;
} else {
regurl = ((CSrvReg)reg).URL;
}
String key = makeKey(regurl, hdr.locale);
forwardRegs.remove(key);
}
}
// Forward the registration or deregistration to the URL.
private void forwardRegOrDereg(InetAddress addr, SrvLocMsg rqst) {
SrvLocHeader hdr = rqst.getHeader();
// Don't forward to myself! Otherwise, nasty recursion happens.
if (conf.isLocalHostSource(addr)) {
return;
}
// If security is on, only forward if this DA can verify the authblocks
if (conf.getHasSecurity()) {
LinkedList spis = (LinkedList)daSPIsHash.get(addr);
if (spis == null) {
// internal error; skip this DA to be safe
return;
}
Hashtable auths = null;
if (rqst instanceof SSrvReg) {
auths = ((SSrvReg)rqst).URLSignature;
} else if (rqst instanceof SSrvDereg) {
auths = ((SSrvDereg)rqst).URLSignature;
} else {
// shouldn't even be forwarding this!
return;
}
// If each authblock is equiv to at least one SPI, forward the reg
Enumeration abs = auths.elements();
while (abs.hasMoreElements()) {
AuthBlock ab = (AuthBlock)abs.nextElement();
// check each DA SPI
boolean daSPImatch = false;
for (int SPIi = 0; SPIi < spis.size(); SPIi++) {
if (AuthBlock.checkEquiv((String)spis.get(SPIi), ab)) {
daSPImatch = true;
break;
}
}
if (!daSPImatch) {
return;
}
}
}
if (conf.traceDATraffic()) {
conf.writeLog("sdat_forward",
new Object[] {
Integer.toHexString(hdr.xid),
addr});
}
// Send it via TCP. DAs should understand TCP, and it's reliable.
SrvLocMsg rply = null;
try {
// Construct the client side message, for outgoing.
if (rqst instanceof SSrvReg) {
SSrvReg rrqst = (SSrvReg)rqst;
CSrvReg msg = new CSrvReg(hdr.fresh,
hdr.locale,
rrqst.URL,
hdr.scopes,
rrqst.attrList,
rrqst.URLSignature,
rrqst.attrSignature);
rply = msg;
} else if (rqst instanceof SSrvDereg) {
SSrvDereg drqst = (SSrvDereg)rqst;
CSrvDereg msg = new CSrvDereg(hdr.locale,
drqst.URL,
hdr.scopes,
drqst.tags,
drqst.URLSignature);
rply = msg;
} else if (rqst instanceof CSrvReg) {
rply = rqst;
}
rply = Transact.transactTCPMsg(addr, rply, false);
} catch (ServiceLocationException ex) {
if (conf.traceDATraffic()) {
conf.writeLog("sdat_forward_exception",
new Object[] {
Integer.toHexString(hdr.xid),
addr,
new Integer(ex.getErrorCode()),
ex.getMessage()});
}
}
// Report any errors.
if (rply == null ||
rply.getErrorCode() != ServiceLocationException.OK) {
if (conf.traceDATraffic()) {
conf.writeLog("sdat_forward_err",
new Object[] {
Integer.toHexString(hdr.xid),
addr,
(rply == null ? "<null>":
Integer.toString(rply.getErrorCode()))});
}
}
}
}