/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 */ /* * Copyright 2001,2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * */ // ServiceStoreInMemory.java: An in-memory implementation // of the service store. // Author: James Kempf // Created On: Mon Oct 20 12:36:35 1997 // Last Modified By: James Kempf // Last Modified On: Tue Mar 2 15:32:23 1999 // Update Count: 472 // package com.sun.slp; import java.util.*; import java.io.*; /** * The ServiceStoreInMemory class implements the ServiceStore interface * on in-memory data structures. *
* * @author James Kempf */ class ServiceStoreInMemory extends Object implements ServiceStore { /** * The BVCollector interface allows various * data structures to collect stuff from the BtreeVector. * * @author James Kempf */ private interface BVCollector { // Set the return value. abstract void setReturn(ServiceRecordInMemory rec); } /** * The ParserBVCollector class implements a BtreeVector * collector for the parser. * * @author James Kempf */ private class ParserBVCollector extends Object implements BVCollector { Parser.ParserRecord prReturns = null; private Vector scopes = null; ParserBVCollector(Vector scopes) { this.scopes = scopes; } public void setReturn(ServiceRecordInMemory rec) { Hashtable services = prReturns.services; Hashtable signatures = prReturns.signatures; ServiceURL surl = rec.getServiceURL(); // Add if we don't already have it. if (services.get(surl) == null) { Vector s = (Vector)rec.getScopes().clone(); DATable.filterScopes(s, scopes, false); // Need to adjust lifetime to reflect the time to live. Don't // set the lifetime if it has already expired. long lifetime = (rec.getExpirationTime() - System.currentTimeMillis()) / 1000; if (lifetime > 0) { ServiceURL url = new ServiceURL(surl.toString(), (int)lifetime); services.put(surl, s); Hashtable sig = rec.getURLSignature(); if (sig != null) { signatures.put(url, sig); } } } } } /** * The AttributeBVCollector class implements a BtreeVector * collector for the collecting attribute values by type. * * @author James Kempf */ private class AttributeBVCollector extends Object implements BVCollector { private Hashtable alreadySeen = new Hashtable(); // records already seen. private Vector attrTags = null; // tags to match against records private Hashtable ht = new Hashtable(); // for collecting attributes. private Vector ret = null; // for returns. AttributeBVCollector(Vector attrTags, Vector ret) { this.attrTags = attrTags; this.ret = ret; } public void setReturn(ServiceRecordInMemory rec) { // If we've got it already, then don't add again. if (alreadySeen.get(rec) == null) { alreadySeen.put(rec, rec); try { findMatchingAttributes(rec, attrTags, ht, ret); } catch (ServiceLocationException ex) { Assert.slpassert(false, "ssim_attrbvc_botch", new Object[] {ex.getMessage()}); } } } } /** * The ScopeBVCollector class implements a BtreeVector * collector for the collecting records if scopes match. * * @author James Kempf */ private class ScopeBVCollector extends Object implements BVCollector { private Hashtable alreadySeen = new Hashtable(); // for those we've seen private Vector records = null; // for returns. private Vector scopes = null; // the scopes we're looking for ScopeBVCollector(Vector records, Vector scopes) { this.records = records; this.scopes = scopes; } public void setReturn(ServiceRecordInMemory rec) { // If we've got it already, then don't add. if (alreadySeen.get(rec) == null) { alreadySeen.put(rec, rec); if (scopes == null) { records.addElement(rec); } else { // Check scopes. int i; Vector rscopes = rec.getScopes(); int len = scopes.size(); for (i = 0; i < len; i++) { if (rscopes.contains(scopes.elementAt(i))) { records.addElement(rec); break; } } } } } } /** * The AllBVCollector class implements a BtreeVector * collector for collecting all records. * * @author James Kempf */ private class AllBVCollector extends Object implements BVCollector { private Vector records = null; // for returns. AllBVCollector(Vector records) { this.records = records; } public void setReturn(ServiceRecordInMemory rec) { // If we've got it already, then don't add. if (!records.contains(rec)) { records.addElement(rec); } } } /** * The List class implements a linked list for storing records * in the BtreeVector structure. * * @author James Kempf */ private class List extends Object { ServiceRecordInMemory record = null; List next = null; List prev = null; // Create a new list object. List(ServiceRecordInMemory record) { this.record = record; } // Insert a new record after this one. Return the new // record. synchronized List insertAfter(ServiceRecordInMemory record) { List newRec = new List(record); newRec.next = next; newRec.prev = this; if (next != null) { next.prev = newRec; } this.next = newRec; return newRec; } // Delete this record from the list. synchronized void delete() { if (next != null) { next.prev = prev; } if (prev != null) { prev.next = next; } prev = null; next = null; } } /** * The RegRecord class implements a record with the value for the * record buckets. It is used as elements in BtreeVector. * * @author James Kempf */ private class RegRecord extends Object { Object value = null; // the value for these registrations. List head = new List(null); // head of the list always null, // never changes. // Construct a new one. RegRecord(Object value) { this.value = value; } // Add a new record to the buckets, return new element. List add(ServiceRecordInMemory rec) { return head.insertAfter(rec); } // For every element in record's list, set the return value in the // returns object. Since deletions may have removed everything // from this record, return true only if something was there. boolean setReturn(BVCollector returns) { boolean match = false; List l = head; for (l = l.next; l != null; l = l.next) { ServiceRecordInMemory rec = l.record; returns.setReturn(rec); match = true; } return match; } public String toString() { return ""; } } /** * The BtreeVector class stores registrations in sorted order. The * Quicksort algorithm is used to insert items and search for something. * * @author James Kempf */ private class BtreeVector extends Object { // Contains the sorted vector. private Vector contents = new Vector(); public String toString() { return ""; } // Return the contents as a sorted vector of RegRecord. // Note that this doesn't return a copy, so // the vector can be side-effected. Vector getContents() { return contents; } // Add the entire contents of the vector to the return record. boolean getAll(BVCollector returns) { int i, n = contents.size(); boolean match = false; for (i = 0; i < n; i++) { RegRecord rec = (RegRecord)contents.elementAt(i); match = match | rec.setReturn(returns); } return match; } // Add a new record to this vector. We also garbage collect any // records that are empty. Return the list object added. List add(Object value, ServiceRecordInMemory record) { RegRecord rec = walkVector(value, true); // an update... // Add the record to this one. return rec.add(record); } // Add only if no element in the vector matches the tag. boolean matchDoesNotContain(Object pattern, BVCollector returns) { // Go through the vector, putting in anything that isn't equal. int i, n = contents.size(); Vector noMatch = new Vector(); boolean match = false; for (i = 0; i < n; i++) { RegRecord rec = (RegRecord)contents.elementAt(i); if (!compareEqual(rec.value, pattern)) { // Add to prospective returns. noMatch.addElement(rec); } } // If we got this far, there are some no matches. n = noMatch.size(); for (i = 0; i < n; i++) { RegRecord rec = (RegRecord)noMatch.elementAt(i); match = match | rec.setReturn(returns); } return match; } boolean matchEqual(Object pattern, BVCollector returns) { boolean match = false; // We can't walk the vector if the value is an AttributePattern, // because equals doesn't apply. if (pattern instanceof AttributePattern) { int i, n = contents.size(); for (i = 0; i < n; i++) { RegRecord rec = (RegRecord)contents.elementAt(i); AttributeString val = (AttributeString)rec.value; AttributePattern pat = (AttributePattern)pattern; if (pat.match(val)) { match = match | rec.setReturn(returns); } } } else { RegRecord rec = walkVector(pattern, false); // not an update... // If nothing came back, return false. if (rec == null) { match = false; } else { // Otherwise set returns in the vector. match = rec.setReturn(returns); } } return match; } boolean matchNotEqual(Object pattern, BVCollector returns) { // Go through the vector, putting in anything that isn't equal. int i, n = contents.size(); boolean match = false; for (i = 0; i < n; i++) { RegRecord rec = (RegRecord)contents.elementAt(i); if (!compareEqual(rec.value, pattern)) { match = match | rec.setReturn(returns); } } return match; } boolean matchLessEqual(Object pattern, BVCollector returns) { // Go through the vector, putting in anything that is // less than or equal. int i, n = contents.size(); boolean match = false; for (i = 0; i < n; i++) { RegRecord rec = (RegRecord)contents.elementAt(i); if (!compareLessEqual(rec.value, pattern)) { break; } match = match | rec.setReturn(returns); } return match; } boolean matchNotLessEqual(Object pattern, BVCollector returns) { // Go through the vector, putting in anything that is not // less than or equal. Start at the top. int i, n = contents.size(); boolean match = false; for (i = n - 1; i >= 0; i--) { RegRecord rec = (RegRecord)contents.elementAt(i); if (compareLessEqual(rec.value, pattern)) { break; } match = match | rec.setReturn(returns); } return match; } boolean matchGreaterEqual(Object pattern, BVCollector returns) { // Go through the vector, putting in anything that is greater // than or equal. Start at the top. int i, n = contents.size(); boolean match = false; for (i = n - 1; i >= 0; i--) { RegRecord rec = (RegRecord)contents.elementAt(i); if (!compareGreaterEqual(rec.value, pattern)) { break; } match = match | rec.setReturn(returns); } return match; } boolean matchNotGreaterEqual(Object pattern, BVCollector returns) { // Go through the vector, putting in anything that is not // than or equal. int i, n = contents.size(); boolean match = false; for (i = 0; i < n; i++) { RegRecord rec = (RegRecord)contents.elementAt(i); if (compareGreaterEqual(rec.value, pattern)) { break; } match = match | rec.setReturn(returns); } return match; } // Binary tree walk the vector, performing the operation. Note that // we use dynamic typing heavily here to get maximum code reuse. private RegRecord walkVector(Object pattern, boolean update) { // Get the starting set of indicies. int size = contents.size(); int middle = size / 2; int top = size - 1; int bottom = 0; RegRecord rec = null; top = (top < 0 ? 0:top); while (size > 0) { // Get the one at the current middle. rec = (RegRecord)contents.elementAt(middle); // Garbage Collection. // If it was null, then delete. But only if we're // inserting. We leave it alone on lookup. if (update) { if (rec.head.next == null) { contents.removeElementAt(middle); size = size - 1; middle = bottom + (size / 2); top = top - 1; top = (top < 0 ? 0:top); continue; } } // Compare value to record, if equal, return record. // code. if (compareEqual(rec.value, pattern)) { return rec; } else if (compareLessEqual(pattern, rec.value)) { // Recalculate index. We move left, because the value is // less that the value in the vector, so an equal value // must be to the left. Note that the top is not in the // interval because it has already been checked and // found wanting. top = middle; size = (top - bottom); middle = top - (size / 2); middle = (middle < 0 ? 0:middle); if (middle == top) { // Neither top nor middle are in the interval, // so size is zero. We need to compare with bottom. rec = null; RegRecord trec = (RegRecord)contents.elementAt(bottom); if (update) { rec = new RegRecord(pattern); // If the pattern is equal to bottom, return it. // If the pattern is less than or equal to bottom, // we insert it at bottom. If it is greater // than or equal, we insert it at middle. if (compareEqual(trec.value, pattern)) { return trec; } else if (compareLessEqual(pattern, trec.value)) { // Pattern is less than bottom, so insert // at bottom. contents.insertElementAt(rec, bottom); } else { contents.insertElementAt(rec, middle); } } else { // If it equals bottom, then return bottom rec. if (compareEqual(trec.value, pattern)) { rec = trec; } } break; } } else if (compareGreaterEqual(pattern, rec.value)) { // Recalculate index. We move right, because the value is // greater that the value in the vector, so an equal // value must be to the right. Note that the top is not // in the interval because it has already been checked // and found wanting. bottom = middle; size = (top - bottom); middle = bottom + (size / 2); if (middle == bottom) { // Neither bottom nor middle is in the interval, // so size is zero. We need to compare with top. rec = null; RegRecord trec = (RegRecord)contents.elementAt(top); if (update) { rec = new RegRecord(pattern); // If the pattern is equal to the top, we // return the top. If the pattern is greater // then top, we insert it after top, else we // insert it at top. if (compareEqual(trec.value, pattern)) { return trec; } else if (compareGreaterEqual(pattern, trec.value)) { // Pattern is greater than top, so insert // after top. int i = top + 1; if (i >= contents.size()) { contents.addElement(rec); } else { contents.insertElementAt(rec, i); } } else { // Pattern is less than top, so insert at // top, causing top to move up. contents.insertElementAt(rec, top); } } else { // If it equals top, then return top rec. if (compareEqual(trec.value, pattern)) { rec = trec; } } break; } } } // Take care of update where vector is empty or cleaned out. if (update && rec == null) { rec = new RegRecord(pattern); Assert.slpassert((contents.size() == 0), "ssim_btree_botch", new Object[0]); contents.addElement(rec); } return rec; } // Add any registrations that match the pattern. boolean compareEqual(Object target, Object pattern) { if (target instanceof Integer || target instanceof Boolean || target instanceof Opaque || target instanceof Long) { if (pattern.equals(target)) { return true; } } else if (target instanceof AttributeString) { // If the pattern is an AttributePattern instead of an // AttributeString, the subclass method will get invoked. if (((AttributeString)pattern).match( (AttributeString)target)) { return true; } } else { Assert.slpassert(false, "ssim_unk_qtype", new Object[] {pattern.getClass().getName()}); } return false; } // Add any registrations that are less than or equal to the pattern. boolean compareLessEqual(Object target, Object pattern) { if (target instanceof Integer) { if (((Integer)target).intValue() <= ((Integer)pattern).intValue()) { return true; } } else if (target instanceof AttributeString) { if (((AttributeString)target).lessEqual( (AttributeString)pattern)) { return true; } } else if (target instanceof Long) { if (((Long)target).longValue() <= ((Long)pattern).longValue()) { return true; } } else if (target instanceof Boolean || target instanceof Opaque) { if (target.toString().compareTo(pattern.toString()) <= 0) { return true; } } else { Assert.slpassert(false, "ssim_unk_qtype", new Object[] {target.getClass().getName()}); } return false; } // Add any registrations that are greater than or equal to the pattern. boolean compareGreaterEqual(Object target, Object pattern) { if (target instanceof Integer) { if (((Integer)target).intValue() >= ((Integer)pattern).intValue()) { return true; } } else if (target instanceof AttributeString) { if (((AttributeString)target).greaterEqual( (AttributeString)pattern)) { return true; } } else if (target instanceof Long) { if (((Long)target).longValue() >= ((Long)pattern).longValue()) { return true; } } else if (target instanceof Boolean || target instanceof Opaque) { if (target.toString().compareTo(pattern.toString()) >= 0) { return true; } } else { Assert.slpassert(false, "ssim_unk_qtype", new Object[] {target.getClass().getName()}); } return false; } } /** * The InMemoryEvaluator evaluates queries for ServiceStoreInMemory. * * @author James Kempf */ private class InMemoryEvaluator implements Parser.QueryEvaluator { private Hashtable attrLevel; // Sorted attribute table. private BtreeVector attrLevelNot; // Used for universal negation. private Vector inScopes; // Input scopes. private ParserBVCollector returns; // For gathering results. InMemoryEvaluator(Hashtable ht, BtreeVector btv, Vector nscopes) { attrLevel = ht; attrLevelNot = btv; inScopes = nscopes; returns = new ParserBVCollector(inScopes); } // Evaluate the query by matching the attribute tag and // value, using the operator. If invert is true, then // return records that do NOT match. public boolean evaluate(AttributeString tag, char op, Object pattern, boolean invert, Parser.ParserRecord prReturns) throws ServiceLocationException { boolean match = false; returns.prReturns = prReturns; // If inversion is on, then gather all from the // table of registrations that do NOT have this // attribute. if (invert) { match = attrLevelNot.matchDoesNotContain(tag, returns); } // Find the table of classes v.s. sorted value vectors. Hashtable ttable = (Hashtable)attrLevel.get(tag); // If attribute not present, then simply return. if (ttable == null) { return match; } // If operator is present, then return all. if (op == Parser.PRESENT) { // ...but only if invert isn't on. if (!invert) { // We use attrLevelNot to get all, because it // will also pick up keywords. There are // no keywords in attrLevel because keywords // don't have any values. match = attrLevelNot.matchEqual(tag, returns); } return match; } // We know that the type table is fully initialized with // BtreeVectors for each type. // Get the pattern's class. Pattern will not be null because // the parser has checked for it and PRESENT has been // filtered out above. Class pclass = pattern.getClass(); String typeKey = pclass.getName(); // If the class is AttributePattern, then use AttributeString // instead. if (pattern instanceof AttributePattern) { typeKey = pclass.getSuperclass().getName(); } // If invert is on, collect those whose types don't match as // well. if (invert) { Enumeration en = ttable.keys(); while (en.hasMoreElements()) { String key = (String)en.nextElement(); // Only record if the type does NOT match. if (!key.equals(typeKey)) { BtreeVector bvec = (BtreeVector)ttable.get(key); match = match | bvec.getAll(returns); } } } // Get the sorted value vector corresponding to the value class. BtreeVector bvec = (BtreeVector)ttable.get(typeKey); // Do the appropriate thing for the operator. switch (op) { case Parser.EQUAL: if (!invert) { match = bvec.matchEqual(pattern, returns); } else { match = bvec.matchNotEqual(pattern, returns); } break; case Parser.LESS: // Note that we've filtered out Opaque, Boolean, and wildcarded // strings before calling this method. if (!invert) { match = bvec.matchLessEqual(pattern, returns); } else { match = bvec.matchNotLessEqual(pattern, returns); } break; case Parser.GREATER: // Note that we've filtered out Opaque and Boolean // before calling this method. if (!invert) { match = bvec.matchGreaterEqual(pattern, returns); } else { match = bvec.matchNotGreaterEqual(pattern, returns); } break; default: Assert.slpassert(false, "ssim_unk_qop", new Object[] {new Character((char)op)}); } return match; } } /** * The ServiceRecordInMemory class implements the * ServiceStore.ServiceRecord interface on in-memory data structures. * Each property is implemented as an instance variable. * * @author James Kempf */ private class ServiceRecordInMemory extends Object implements ServiceStore.ServiceRecord { private ServiceURL serviceURL = null; // the service URL private Vector attrList = null; // the attribute list private Locale locale = null; // the locale private long timeToDie = 0; // when the record should die. private Vector scopes = null; // the scopes private Hashtable urlSig = null; // URL signature block list, if any. private Hashtable attrSig = null; // Attribute signature block list, if any. // Create a ServiceStoreInMemory record. ServiceRecordInMemory(ServiceURL surl, Vector alist, Vector nscopes, Locale loc, Hashtable nurlSig, Hashtable nattrSig) { // All need to be nonnull. Assert.nonNullParameter(surl, "surl"); Assert.nonNullParameter(alist, "alist"); Assert.nonNullParameter(nscopes, "nscopes"); Assert.nonNullParameter(loc, "loc"); serviceURL = surl; attrList = attributeVectorToServerAttribute(alist, loc); scopes = nscopes; locale = loc; urlSig = nurlSig; attrSig = nattrSig; int lifetime = serviceURL.getLifetime(); timeToDie = lifetime * 1000 + System.currentTimeMillis(); } /** * Return the ServiceURL for the record. * * @return The record's service URL. */ public final ServiceURL getServiceURL() { return serviceURL; } /** * Return the Vector of ServerAttribute objects for the record. * * @return Vector of ServerAttribute objects for the record. */ public final Vector getAttrList() { return attrList; } /** * Return the locale of the registration. * * @return The locale of the registration. */ public final Locale getLocale() { return locale; } /** * Return the Vector of scopes in which the record is registered. * * @return Vector of strings with scope names. */ public final Vector getScopes() { return scopes; } /** * Return the expiration time for the record. This informs the * service store when the record should expire and be removed * from the table. * * @return The expiration time for the record. */ public long getExpirationTime() { return timeToDie; } /** * Return the URL signature list. * * @return URL signature block list. */ public Hashtable getURLSignature() { return urlSig; } /** * Return the attribute signature list. * * @return Attribute signature list. */ public Hashtable getAttrSignature() { return attrSig; } // // Package-local methods. final void setAttrList(Vector newList) { attrList = newList; } final void setScopes(Vector newScopes) { scopes = newScopes; } final void setURLSignature(Hashtable nauth) { urlSig = nauth; } final void setAttrSignature(Hashtable nauth) { attrSig = nauth; } public String toString() { String ret = "{"; ret += serviceURL + ", " + locale + ", " + attrList + ", " + scopes + ", " + locale + ", " + urlSig + ", " + attrSig; ret += "}"; return ret; } // Convert a vector of ServiceLocationAttribute objects to // ServerAttibutes. private Vector attributeVectorToServerAttribute(Vector attrs, Locale locale) { int i, n = attrs.size(); Vector v = new Vector(); for (i = 0; i < n; i++) { ServiceLocationAttribute attr = (ServiceLocationAttribute)attrs.elementAt(i); v.addElement(new ServerAttribute(attr, locale)); } return v; } } /** * A record for scopeTypeLangTable table, * * @author James Kempf */ private class STLRecord extends Object { Hashtable attrValueSort = new Hashtable(); // Table of attributes, sorted by value. BtreeVector attrSort = new BtreeVector(); // Btree of attributes. boolean isAbstract = false; // True if the record is for an abstract // type. STLRecord(boolean isAbstract) { this.isAbstract = isAbstract; } } // // ServiceStoreInMemory instance variables. // // ServiceStoreInMemory maintains an invaraint that the record for a // particular URL, set of scopes, and locale is the same object // (pointer-wise) regardless of where it is inserted into the table. // So it can be compared with ==. // The scopeTypeLangTable // // Keys for this table are scope/service type/lang tag. Values are // STLRecord objects. The STLRecord.attrValueSort field is a Hashtable // where all registrations *having* the attribute tag keys in the // table are contained. This table is used in queries for positive // logical expressions. The STLRecord.attrSort field is a BtreeVector // keyed by attribute. It is used for negative queries to find all // records not having a particular attribute and to find all // registrations. The STLRecord.isAbstract field tells whether the record // is for an abstract type name. // // The values in the STLRecord.attrValueSort hashtable are themselves // hashtables. These hashtables are keyed by one of the type keys below, // with the values being BtreeVector objects. The BtreeVector objects // contain sorted lists of RegRecord objects for Integer, // AttributeString, Boolean, and Opaque types. All records having // values equal to the value in the RegRecord are put into a list // on the RegRecord. There is no STLRecord.attrValueSort // hashtable for keyword attributes because they have no values. // The parser evaluator must use the STLRecord.attrSort hashtable when a // present operator is encountered (the only valid operator with a // keyword). // // The values in the STLRecord.attrSort BtreeVector are RegRecord // objects with all records having that attribute tag being on the // RegRecord list. // Keys for the various types. private final static String INTEGER_TYPE = "java.lang.Integer"; private final static String ATTRIBUTE_STRING_TYPE = "com.sun.slp.AttributeString"; private final static String BOOLEAN_TYPE = "java.lang.Boolean"; private final static String OPAQUE_TYPE = "com.sun.slp.Opaque"; private Hashtable scopeTypeLangTable = new Hashtable(); // The urlScopeLangTable // // Keys for this table are service url as a string. We don't use // the service URL itself because the hash code depends on the // current service type rather than the original, and we need // to be able to distinguish for a non-service: URL if a // registration comes in with a different service type from the // original. Values are hashtables with key being scope name, // values are hashtables with lang tag key. Ultimate values are // a vector of List objects for lists in which List.record is // inserted. This table is used to perform deletions and for // finding the attributes associated with a particular URL. private Hashtable urlScopeLangTable = new Hashtable(); // The sstLocales Table // // The scope/service type v.s. number of languages. Keys are // the type/scope, values are a hashtable keyed by lang tag. // Values in the lang tag table are Integer objects giving // the number of registrations for that type/scope in the // given locale. private Hashtable sstLocales = new Hashtable(); // A queue of records sorted according to expiration time. BtreeVector ageOutQueue = new BtreeVector(); // Constants that indicate whether there are any registrations. private final static int NO_REGS = 0; private final static int NO_REGS_IN_LOCALE = 1; private final static int REGS_IN_LOCALE = 2; // Boot time. For DAAdvert timestamps. private long bootTime = SLPConfig.currentSLPTime(); // // ServiceStore Interface Methods. // /** * Return the time since the last stateless reboot * of the ServiceStore. * * @return A Long giving the time since the last stateless reboot, * in NTP format. */ public long getStateTimestamp() { return bootTime; } /** * Age out all records whose time has expired. * * @param deleted A Vector for return of ServiceStore.Service records * containing deleted services. * @return The time interval until another table walk must be done, * in milliseconds. * */ synchronized public long ageOut(Vector deleted) { // Get the ageOut queue and remove all records whose // time has popped. SLPConfig conf = SLPConfig.getSLPConfig(); boolean traceDrop = conf.traceDrop(); Vector queue = ageOutQueue.getContents(); // Go through the queue, dropping records that // have expired. int i; for (i = 0; i < queue.size(); i++) { RegRecord qRec = (RegRecord)queue.elementAt(i); long exTime = ((Long)(qRec.value)).longValue(); long time = System.currentTimeMillis(); // Break out when none expire now. if (exTime > time) { break; } // Remove the element from the queue. /* * Must decrement the index 'i' otherwise the next iteration * around the loop will miss the element immediately after * the element removed. * * WARNING: Do not use 'i' again until the loop has * iterated as it may, after decrementing, * be negative. */ queue.removeElementAt(i); i--; // Deregister all on this list. We // take specific care to save the next // list element before we deregister, otherwise // it will be gone after the deregister. List l = qRec.head.next; while (l != null) { ServiceRecordInMemory rec = l.record; ServiceURL url = rec.getServiceURL(); Vector scopes = rec.getScopes(); Locale locale = rec.getLocale(); if (traceDrop) { conf.writeLog("ssim_ageout", new Object[] { url, rec.getAttrList(), scopes, locale, rec.getURLSignature(), rec.getAttrSignature(), Long.toString(time), Long.toString(exTime)}); } // Save the record for the service table, in case more // processing needed. deleted.addElement(rec); // Save l.next NOW before deregisterInternal() removes it! l = l.next; String lang = locale.getLanguage(); deregisterInternal(url, scopes, lang); } } // Calculate the new sleep time. If there's anything in the vector, // then use element 0, because the vector is sorted by time // and that will be minimum. Otherwise, use the maximum. long newSleepy = Defaults.lMaxSleepTime; if (queue.size() > 0) { RegRecord rec = (RegRecord)queue.elementAt(0); newSleepy = ((Long)(rec.value)).longValue() - System.currentTimeMillis(); newSleepy = (newSleepy > 0 ? newSleepy:0); // it will wake right up, but // so what? } return newSleepy; } /** * Create a new registration with the given parameters. * * @param url The ServiceURL. * @param attrs The Vector of ServiceLocationAttribute objects. * @param locale The Locale. * @param scopes Vector of scopes in which this record is registered. * @param urlSig auth block Hashtable for URL signature, or null if none. * @param attrSig auth block Hashtable for URL signature, or null if none. * @return True if there is an already existing registration that * this one replaced. * @exception ServiceLocationException Thrown if any * error occurs during registration or if the table * requires a network connection that failed. This * includes timeout failures. */ synchronized public boolean register(ServiceURL url, Vector attrs, Vector scopes, Locale locale, Hashtable urlSig, Hashtable attrSig) throws ServiceLocationException { boolean existing = false; String lang = locale.getLanguage(); // Find an existing record, in any set of scopes having this language. ServiceRecordInMemory rec = findExistingRecord(url, null, lang); // Deregister from existing scopes, if there is an existing record. if (rec != null) { if (urlSig != null) { // Ensure that the rereg SPI set and the record's SPI set are // equivalent. We need only check the URL sigs here, since // this operation is equivalent to a dereg followed by a reg, // and dereg requires only URL auth blocks. Enumeration spis = urlSig.keys(); while (spis.hasMoreElements()) { Object spi = spis.nextElement(); if (rec.urlSig.remove(spi) == null) { throw new ServiceLocationException( ServiceLocationException.AUTHENTICATION_FAILED, "not_all_spis_present", new Object[] {spi}); } } if (rec.urlSig.size() != 0) { // not all required SPIs were present in SrvReg throw new ServiceLocationException( ServiceLocationException.AUTHENTICATION_FAILED, "not_all_spis_present", new Object[] {rec.urlSig.keys()}); } } deregisterInternal(url, rec.getScopes(), lang); existing = true; } // Create a new record to register. rec = new ServiceRecordInMemory(url, attrs, scopes, locale, urlSig, attrSig); // Add new registration. registerInternal(rec); return existing; } /** * Deregister a ServiceURL from the database for every locale * and every scope. There will be only one record for each URL * and locale deregistered, regardless of the number of scopes in * which the URL was registered, since the attributes will be the * same in each scope if the locale is the same. * * @param url The ServiceURL * @param scopes Vector of scopes. * @param urlSig The URL signature, if any. * @exception ServiceLocationException Thrown if the * ServiceStore does not contain the URL, or if any * error occurs during the operation, or if the table * requires a network connection that failed. This * includes timeout failures. */ synchronized public void deregister(ServiceURL url, Vector scopes, Hashtable urlSig) throws ServiceLocationException { // Find existing record. Any locale will do. ServiceRecordInMemory oldRec = findExistingRecord(url, scopes, null); // Error if none. if (oldRec == null) { throw new ServiceLocationException( ServiceLocationException.INVALID_REGISTRATION, "ssim_no_rec", new Object[] {url}); } // verify that the dereg SPI set and the record's SPI set are // equivalent if (urlSig != null) { Enumeration spis = urlSig.keys(); while (spis.hasMoreElements()) { Object spi = spis.nextElement(); if (oldRec.urlSig.remove(spi) == null) { throw new ServiceLocationException( ServiceLocationException.AUTHENTICATION_FAILED, "not_all_spis_present", new Object[] {spi}); } } if (oldRec.urlSig.size() != 0) { // not all required SPIs were present in SrvDereg throw new ServiceLocationException( ServiceLocationException.AUTHENTICATION_FAILED, "not_all_spis_present", new Object[] {oldRec.urlSig.keys()}); } } /* * Deregister the URL for all locales. Use the recorded service URL * because the one passed by the client is possibly incomplete e.g. * lacking the service type. */ deregisterInternal(oldRec.getServiceURL(), scopes, null); } /** * Update the service registration with the new parameters, adding * attributes and updating the service URL's lifetime. * * @param url The ServiceURL. * @param attrs The Vector of ServiceLocationAttribute objects. * @param locale The Locale. * @param scopes Vector of scopes in which this record is registered. * @exception ServiceLocationException Thrown if any * error occurs during registration or if the table * requires a network connection that failed. This * includes timeout failures. */ synchronized public void updateRegistration(ServiceURL url, Vector attrs, Vector scopes, Locale locale) throws ServiceLocationException { String lang = locale.getLanguage(); ServiceRecordInMemory oldRec = findExistingRecord(url, scopes, lang); // Error if none. if (oldRec == null) { throw new ServiceLocationException( ServiceLocationException.INVALID_UPDATE, "ssim_no_rec", new Object[] {url}); } // If this is a nonServiceURL, check whether it's registered // under a different service type. ServiceType type = url.getServiceType(); if (!type.isServiceURL()) { checkForExistingUnderOtherServiceType(url, scopes); } // Deregister the URL in this locale. deregisterInternal(url, scopes, lang); // Create a new record to update. ServiceRecordInMemory rec = new ServiceRecordInMemory(url, attrs, scopes, locale, null, null); // Merge old record into new. mergeOldRecordIntoNew(oldRec, rec); // Add the new record. registerInternal(rec); } /** * Delete the attributes from the ServiceURL object's table entries. * Delete for every locale that has the attributes and every scope. * Note that the attribute tags must be lower-cased in the locale of * the registration, not in the locale of the request. * * @param url The ServiceURL. * @param scopes Vector of scopes. * @param attrTags The Vector of String * objects specifying the attribute tags of * the attributes to delete. * @param locale Locale of the request. * @exception ServiceLocationException Thrown if the * ServiceStore does not contain the URL or if any * error occurs during the operation or if the table * requires a network connection that failed. This * includes timeout failures. */ synchronized public void deleteAttributes(ServiceURL url, Vector scopes, Vector attrTags, Locale locale) throws ServiceLocationException { String lang = SLPConfig.localeToLangTag(locale); // Get the scope level from urlScopeLangTable. Hashtable scopeLevel = (Hashtable)urlScopeLangTable.get(url.toString()); // Error if no old record to update. if (scopeLevel == null) { throw new ServiceLocationException( ServiceLocationException.INVALID_REGISTRATION, "ssim_no_rec", new Object[] {url}); } // Check existing records to be sure that the scopes // match. Attributes must be the same across // scopes. checkScopeStatus(url, scopes, ServiceLocationException.INVALID_REGISTRATION); // Create attribute patterns for the default locale. This // is an optimization. Only Turkish differs in lower // case from the default. If there are any other exceptions, // we need to move this into the loop. Vector attrPatterns = stringVectorToAttributePattern(attrTags, Defaults.locale); // Look through the language table for this language at scope level. Enumeration en = scopeLevel.keys(); Assert.slpassert(en.hasMoreElements(), "ssim_empty_scope_table", new Object[] {url}); Hashtable ht = new Hashtable(); boolean foundIt = false; while (en.hasMoreElements()) { String scope = (String)en.nextElement(); Hashtable langLevel = (Hashtable)scopeLevel.get(scope); Enumeration een = langLevel.keys(); Assert.slpassert(een.hasMoreElements(), "ssim_empty_lang_table", new Object[] {url}); // Find the list of records for this language. Vector listVec = (Vector)langLevel.get(lang); if (listVec == null) { continue; } foundIt = true; List elem = (List)listVec.elementAt(0); ServiceRecordInMemory rec = elem.record; Locale loc = rec.getLocale(); // If we've done this one already, go on. if (ht.get(rec) != null) { continue; } ht.put(rec, rec); // Delete old registration. deregisterInternal(url, rec.getScopes(), lang); // Delete attributes from this record. // If the locale is Turkish, then use the Turkish patterns. if (loc.getLanguage().equals("tr")) { Vector turkishTags = stringVectorToAttributePattern(attrTags, loc); deleteAttributes(rec, turkishTags); } else { deleteAttributes(rec, attrPatterns); } // Reregister the record. registerInternal(rec); } // If no record found, report error. if (!foundIt) { throw new ServiceLocationException( ServiceLocationException.INVALID_REGISTRATION, "ssim_no_rec_locale", new Object[] {url, locale}); } } /** * Return a Vector of String containing the service types for this * scope and naming authority. If there are none, an empty vector is * returned. * * @param namingAuthority The namingAuthority, or "*" if for all. * @param scopes The scope names. * @return A Vector of String objects that are the type names, or * an empty vector if there are none. * @exception ServiceLocationException Thrown if any * error occurs during the operation or if the table * requires a network connection that failed. This * includes timeout failures. */ synchronized public Vector findServiceTypes(String namingAuthority, Vector scopes) throws ServiceLocationException { Vector ret = new Vector(); Enumeration keys = scopeTypeLangTable.keys(); boolean isWildCard = namingAuthority.equals("*"); boolean isIANA = (namingAuthority.length() <= 0); // Get all the keys in the table, look for scope. while (keys.hasMoreElements()) { String sstKey = (String)keys.nextElement(); // Check whether this is an abstract type entry. // If so, then we ignore it, because we only // want full type names in the return. if (isAbstractTypeRecord(sstKey)) { continue; } // If the scope matches then check the naming authority. String keyScope = keyScope(sstKey); if (scopes.contains(keyScope)) { String keyType = keyServiceType(sstKey); // If not already there, see if we should add this one to the // vector. if (!ret.contains(keyType)) { ServiceType type = new ServiceType(keyType); // If wildcard, then simply add it to the vector. if (isWildCard) { ret.addElement(type.toString()); } else { // Check naming authority. String na = type.getNamingAuthority(); if (type.isNADefault() && isIANA) { // check for IANA.. ret.addElement(type.toString()); } else if (namingAuthority.equals(na)) { // Not IANA.. ret.addElement(type.toString()); } } } } } return ret; } /** * Return a Hashtable with the key FS_SERVICES matched to the * hashtable of ServiceURL objects as key and a vector * of their scopes as value, and the key FS_SIGTABLE * matched to a hashtable with ServiceURL objects as key * and the auth block Hashtable for the URL (if any) for value. The * returned service URLs will match the service type, scope, query, * and locale. If there are no signatures, the FS_SIGTABLE * key returns null. If there are no * registrations in any locale, FS_SERVICES is bound to an * empty table. * * @param serviceType The service type name. * @param scope The scope name. * @param query The query, with any escaped characters as yet unprocessed. * @param locale The locale in which to lowercase query and search. * @return A Hashtable with the key FS_SERVICES matched to the * hashtable of ServiceURL objects as key and a vector * of their scopes as value, and the key FS_SIGTABLE * matched to a hashtable with ServiceURL objects as key * and the auth block Hashtable for the URL (if any) for value. * If there are no registrations in any locale, FS_SERVICES * is bound to an empty table. * @exception ServiceLocationException Thrown if a parse error occurs * during query parsing or if any * error occurs during the operation or if the table * requires a network connection that failed. This * includes timeout failures. */ synchronized public Hashtable findServices(String serviceType, Vector scopes, String query, Locale locale) throws ServiceLocationException { String lang = locale.getLanguage(); Parser.ParserRecord ret = new Parser.ParserRecord(); Hashtable services = null; Hashtable signatures = null; int i, n = scopes.size(); int len = 0; // Get the services and signatures tables. services = ret.services; signatures = ret.signatures; // Remove leading and trailing spaces. query = query.trim(); len = query.length(); // Check whether there are any registrations for this type/scope/ // language tag and, if not, whether there are others. // in another language, but not this one. int regStatus = languageSupported(serviceType, scopes, lang); if (regStatus == NO_REGS_IN_LOCALE) { throw new ServiceLocationException( ServiceLocationException.LANGUAGE_NOT_SUPPORTED, "ssim_lang_unsup", new Object[] {locale}); } else if (regStatus == REGS_IN_LOCALE) { // Only do query if regs exist. for (i = 0; i < n; i++) { String scope = (String)scopes.elementAt(i); String sstKey = makeScopeTypeLangKey(scope, serviceType, lang); STLRecord regRecs = (STLRecord)scopeTypeLangTable.get(sstKey); // If no record for this combo of service type and // scope, continue. if (regRecs == null) { continue; } // Special case if the query string is empty. This // indicates that all registrations should be returned. if (len <= 0) { BtreeVector bvec = regRecs.attrSort; ParserBVCollector collector = new ParserBVCollector(scopes); collector.prReturns = ret; // Use the BtreeVector.getAll() method to get all // registrations. We will end up revisiting some // list elements because there will be ones // for multiple attributes, but that will be // filtered in the BVCollector.setReturn() method. bvec.getAll(collector); } else { // Otherwise, use the LDAPv3 parser to evaluate. InMemoryEvaluator ev = new InMemoryEvaluator(regRecs.attrValueSort, regRecs.attrSort, scopes); Parser.parseAndEvaluateQuery(query, ev, locale, ret); } } } // Create return hashtable. Hashtable ht = new Hashtable(); // Set up return hashtable. ht.put(ServiceStore.FS_SERVICES, services); // Put in signatures if there. if (signatures.size() > 0) { ht.put(ServiceStore.FS_SIGTABLE, signatures); } return ht; } /** * Return a Hashtable with key FA_ATTRIBUTES matched to the * vector of ServiceLocationAttribute objects and key FA_SIG * matched to the auth block Hashtable for the attributes (if any) * The attribute objects will have tags matching the tags in * the input parameter vector. If there are no registrations in any locale, * FA_ATTRIBUTES is an empty vector. * * @param url The ServiceURL for which the records should be returned. * @param scopes The scope names for which to search. * @param attrTags The Vector of String * objects containing the attribute tags. * @param locale The locale in which to lower case tags and search. * @return A Hashtable with a vector of ServiceLocationAttribute objects * as the key and the auth block Hashtable for the attributes * (if any) as the value. * If there are no registrations in any locale, FA_ATTRIBUTES * is an empty vector. * @exception ServiceLocationException Thrown if any * error occurs during the operation or if the table * requires a network connection that failed. This * includes timeout failures. An error should be * thrown if the tag vector is for a partial request * and any of the scopes are protected. */ synchronized public Hashtable findAttributes(ServiceURL url, Vector scopes, Vector attrTags, Locale locale) throws ServiceLocationException { Hashtable ht = new Hashtable(); Vector ret = new Vector(); String lang = locale.getLanguage(); Hashtable sig = null; // Check whether there are any registrations for this scope/type // language and, if not, whether there are others. // in another language, but not this one. int regStatus = languageSupported(url.getServiceType().toString(), scopes, lang); if (regStatus == NO_REGS_IN_LOCALE) { throw new ServiceLocationException( ServiceLocationException.LANGUAGE_NOT_SUPPORTED, "ssim_lang_unsup", new Object[] {locale}); } else if (regStatus == REGS_IN_LOCALE) { // Only if there are any regs at all. // Process string tags into pattern objects. Note that, here, // the patterns are locale specific because the locale of // the request determines how the attribute tags are lower // cased. attrTags = stringVectorToAttributePattern(attrTags, locale); // Return attributes from the matching URL record. Hashtable scopeLevel = (Hashtable)urlScopeLangTable.get(url.toString()); // If nothing there, then simply return. The URL isn't // registered. if (scopeLevel != null) { // We reuse ht here for attributes. int i, n = scopes.size(); for (i = 0; i < n; i++) { String scope = (String)scopes.elementAt(i); Hashtable langLevel = (Hashtable)scopeLevel.get(scope); // If no registration in this scope, continue. if (langLevel == null) { continue; } // Get the vector of lists. Vector listVec = (Vector)langLevel.get(lang); // If no registration in this locale, continue. if (listVec == null) { continue; } // Get the service record. List elem = (List)listVec.elementAt(0); ServiceRecordInMemory rec = elem.record; // Once we've found *the* URL record, we can leave the loop // because there is only one record per locale. findMatchingAttributes(rec, attrTags, ht, ret); // Clear out the hashtable. We reuse it for the return. ht.clear(); // Store the return vector and the signatures, if any. ht.put(ServiceStore.FA_ATTRIBUTES, ret); sig = rec.getAttrSignature(); if (sig != null) { ht.put(ServiceStore.FA_SIG, sig); } break; } } } // Put in the empty vector, in case there are no regs at all. if (ht.size() <= 0) { ht.put(ServiceStore.FA_ATTRIBUTES, ret); } return ht; } /** * Return a Vector of ServiceLocationAttribute objects with attribute tags * matching the tags in the input parameter vector for all service URL's * of the service type. If there are no registrations * in any locale, an empty vector is returned. * * @param serviceType The service type name. * @param scopes The scope names for which to search. * @param attrTags The Vector of String * objects containing the attribute tags. * @param locale The locale in which to lower case tags. * @return A Vector of ServiceLocationAttribute objects matching the query. * If no match occurs but there are registrations * in other locales, null is returned. If there are no registrations * in any locale, an empty vector is returned. * @exception ServiceLocationException Thrown if any * error occurs during the operation or if the table * requires a network connection that failed. This * includes timeout failures. An error should also be * signalled if any of the scopes are protected. */ synchronized public Vector findAttributes(String serviceType, Vector scopes, Vector attrTags, Locale locale) throws ServiceLocationException { String lang = locale.getLanguage(); Vector ret = new Vector(); // Check whether there are any registrations for this type/scope/ // language and, if not, whether there are others. // in another language, but not this one. int regStatus = languageSupported(serviceType, scopes, lang); if (regStatus == NO_REGS_IN_LOCALE) { throw new ServiceLocationException( ServiceLocationException.LANGUAGE_NOT_SUPPORTED, "ssim_lang_unsup", new Object[] {locale}); } else if (regStatus == REGS_IN_LOCALE) { // Process string tags into pattern objects. Note that, here, // the patterns are locale specific because the locale of // the request determines how the attribute tags are lower // cased. attrTags = stringVectorToAttributePattern(attrTags, locale); int len = attrTags.size(); // Make a collector for accessing the BtreeVector. BVCollector collector = new AttributeBVCollector(attrTags, ret); int i, n = scopes.size(); for (i = 0; i < n; i++) { String scope = (String)scopes.elementAt(i); String sstKey = makeScopeTypeLangKey(scope, serviceType, lang); STLRecord regRecs = (STLRecord)scopeTypeLangTable.get(sstKey); // If no service type and scope, go to next scope. if (regRecs == null) { continue; } // Get BtreeVector with all attributes for searching. BtreeVector bvec = regRecs.attrSort; // If there are no tags, then simply return everything in // the BtreeVector. if (len <= 0) { bvec.getAll(collector); } else { // Use Btree vector to match the attribute tag patterns, // returning matching records. int j; for (j = 0; j < len; j++) { AttributePattern pat = (AttributePattern)attrTags.elementAt(j); bvec.matchEqual(pat, collector); } } } } return ret; } /** * Obtain the record matching the service URL and locale. * * @param URL The service record to match. * @param locale The locale of the record. * @return The ServiceRecord object, or null if none. */ synchronized public ServiceStore.ServiceRecord getServiceRecord(ServiceURL URL, Locale locale) { if (URL == null || locale == null) { return null; } // Search in all scopes. return findExistingRecord(URL, null, SLPConfig.localeToLangTag(locale)); } /** * Obtains service records with scopes matching from vector scopes. * If scopes is null, then returns all records. * * @param scopes Vector of scopes to match. * @return Enumeration Of ServiceRecord Objects. */ synchronized public Enumeration getServiceRecordsByScope(Vector scopes) { // Use a scope collector. Vector records = new Vector(); BVCollector collector = new ScopeBVCollector(records, scopes); Enumeration keys = scopeTypeLangTable.keys(); while (keys.hasMoreElements()) { String sstKey = (String)keys.nextElement(); STLRecord regRecs = (STLRecord)scopeTypeLangTable.get(sstKey); // Get all records. BtreeVector bvec = regRecs.attrSort; bvec.getAll(collector); } return records.elements(); } /** * Dump the service store to the log. * */ synchronized public void dumpServiceStore() { SLPConfig conf = SLPConfig.getSLPConfig(); conf.writeLogLine("ssim_dump_start", new Object[] {this}); Enumeration keys = scopeTypeLangTable.keys(); while (keys.hasMoreElements()) { String sstKey = (String)keys.nextElement(); STLRecord regRec = (STLRecord)scopeTypeLangTable.get(sstKey); // If the service type is abstract, then skip it. It will be // displayed when the concrete type is. if (regRec.isAbstract) { continue; } // Get all records. BtreeVector bvec = regRec.attrSort; Vector vReturns = new Vector(); BVCollector collector = new AllBVCollector(vReturns); bvec.getAll(collector); // Now write them out. int i, n = vReturns.size(); for (i = 0; i < n; i++) { ServiceRecordInMemory rec = (ServiceRecordInMemory)vReturns.elementAt(i); writeRecordToLog(conf, rec); } } conf.writeLog("ssim_dump_end", new Object[] {this}); } // // Protected/private methods. // // Register the record without any preliminaries. We assume that // any old records have been removed and merged into this one, // as necessary. private void registerInternal(ServiceRecordInMemory rec) { ServiceURL surl = rec.getServiceURL(); ServiceType type = surl.getServiceType(); String serviceType = type.toString(); String abstractTypeName = type.getAbstractTypeName(); Locale locale = rec.getLocale(); String lang = locale.getLanguage(); Vector scopes = rec.getScopes(); // Make one age out queue entry. It will go into // all scopes, but that's OK. List ageOutElem = addToAgeOutQueue(rec); // Go through all scopes. int i, n = scopes.size(); for (i = 0; i < n; i++) { String scope = (String)scopes.elementAt(i); // Initialize the urltable list vector for this URL. Vector listVec = initializeURLScopeLangTableVector(surl, scope, lang); // Add to scope/type/lang table. addRecordToScopeTypeLangTable(scope, serviceType, lang, false, rec, listVec); // Add a new service type/scope record for this locale. addTypeLocale(serviceType, scope, lang); // Add ageOut record, so that it gets deleted when // the record does. listVec.addElement(ageOutElem); // If the type is an abstract type, then add // separate records. if (type.isAbstractType()) { addRecordToScopeTypeLangTable(scope, abstractTypeName, lang, true, rec, listVec); addTypeLocale(abstractTypeName, scope, lang); } } } // Create a urlScopeLangTable record for this URL. private Vector initializeURLScopeLangTableVector(ServiceURL url, String scope, String lang) { // Get scope level, creating if new. Hashtable scopeLevel = (Hashtable)urlScopeLangTable.get(url.toString()); if (scopeLevel == null) { scopeLevel = new Hashtable(); urlScopeLangTable.put(url.toString(), scopeLevel); } // Get lang level, creating if new. Hashtable langLevel = (Hashtable)scopeLevel.get(scope); if (langLevel == null) { langLevel = new Hashtable(); scopeLevel.put(scope, langLevel); } // Check whether there's anything already there. // Bug if so. Assert.slpassert(langLevel.get(lang) == null, "ssim_url_lang_botch", new Object[] {lang, url, scope}); // Add a new list vector, and return it. Vector listVec = new Vector(); langLevel.put(lang, listVec); return listVec; } // Add a record to the scope/type/language table. private void addRecordToScopeTypeLangTable(String scope, String serviceType, String lang, boolean isAbstract, ServiceRecordInMemory rec, Vector listVec) { // Make key for scope/type/language table. String stlKey = makeScopeTypeLangKey(scope, serviceType, lang); // Get record for scope/type/lang. STLRecord trec = (STLRecord)scopeTypeLangTable.get(stlKey); // If it's not there, make it. if (trec == null) { trec = new STLRecord(isAbstract); scopeTypeLangTable.put(stlKey, trec); } // Otherwise, add record to all. addRecordToAttrValueSort(trec.attrValueSort, rec, listVec); addRecordToAttrSort(trec.attrSort, rec, listVec); } // Add a new record into the attr value table. private void addRecordToAttrValueSort(Hashtable table, ServiceRecordInMemory rec, Vector listVec) { Vector attrList = rec.getAttrList(); int i, n = attrList.size(); // Go through the attribute list. for (i = 0; i < n; i++) { ServerAttribute attr = (ServerAttribute)attrList.elementAt(i); AttributeString tag = attr.idPattern; Vector values = attr.values; // If a type table record exists, use it. Otherwise, // create a newly initialized one. Hashtable ttable = (Hashtable)table.get(tag); if (ttable == null) { ttable = makeAttrTypeTable(); table.put(tag, ttable); } // Get the class of values. String typeKey = null; if (values == null) { // We're done, since there are no attributes to add. continue; } else { Object val = values.elementAt(0); typeKey = val.getClass().getName(); } // Get the BtreeVector. BtreeVector bvec = (BtreeVector)ttable.get(typeKey); // Insert a record for each value. int j, m = values.size(); for (j = 0; j < m; j++) { List elem = bvec.add(values.elementAt(j), rec); // Put the element into the deletion table. listVec.addElement(elem); } } } // Return a newly initialized attribute type table. It will // have a hash for each allowed type, with a new BtreeVector // attached. private Hashtable makeAttrTypeTable() { Hashtable ret = new Hashtable(); ret.put(INTEGER_TYPE, new BtreeVector()); ret.put(ATTRIBUTE_STRING_TYPE, new BtreeVector()); ret.put(BOOLEAN_TYPE, new BtreeVector()); ret.put(OPAQUE_TYPE, new BtreeVector()); return ret; } // Add a new record into the attrs table. private void addRecordToAttrSort(BtreeVector table, ServiceRecordInMemory rec, Vector listVec) { Vector attrList = rec.getAttrList(); int i, n = attrList.size(); // If no attributes, then add with empty string as // the attribute tag. if (n <= 0) { List elem = table.add(new AttributeString("", rec.getLocale()), rec); listVec.addElement(elem); return; } // Iterate through the attribute list, adding to the // BtreeVector with attribute as the sort key. for (i = 0; i < n; i++) { ServerAttribute attr = (ServerAttribute)attrList.elementAt(i); List elem = table.add(attr.idPattern, rec); // Save for deletion. listVec.addElement(elem); } } // Add a record to the ageOut queue. private List addToAgeOutQueue(ServiceRecordInMemory rec) { Long exTime = new Long(rec.getExpirationTime()); return ageOutQueue.add(exTime, rec); } // Remove the URL record from the database. private void deregisterInternal(ServiceURL url, Vector scopes, String lang) { ServiceType type = url.getServiceType(); // To deregister, we only need to find the Vector of List objects // containing the places where this registration is hooked into // lists and unhook them. Garbage collection of other structures // is handled during insertion or in deregisterTypeLocale(), // if there are no more registrations at all. // Find the scope table.. Hashtable scopeLangTable = (Hashtable)urlScopeLangTable.get(url.toString()); // If it's not there, then maybe not registered. if (scopeLangTable == null) { return; } // For each scope, find the lang table. int i, n = scopes.size(); for (i = 0; i < n; i++) { String scope = (String)scopes.elementAt(i); Hashtable langTable = (Hashtable)scopeLangTable.get(scope); if (langTable == null) { continue; } // If the locale is non-null, then just deregister from this // locale. if (lang != null) { deregisterFromLocale(langTable, lang); // Record the deletion in the scope/type table, and // also the number of regs table. deleteTypeLocale(type.toString(), scope, lang); // Check for abstract type as well. if (type.isAbstractType()) { deleteTypeLocale(type.getAbstractTypeName(), scope, lang); } } else { // Otherwise, deregister all languages. Enumeration en = langTable.keys(); while (en.hasMoreElements()) { lang = (String)en.nextElement(); deregisterFromLocale(langTable, lang); // Record the deletion in the scope/type table, and // also the number of regs table. deleteTypeLocale(type.toString(), scope, lang); // Check for abstract type as well. if (type.isAbstractType()) { deleteTypeLocale(type.getAbstractTypeName(), scope, lang); } } } // If the table is empty, then remove the lang table. if (langTable.size() <= 0) { scopeLangTable.remove(scope); } } // If all languages were deleted, delete the // urlScopeLangTable record. Other GC handled in // deleteTypeLocale(). if (scopeLangTable.size() <= 0) { urlScopeLangTable.remove(url.toString()); } } // Deregister a single locale from the language table. private void deregisterFromLocale(Hashtable langTable, String lang) { // Get the Vector containing the list of registrations. Vector regList = (Vector)langTable.get(lang); Assert.slpassert(regList != null, "ssim_null_reg_vector", new Object[] {lang}); // Walk down the list of registrations and unhook them from // their respective lists. int i, n = regList.size(); for (i = 0; i < n; i++) { List elem = (List)regList.elementAt(i); elem.delete(); } // Remove the locale record. langTable.remove(lang); } // Find an existing record matching the URL by searching in all scopes. // The record will be the same for all scopes in the same language. // If locale is null, return any. If there are none, return null. private ServiceRecordInMemory findExistingRecord(ServiceURL surl, Vector scopes, String lang) { ServiceRecordInMemory rec = null; // Look in urlScopeLangTable. Hashtable scopeLevel = (Hashtable)urlScopeLangTable.get(surl.toString()); if (scopeLevel != null) { // If scopes is null, then perform the search for all // scopes in the table. Otherwise perform it for // all scopes incoming. Enumeration en = null; if (scopes == null) { en = scopeLevel.keys(); } else { en = scopes.elements(); } while (en.hasMoreElements()) { String scope = (String)en.nextElement(); Hashtable langLevel = (Hashtable)scopeLevel.get(scope); // If no langLevel table, continue searching. if (langLevel == null) { continue; } Vector listVec = null; // Use lang tag if we have it, otherwise, pick arbitrary. if (lang != null) { listVec = (Vector)langLevel.get(lang); } else { Enumeration llen = langLevel.elements(); listVec = (Vector)llen.nextElement(); } // If none for this locale, try the next scope. if (listVec == null) { continue; } // Select out the record. List elem = (List)listVec.elementAt(0); rec = elem.record; break; } } return rec; } // Find attributes matching the record and place the matching attributes // into the vector. Use the hashtable for collation. static void findMatchingAttributes(ServiceRecordInMemory rec, Vector attrTags, Hashtable ht, Vector ret) throws ServiceLocationException { int len = attrTags.size(); Vector attrList = rec.getAttrList(); // For each attribute, go through the tag vector If an attribute // matches, merge it into the return vector. int i, n = attrList.size(); for (i = 0; i < n; i++) { ServerAttribute attr = (ServerAttribute)attrList.elementAt(i); // All attributes match if the pattern vector is // empty. if (len <= 0) { saveValueIfMatch(attr, null, ht, ret); } else { // Check each pattern against the attribute id. int j; for (j = 0; j < len; j++) { AttributePattern attrTag = (AttributePattern)attrTags.elementAt(j); saveValueIfMatch(attr, attrTag, ht, ret); } } } } // Check the attribute against the pattern. If the pattern is null, // then match occurs. Merge the attribute into the vector // if match. static private void saveValueIfMatch(ServerAttribute attr, AttributePattern attrTag, Hashtable ht, Vector ret) throws ServiceLocationException { AttributeString id = attr.idPattern; // We save the attribute value if either // the pattern is null or it matches the attribute id. if (attrTag == null || attrTag.match(id)) { Vector values = attr.getValues(); // Create new values vector so record copy isn't // modified. if (values != null) { values = (Vector)values.clone(); } // Create new attribute so record copy isn't // modified. ServiceLocationAttribute nattr = new ServiceLocationAttribute(attr.getId(), values); // Merge duplicate attributes into vector. ServiceLocationAttribute.mergeDuplicateAttributes(nattr, ht, ret, true); } } // Check whether the incoming scopes are the same as existing // scopes. private void checkScopeStatus(ServiceURL surl, Vector scopes, short errCode) throws ServiceLocationException { // Drill down in the urlScopeLangTable table. Hashtable scopeLevel = (Hashtable)urlScopeLangTable.get(surl.toString()); if (scopeLevel == null) { return; // not yet registered... } // We need to have exactly the same scopes as in // the registration. int i, n = scopes.size(); boolean ok = true; if (n != scopeLevel.size()) { ok = false; } else { for (i = 0; i < n; i++) { if (scopeLevel.get(scopes.elementAt(i)) == null) { ok = false; break; } } } if (!ok) { throw new ServiceLocationException(errCode, "ssim_scope_mis", new Object[0]); } } // Check whether an existing nonservice URL is registered under // a different service type. private void checkForExistingUnderOtherServiceType(ServiceURL url, Vector scopes) throws ServiceLocationException { // Drill down in the urlScopeLangTable table. Hashtable scopeLevel = (Hashtable)urlScopeLangTable.get(url.toString()); if (scopeLevel == null) { return; // not yet registered. } // Get hashtable of locale records under scopes. Any scope // will do. Object scope = scopes.elementAt(0); Hashtable localeLevel = (Hashtable)scopeLevel.get(scope); Assert.slpassert(localeLevel != null, "ssim_null_lang_table", new Object[] {scope}); // Get a record from any locale. Enumeration en = localeLevel.elements(); Assert.slpassert(en.hasMoreElements(), "ssim_empty_lang_table", new Object[] {scope}); // Get vector of registrations. Vector vec = (Vector)en.nextElement(); Assert.slpassert(vec.size() > 0, "ssim_empty_reg_vector", new Object[] {scope}); List elem = (List)vec.elementAt(0); // OK, now check the registration. ServiceURL recURL = elem.record.getServiceURL(); ServiceType recType = recURL.getServiceType(); if (!recType.equals(url.getServiceType())) { throw new ServiceLocationException( ServiceLocationException.INVALID_UPDATE, "ssim_st_already", new Object[0]); } } // Merge old record into new record. final private void mergeOldRecordIntoNew(ServiceRecordInMemory oldRec, ServiceRecordInMemory newRec) throws ServiceLocationException { Vector newAttrs = newRec.getAttrList(); Vector oldAttrs = oldRec.getAttrList(); Hashtable ht = new Hashtable(); // Charge up the hashtable with the new attributes. int i, n = newAttrs.size(); for (i = 0; i < n; i++) { ServerAttribute attr = (ServerAttribute)newAttrs.elementAt(i); ht.put(attr.getId().toLowerCase(), attr); } // Merge in the old attributes. n = oldAttrs.size(); for (i = 0; i < n; i++) { ServerAttribute attr = (ServerAttribute)oldAttrs.elementAt(i); if (ht.get(attr.getId().toLowerCase()) == null) { newAttrs.addElement(attr); } } // Change the attribute vector on the rec. newRec.setAttrList(newAttrs); // Merge old scopes into new. Vector oldScopes = oldRec.getScopes(); Vector newScopes = newRec.getScopes(); int j, m = oldScopes.size(); for (j = 0; j < m; j++) { String scope = (String)oldScopes.elementAt(j); if (!newScopes.contains(scope)) { newScopes.addElement(scope); } } // Note that we don't have to merge security because there // will never be an incremental update to a record // in a protected scope. // Change the scope vector on the rec. newRec.setScopes(newScopes); } // Delete attributes matching attrTags. private void deleteAttributes(ServiceRecordInMemory rec, Vector attrTags) throws ServiceLocationException { // For each attribute, go through the tag vector and put attributes // that do not match the tags into the new attribute vector. Vector attrList = rec.getAttrList(); // If there are no attributes for this one, then simply return. if (attrList.size() <= 0) { return; } int i, n = attrList.size(); Vector newAttrList = new Vector(); int len = attrTags.size(); for (i = 0; i < n; i++) { ServerAttribute attr = (ServerAttribute)attrList.elementAt(i); AttributeString id = attr.idPattern; boolean deleteIt = false; int j; // Now check the tags. for (j = 0; j < len; j++) { AttributePattern attrTag = (AttributePattern)attrTags.elementAt(j); // If there's a match, mark for deletion. if (attrTag.match(id)) { deleteIt = true; break; } } if (!deleteIt) { newAttrList.addElement(attr); } } // Replace the attribute vector in the record. rec.setAttrList(newAttrList); } // Convert a vector of attribute tag strings to attribute pattern objects. private Vector stringVectorToAttributePattern(Vector tags, Locale locale) throws ServiceLocationException { // Takes care of findAttributes() case where no vector. if (tags == null) { return null; } Vector v = new Vector(); int i, n = tags.size(); for (i = 0; i < n; i++) { String value = (String)tags.elementAt(i); AttributePattern tag = new AttributePattern(value, locale); if (!v.contains(tag)) { v.addElement(tag); } } return v; } // // Output of service store to log. // // Write record to config log file. private void writeRecordToLog(SLPConfig conf, ServiceStore.ServiceRecord rec) { Locale locale = rec.getLocale(); ServiceURL surl = rec.getServiceURL(); Vector scopes = rec.getScopes(); Vector attributes = rec.getAttrList(); long exTime = rec.getExpirationTime(); Hashtable urlSig = rec.getURLSignature(); Hashtable attrSig = rec.getAttrSignature(); conf.writeLogLine("ssim_dump_entry_start", new Object[0]); conf.writeLogLine("ssim_dump_entry", new Object[] { locale, surl.toString(), Integer.toString(surl.getLifetime()), Long.toString(((exTime - System.currentTimeMillis())/1000)), surl.getServiceType(), scopes, attributes}); if (urlSig != null) { conf.writeLogLine("ssim_dump_urlsig", new Object[] {urlSig}); } if (attrSig != null) { conf.writeLogLine("ssim_dump_attrsig", new Object[] { attrSig}); } conf.writeLogLine("ssim_entry_end", new Object[0]); } // // Utilities for dealing with service type/scope locale table. // // Bump up the number of registrations for this service type, scope and // locale. private void addTypeLocale(String type, String scope, String lang) { String sstKey = makeScopeTypeKey(scope, type); // Get any existing record. Hashtable langTable = (Hashtable)sstLocales.get(sstKey); // Insert a new one if none there. if (langTable == null) { langTable = new Hashtable(); sstLocales.put(sstKey, langTable); } // Look up locale. Integer numRegs = (Integer)langTable.get(lang); // Add a new one if none there, otherwise, bump up old. if (numRegs == null) { numRegs = new Integer(1); } else { numRegs = new Integer(numRegs.intValue() + 1); } // Put it back. langTable.put(lang, numRegs); } // Bump down the number of registrations for this service type, scope, // in all locales. private void deleteTypeLocale(String type, String scope, String lang) { String sstKey = makeScopeTypeKey(scope, type); // Get any existing record. Hashtable langTable = (Hashtable)sstLocales.get(sstKey); // If none there, then error. But this should have been caught // during deletion, so it's fatal. Assert.slpassert(langTable != null, "ssim_ssttable_botch", new Object[] { type, scope}); // Get the Integer object recording the number of registrations. Integer numRegs = (Integer)langTable.get(lang); Assert.slpassert(numRegs != null, "ssim_ssttable_lang_botch", new Object[] { lang, type, scope}); // Bump down by one, remove if zero. numRegs = new Integer(numRegs.intValue() - 1); if (numRegs.intValue() <= 0) { langTable.remove(lang); if (langTable.size() <= 0) { sstLocales.remove(sstKey); } // Garbage collection. // Remove records from the scopeTypeLangTable, // since there are no registrations left for this // type/scope/locale. String stlKey = makeScopeTypeLangKey(scope, type, lang); scopeTypeLangTable.remove(stlKey); } else { // Put it back. langTable.put(lang, numRegs); } } // Return REGS if the language is supported. Supported means that the // there are some registrations of this service type in it or that // there are none in any locale. Return NO_REGS if there are absolutely // no registrations whatsoever, in any language. Return NO_REGS_IN_LOCALE // if there are no registrations in that language but there are in // others. private int languageSupported(String type, Vector scopes, String lang) { // Look through scope vector. boolean otherLangRegs = false; boolean sameLangRegs = false; int i, n = scopes.size(); for (i = 0; i < n; i++) { String scope = (String)scopes.elementAt(i); String sstKey = makeScopeTypeKey(scope, type); // Get any existing record. Hashtable langTable = (Hashtable)sstLocales.get(sstKey); // If there are no regs, then check next scope. if (langTable == null) { continue; } Object numRegs = langTable.get(lang); // Check whether there are other language regs // or same language regs. if (numRegs == null) { otherLangRegs = true; } else { sameLangRegs = true; } } // Return appropriate code. if (otherLangRegs == false && sameLangRegs == false) { return NO_REGS; } else if (otherLangRegs == true && sameLangRegs == false) { return NO_REGS_IN_LOCALE; } else { return REGS_IN_LOCALE; } } // // Hash key calculations and hash table structuring. // // Return a key for type and scope. private String makeScopeTypeKey(String scope, String type) { return scope + "/" + type; } // Make a hash key consisting of the scope and service type. final private String makeScopeTypeLangKey(String scope, String serviceType, String lang) { return scope + "/" + serviceType + "/" + lang; } // Return the key's scope. final private String keyScope(String key) { int idx = key.indexOf('/'); String ret = ""; if (idx > 0) { ret = key.substring(0, idx); } return ret; } // Return the key's service type/NA. final private String keyServiceType(String key) { int idx = key.indexOf('/'); String ret = ""; int len = key.length(); if (idx >= 0 && idx < len - 1) { ret = key.substring(idx+1, len); } // Parse off the final lang. idx = ret.indexOf('/'); ret = ret.substring(0, idx); return ret; } // Return true if the record is for an abstract type. final private boolean isAbstractTypeRecord(String sstKey) { STLRecord rec = (STLRecord)scopeTypeLangTable.get(sstKey); return rec.isAbstract; } }