ServiceStoreInMemory.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 2001,2003 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
*/
// SCCS Status: %W% %G%
// 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.
* <details of those structures here>
*
* @version %R%.%L% %D%
* @author James Kempf
*/
class ServiceStoreInMemory extends Object implements ServiceStore {
/**
* The BVCollector interface allows various
* data structures to collect stuff from the BtreeVector.
*
* @version %R%.%L% %D%
* @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.
*
* @version %R%.%L% %D%
* @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.
*
* @version %R%.%L% %D%
* @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.
*
* @version %R%.%L% %D%
* @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.
*
* @version %R%.%L% %D%
* @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.
*
* @version %R%.%L% %D%
* @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.
*
* @version %R%.%L% %D%
* @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 "<RegRecord value="+value+"list="+head.next+">";
}
}
/**
* The BtreeVector class stores registrations in sorted order. The
* Quicksort algorithm is used to insert items and search for something.
*
* @version %R%.%L% %D%
* @author James Kempf
*/
private class BtreeVector extends Object {
// Contains the sorted vector.
private Vector contents = new Vector();
public String toString() {
return "<BtreeVector "+contents.toString()+">";
}
// 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.
*
* @version %R%.%L% %D%
* @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.
*
* @version %R%.%L% %D%
* @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,
*
* @version %R%.%L% %D%
* @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;
}
}