Path: blob/master/test/jdk/sun/net/www/protocol/https/TestHttpsServer.java
41159 views
/*1* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.net.*;24import java.io.*;25import java.nio.*;26import java.nio.channels.*;27import sun.net.www.MessageHeader;28import java.util.*;29import javax.net.ssl.*;30import javax.net.ssl.SSLEngineResult.*;31import java.security.*;3233/**34* This class implements a simple HTTPS server. It uses multiple threads to35* handle connections in parallel, and will spin off a new thread to handle36* each request. (this is easier to implement with SSLEngine)37* <p>38* It must be instantiated with a {@link HttpCallback} object to which39* requests are given and must be handled.40* <p>41* Simple synchronization between the client(s) and server can be done42* using the {@link #waitForCondition(String)}, {@link #setCondition(String)} and43* {@link #rendezvous(String,int)} methods.44*45* NOTE NOTE NOTE NOTE NOTE NOTE NOTE46*47* If you make a change in here, please don't forget to make the48* corresponding change in the J2SE equivalent.49*50* NOTE NOTE NOTE NOTE NOTE NOTE NOTE51*/5253public class TestHttpsServer {5455ServerSocketChannel schan;56int threads;57int cperthread;58HttpCallback cb;59Server[] servers;6061// ssl related fields62static SSLContext sslCtx;6364/**65* Create a <code>TestHttpsServer<code> instance with the specified callback object66* for handling requests. One thread is created to handle requests,67* and up to ten TCP connections will be handled simultaneously.68* @param cb the callback object which is invoked to handle each69* incoming request70*/7172public TestHttpsServer(HttpCallback cb) throws IOException {73this(cb, 1, 10, 0);74}7576/**77* Create a <code>TestHttpsServer<code> instance with the specified number of78* threads and maximum number of connections per thread. This functions79* the same as the 4 arg constructor, where the port argument is set to zero.80* @param cb the callback object which is invoked to handle each81* incoming request82* @param threads the number of threads to create to handle requests83* in parallel84* @param cperthread the number of simultaneous TCP connections to85* handle per thread86*/8788public TestHttpsServer(HttpCallback cb, int threads, int cperthread)89throws IOException {90this(cb, threads, cperthread, 0);91}9293/**94* Create a <code>TestHttpsServer<code> instance with the specified number95* of threads and maximum number of connections per thread and running on96* the specified port. The specified number of threads are created to97* handle incoming requests, and each thread is allowed98* to handle a number of simultaneous TCP connections.99* @param cb the callback object which is invoked to handle100* each incoming request101* @param threads the number of threads to create to handle102* requests in parallel103* @param cperthread the number of simultaneous TCP connections104* to handle per thread105* @param port the port number to bind the server to. <code>Zero</code>106* means choose any free port.107*/108public TestHttpsServer(HttpCallback cb, int threads, int cperthread, int port)109throws IOException {110this(cb, threads, cperthread, null, port);111}112113/**114* Create a <code>TestHttpsServer<code> instance with the specified number115* of threads and maximum number of connections per thread and running on116* the specified port. The specified number of threads are created to117* handle incoming requests, and each thread is allowed118* to handle a number of simultaneous TCP connections.119* @param cb the callback object which is invoked to handle120* each incoming request121* @param threads the number of threads to create to handle122* requests in parallel123* @param cperthread the number of simultaneous TCP connections124* to handle per thread125* @param address the InetAddress to bind to. {@code Null} means the126* wildcard address.127* @param port the port number to bind the server to. {@code Zero}128* means choose any free port.129*/130131public TestHttpsServer(HttpCallback cb, int threads, int cperthread, InetAddress address, int port)132throws IOException {133schan = ServerSocketChannel.open();134InetSocketAddress addr = new InetSocketAddress(address, port);135schan.socket().bind(addr);136this.threads = threads;137this.cb = cb;138this.cperthread = cperthread;139140try {141// create and initialize a SSLContext142KeyStore ks = KeyStore.getInstance("JKS");143KeyStore ts = KeyStore.getInstance("JKS");144char[] passphrase = "passphrase".toCharArray();145146ks.load(new FileInputStream(System.getProperty("javax.net.ssl.keyStore")), passphrase);147ts.load(new FileInputStream(System.getProperty("javax.net.ssl.trustStore")), passphrase);148149KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");150kmf.init(ks, passphrase);151152TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");153tmf.init(ts);154155sslCtx = SSLContext.getInstance("TLS");156157sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);158159servers = new Server[threads];160for (int i=0; i<threads; i++) {161servers[i] = new Server(cb, schan, cperthread);162servers[i].start();163}164} catch (Exception ex) {165throw new RuntimeException("test failed. cause: "+ex.getMessage());166}167}168169/** Tell all threads in the server to exit within 5 seconds.170* This is an abortive termination. Just prior to the thread exiting171* all channels in that thread waiting to be closed are forceably closed.172*/173174public void terminate() {175for (int i=0; i<threads; i++) {176servers[i].terminate ();177}178}179180/**181* return the local port number to which the server is bound.182* @return the local port number183*/184185public int getLocalPort () {186return schan.socket().getLocalPort ();187}188189public String getAuthority() {190InetAddress address = schan.socket().getInetAddress();191String hostaddr = address.getHostAddress();192if (address.isAnyLocalAddress()) hostaddr = "localhost";193if (hostaddr.indexOf(':') > -1) hostaddr = "[" + hostaddr + "]";194return hostaddr + ":" + getLocalPort();195}196197static class Server extends Thread {198199ServerSocketChannel schan;200Selector selector;201SelectionKey listenerKey;202SelectionKey key; /* the current key being processed */203HttpCallback cb;204ByteBuffer consumeBuffer;205int maxconn;206int nconn;207ClosedChannelList clist;208boolean shutdown;209210Server(HttpCallback cb, ServerSocketChannel schan, int maxconn) {211this.schan = schan;212this.maxconn = maxconn;213this.cb = cb;214nconn = 0;215consumeBuffer = ByteBuffer.allocate(512);216clist = new ClosedChannelList();217try {218selector = Selector.open();219schan.configureBlocking(false);220listenerKey = schan.register(selector, SelectionKey.OP_ACCEPT);221} catch (IOException e) {222System.err.println("Server could not start: " + e);223}224}225226/* Stop the thread as soon as possible */227public synchronized void terminate() {228shutdown = true;229}230231public void run() {232try {233while (true) {234selector.select(1000);235Set selected = selector.selectedKeys();236Iterator iter = selected.iterator();237while (iter.hasNext()) {238key = (SelectionKey)iter.next();239if (key.equals (listenerKey)) {240SocketChannel sock = schan.accept();241if (sock == null) {242/* false notification */243iter.remove();244continue;245}246sock.configureBlocking(true);247SSLEngine sslEng = sslCtx.createSSLEngine();248sslEng.setUseClientMode(false);249new ServerWorker(cb, sock, sslEng).start();250nconn ++;251if (nconn == maxconn) {252/* deregister */253listenerKey.cancel();254listenerKey = null;255}256} else {257if (key.isReadable()) {258boolean closed = false;259SocketChannel chan = (SocketChannel)key.channel();260if (key.attachment() != null) {261closed = consume(chan);262}263264if (closed) {265chan.close();266key.cancel();267if (nconn == maxconn) {268listenerKey = schan.register(selector, SelectionKey.OP_ACCEPT);269}270nconn --;271}272}273}274iter.remove();275}276clist.check();277278synchronized (this) {279if (shutdown) {280clist.terminate();281return;282}283}284}285} catch (IOException e) {286System.out.println("Server exception: " + e);287// TODO finish288}289}290291/* read all the data off the channel without looking at it292* return true if connection closed293*/294boolean consume(SocketChannel chan) {295try {296consumeBuffer.clear();297int c = chan.read(consumeBuffer);298if (c == -1)299return true;300} catch (IOException e) {301return true;302}303return false;304}305}306307static class ServerWorker extends Thread {308private ByteBuffer inNetBB;309private ByteBuffer outNetBB;310private ByteBuffer inAppBB;311private ByteBuffer outAppBB;312313SSLEngine sslEng;314SocketChannel schan;315HttpCallback cb;316HandshakeStatus currentHSStatus;317boolean initialHSComplete;318boolean handshakeStarted;319/*320* All inbound data goes through this buffer.321*322* It might be nice to use a cache of ByteBuffers so we're323* not alloc/dealloc'ing all over the place.324*/325326/*327* Application buffers, also used for handshaking328*/329private int appBBSize;330331ServerWorker(HttpCallback cb, SocketChannel schan, SSLEngine sslEng) {332this.sslEng = sslEng;333this.schan = schan;334this.cb = cb;335currentHSStatus = HandshakeStatus.NEED_UNWRAP;336initialHSComplete = false;337int netBBSize = sslEng.getSession().getPacketBufferSize();338inNetBB = ByteBuffer.allocate(netBBSize);339outNetBB = ByteBuffer.allocate(netBBSize);340appBBSize = sslEng.getSession().getApplicationBufferSize();341inAppBB = ByteBuffer.allocate(appBBSize);342outAppBB = ByteBuffer.allocate(appBBSize);343}344345public SSLEngine getSSLEngine() {346return sslEng;347}348349public ByteBuffer outNetBB() {350return outNetBB;351}352353public ByteBuffer outAppBB() {354return outAppBB;355}356357public void run () {358try {359SSLEngineResult result;360361while (!initialHSComplete) {362363switch (currentHSStatus) {364365case NEED_UNWRAP:366int bytes = schan.read(inNetBB);367if (!handshakeStarted && bytes > 0) {368handshakeStarted = true;369int byte0 = inNetBB.get(0);370if (byte0 != 0x16) {371// first byte of a TLS connection is supposed to be372// 0x16. If not it may be a plain text connection.373//374// Sometime a rogue client may try to open a plain375// connection with our server. Calling this method376// gives a chance to the test logic to ignore such377// rogue connections.378//379if (cb.dropPlainTextConnections()) {380try { schan.close(); } catch (IOException x) { };381return;382}383// else sslEng.unwrap will throw later on...384}385}386387needIO:388while (currentHSStatus == HandshakeStatus.NEED_UNWRAP) {389/*390* Don't need to resize requestBB, since no app data should391* be generated here.392*/393inNetBB.flip();394result = sslEng.unwrap(inNetBB, inAppBB);395inNetBB.compact();396currentHSStatus = result.getHandshakeStatus();397398switch (result.getStatus()) {399400case OK:401switch (currentHSStatus) {402case NOT_HANDSHAKING:403throw new IOException(404"Not handshaking during initial handshake");405406case NEED_TASK:407Runnable task;408while ((task = sslEng.getDelegatedTask()) != null) {409task.run();410currentHSStatus = sslEng.getHandshakeStatus();411}412break;413}414415break;416417case BUFFER_UNDERFLOW:418break needIO;419420default: // BUFFER_OVERFLOW/CLOSED:421throw new IOException("Received" + result.getStatus() +422"during initial handshaking");423}424}425426/*427* Just transitioned from read to write.428*/429if (currentHSStatus != HandshakeStatus.NEED_WRAP) {430break;431}432433// Fall through and fill the write buffer.434435case NEED_WRAP:436/*437* The flush above guarantees the out buffer to be empty438*/439outNetBB.clear();440result = sslEng.wrap(inAppBB, outNetBB);441outNetBB.flip();442schan.write (outNetBB);443outNetBB.compact();444currentHSStatus = result.getHandshakeStatus();445446switch (result.getStatus()) {447case OK:448449if (currentHSStatus == HandshakeStatus.NEED_TASK) {450Runnable task;451while ((task = sslEng.getDelegatedTask()) != null) {452task.run();453currentHSStatus = sslEng.getHandshakeStatus();454}455}456457break;458459default: // BUFFER_OVERFLOW/BUFFER_UNDERFLOW/CLOSED:460throw new IOException("Received" + result.getStatus() +461"during initial handshaking");462}463break;464465case FINISHED:466initialHSComplete = true;467break;468default: // NOT_HANDSHAKING/NEED_TASK469throw new RuntimeException("Invalid Handshaking State" +470currentHSStatus);471} // switch472}473// read the application data; using non-blocking mode474schan.configureBlocking(false);475read(schan, sslEng);476} catch (Exception ex) {477throw new RuntimeException(ex);478}479}480481/* return true if the connection is closed, false otherwise */482483private boolean read(SocketChannel chan, SSLEngine sslEng) {484HttpTransaction msg;485boolean res;486try {487InputStream is = new BufferedInputStream(new NioInputStream(chan, sslEng, inNetBB, inAppBB));488String requestline = readLine(is);489MessageHeader mhead = new MessageHeader(is);490String clen = mhead.findValue("Content-Length");491String trferenc = mhead.findValue("Transfer-Encoding");492String data = null;493if (trferenc != null && trferenc.equals("chunked"))494data = new String(readChunkedData(is));495else if (clen != null)496data = new String(readNormalData(is, Integer.parseInt(clen)));497String[] req = requestline.split(" ");498if (req.length < 2) {499/* invalid request line */500return false;501}502String cmd = req[0];503URI uri = null;504try {505uri = new URI(req[1]);506msg = new HttpTransaction(this, cmd, uri, mhead, data, null, chan);507cb.request(msg);508} catch (URISyntaxException e) {509System.err.println ("Invalid URI: " + e);510msg = new HttpTransaction(this, cmd, null, null, null, null, chan);511msg.sendResponse(501, "Whatever");512}513res = false;514} catch (IOException e) {515res = true;516}517return res;518}519520byte[] readNormalData(InputStream is, int len) throws IOException {521byte[] buf = new byte[len];522int c, off=0, remain=len;523while (remain > 0 && ((c=is.read (buf, off, remain))>0)) {524remain -= c;525off += c;526}527return buf;528}529530private void readCRLF(InputStream is) throws IOException {531int cr = is.read();532int lf = is.read();533534if (((cr & 0xff) != 0x0d) ||535((lf & 0xff) != 0x0a)) {536throw new IOException(537"Expected <CR><LF>: got '" + cr + "/" + lf + "'");538}539}540541byte[] readChunkedData(InputStream is) throws IOException {542LinkedList l = new LinkedList();543int total = 0;544for (int len=readChunkLen(is); len!=0; len=readChunkLen(is)) {545l.add(readNormalData(is, len));546total += len;547readCRLF(is); // CRLF at end of chunk548}549readCRLF(is); // CRLF at end of Chunked Stream.550byte[] buf = new byte[total];551Iterator i = l.iterator();552int x = 0;553while (i.hasNext()) {554byte[] b = (byte[])i.next();555System.arraycopy(b, 0, buf, x, b.length);556x += b.length;557}558return buf;559}560561private int readChunkLen(InputStream is) throws IOException {562int c, len=0;563boolean done=false, readCR=false;564while (!done) {565c = is.read();566if (c == '\n' && readCR) {567done = true;568} else {569if (c == '\r' && !readCR) {570readCR = true;571} else {572int x=0;573if (c >= 'a' && c <= 'f') {574x = c - 'a' + 10;575} else if (c >= 'A' && c <= 'F') {576x = c - 'A' + 10;577} else if (c >= '0' && c <= '9') {578x = c - '0';579}580len = len * 16 + x;581}582}583}584return len;585}586587private String readLine(InputStream is) throws IOException {588boolean done=false, readCR=false;589byte[] b = new byte[512];590int c, l = 0;591592while (!done) {593c = is.read();594if (c == '\n' && readCR) {595done = true;596} else {597if (c == '\r' && !readCR) {598readCR = true;599} else {600b[l++] = (byte)c;601}602}603}604return new String(b);605}606607/** close the channel associated with the current key by:608* 1. shutdownOutput (send a FIN)609* 2. mark the key so that incoming data is to be consumed and discarded610* 3. After a period, close the socket611*/612613synchronized void orderlyCloseChannel(SocketChannel ch) throws IOException {614ch.socket().shutdownOutput();615}616617synchronized void abortiveCloseChannel(SocketChannel ch) throws IOException {618Socket s = ch.socket();619s.setSoLinger(true, 0);620ch.close();621}622}623624625/**626* Implements blocking reading semantics on top of a non-blocking channel627*/628629static class NioInputStream extends InputStream {630SSLEngine sslEng;631SocketChannel channel;632Selector selector;633ByteBuffer inNetBB;634ByteBuffer inAppBB;635SelectionKey key;636int available;637byte[] one;638boolean closed;639ByteBuffer markBuf; /* reads may be satisifed from this buffer */640boolean marked;641boolean reset;642int readlimit;643644public NioInputStream(SocketChannel chan, SSLEngine sslEng, ByteBuffer inNetBB, ByteBuffer inAppBB) throws IOException {645this.sslEng = sslEng;646this.channel = chan;647selector = Selector.open();648this.inNetBB = inNetBB;649this.inAppBB = inAppBB;650key = chan.register(selector, SelectionKey.OP_READ);651available = 0;652one = new byte[1];653closed = marked = reset = false;654}655656public synchronized int read(byte[] b) throws IOException {657return read(b, 0, b.length);658}659660public synchronized int read() throws IOException {661return read(one, 0, 1);662}663664public synchronized int read(byte[] b, int off, int srclen) throws IOException {665666int canreturn, willreturn;667668if (closed)669return -1;670671if (reset) { /* satisfy from markBuf */672canreturn = markBuf.remaining();673willreturn = canreturn > srclen ? srclen : canreturn;674markBuf.get(b, off, willreturn);675if (canreturn == willreturn) {676reset = false;677}678} else { /* satisfy from channel */679canreturn = available();680if (canreturn == 0) {681block();682canreturn = available();683}684willreturn = canreturn > srclen ? srclen : canreturn;685inAppBB.get(b, off, willreturn);686available -= willreturn;687688if (marked) { /* copy into markBuf */689try {690markBuf.put(b, off, willreturn);691} catch (BufferOverflowException e) {692marked = false;693}694}695}696return willreturn;697}698699public synchronized int available() throws IOException {700if (closed)701throw new IOException("Stream is closed");702703if (reset)704return markBuf.remaining();705706if (available > 0)707return available;708709inAppBB.clear();710int bytes = channel.read(inNetBB);711712int needed = sslEng.getSession().getApplicationBufferSize();713if (needed > inAppBB.remaining()) {714inAppBB = ByteBuffer.allocate(needed);715}716inNetBB.flip();717SSLEngineResult result = sslEng.unwrap(inNetBB, inAppBB);718inNetBB.compact();719available = result.bytesProduced();720721if (available > 0)722inAppBB.flip();723else if (available == -1)724throw new IOException("Stream is closed");725return available;726}727728/**729* block() only called when available==0 and buf is empty730*/731private synchronized void block() throws IOException {732//assert available == 0;733int n = selector.select();734//assert n == 1;735selector.selectedKeys().clear();736available();737}738739public void close() throws IOException {740if (closed)741return;742channel.close();743closed = true;744}745746public synchronized void mark(int readlimit) {747if (closed)748return;749this.readlimit = readlimit;750markBuf = ByteBuffer.allocate(readlimit);751marked = true;752reset = false;753}754755public synchronized void reset() throws IOException {756if (closed )757return;758if (!marked)759throw new IOException("Stream not marked");760marked = false;761reset = true;762markBuf.flip();763}764}765766static class NioOutputStream extends OutputStream {767SSLEngine sslEng;768SocketChannel channel;769ByteBuffer outNetBB;770ByteBuffer outAppBB;771SelectionKey key;772Selector selector;773boolean closed;774byte[] one;775776public NioOutputStream(SocketChannel channel, SSLEngine sslEng, ByteBuffer outNetBB, ByteBuffer outAppBB) throws IOException {777this.sslEng = sslEng;778this.channel = channel;779this.outNetBB = outNetBB;780this.outAppBB = outAppBB;781selector = Selector.open();782key = channel.register(selector, SelectionKey.OP_WRITE);783closed = false;784one = new byte[1];785}786787public synchronized void write(int b) throws IOException {788one[0] = (byte)b;789write(one, 0, 1);790}791792public synchronized void write(byte[] b) throws IOException {793write(b, 0, b.length);794}795796public synchronized void write(byte[] b, int off, int len) throws IOException {797if (closed)798throw new IOException("stream is closed");799800outAppBB = ByteBuffer.allocate(len);801outAppBB.put(b, off, len);802outAppBB.flip();803int n;804outNetBB.clear();805int needed = sslEng.getSession().getPacketBufferSize();806if (outNetBB.capacity() < needed) {807outNetBB = ByteBuffer.allocate(needed);808}809SSLEngineResult ret = sslEng.wrap(outAppBB, outNetBB);810outNetBB.flip();811int newLen = ret.bytesProduced();812while ((n = channel.write (outNetBB)) < newLen) {813newLen -= n;814if (newLen == 0)815return;816selector.select();817selector.selectedKeys().clear();818}819}820821public void close() throws IOException {822if (closed)823return;824channel.close();825closed = true;826}827}828829/**830* Utilities for synchronization. A condition is831* identified by a string name, and is initialized832* upon first use (ie. setCondition() or waitForCondition()). Threads833* are blocked until some thread calls (or has called) setCondition() for the same834* condition.835* <P>836* A rendezvous built on a condition is also provided for synchronizing837* N threads.838*/839840private static HashMap conditions = new HashMap();841842/*843* Modifiable boolean object844*/845private static class BValue {846boolean v;847}848849/*850* Modifiable int object851*/852private static class IValue {853int v;854IValue(int i) {855v =i;856}857}858859860private static BValue getCond(String condition) {861synchronized (conditions) {862BValue cond = (BValue) conditions.get(condition);863if (cond == null) {864cond = new BValue();865conditions.put(condition, cond);866}867return cond;868}869}870871/**872* Set the condition to true. Any threads that are currently blocked873* waiting on the condition, will be unblocked and allowed to continue.874* Threads that subsequently call waitForCondition() will not block.875* If the named condition did not exist prior to the call, then it is created876* first.877*/878879public static void setCondition(String condition) {880BValue cond = getCond(condition);881synchronized (cond) {882if (cond.v) {883return;884}885cond.v = true;886cond.notifyAll();887}888}889890/**891* If the named condition does not exist, then it is created and initialized892* to false. If the condition exists or has just been created and its value893* is false, then the thread blocks until another thread sets the condition.894* If the condition exists and is already set to true, then this call returns895* immediately without blocking.896*/897898public static void waitForCondition(String condition) {899BValue cond = getCond(condition);900synchronized (cond) {901if (!cond.v) {902try {903cond.wait();904} catch (InterruptedException e) {}905}906}907}908909/* conditions must be locked when accessing this */910static HashMap rv = new HashMap();911912/**913* Force N threads to rendezvous (ie. wait for each other) before proceeding.914* The first thread(s) to call are blocked until the last915* thread makes the call. Then all threads continue.916* <p>917* All threads that call with the same condition name, must use the same value918* for N (or the results may be not be as expected).919* <P>920* Obviously, if fewer than N threads make the rendezvous then the result921* will be a hang.922*/923924public static void rendezvous(String condition, int N) {925BValue cond;926IValue iv;927String name = "RV_"+condition;928929/* get the condition */930931synchronized (conditions) {932cond = (BValue)conditions.get(name);933if (cond == null) {934/* we are first caller */935if (N < 2) {936throw new RuntimeException("rendezvous must be called with N >= 2");937}938cond = new BValue();939conditions.put(name, cond);940iv = new IValue(N-1);941rv.put(name, iv);942} else {943/* already initialised, just decrement the counter */944iv = (IValue) rv.get(name);945iv.v--;946}947}948949if (iv.v > 0) {950waitForCondition(name);951} else {952setCondition(name);953synchronized (conditions) {954clearCondition(name);955rv.remove(name);956}957}958}959960/**961* If the named condition exists and is set then remove it, so it can962* be re-initialized and used again. If the condition does not exist, or963* exists but is not set, then the call returns without doing anything.964* Note, some higher level synchronization965* may be needed between clear and the other operations.966*/967968public static void clearCondition(String condition) {969BValue cond;970synchronized (conditions) {971cond = (BValue) conditions.get(condition);972if (cond == null) {973return;974}975synchronized (cond) {976if (cond.v) {977conditions.remove(condition);978}979}980}981}982}983984985