RemoteStream.java
/*
** Module : RemoteStream.java
** Public : represents a stream instance that is on a remote node and
** redirects all operations to that instance
**
** Copyright (c) 2006-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------
** 001 GES 20060211 @24553 Created initial version which provides
** represents a stream instance that is on a
** remote node and redirects all operations to
** that instance.
** 002 GES 20060217 @24673 Added writeByte().
** 003 GES 20060226 @24753 Added methods to remote the paging state.
** 004 GES 20060302 @24858 Changes to enable removal from the
** finalizable lists.
** 005 GES 20060817 @28660 Redirected cleanup processing to the client.
** 006 GES 20061206 @31582 Reduce the number of trips to the client
** since page size can only be set from the
** server, it can be cached and reused locally.
** 007 NVS 20070725 @34689 Implemented methods isIn() and isOut(), which
** are newly added to the base class as abstract.
** 008 NVS 20070731 @34754 Implemented advancePage() which overrides the
** method in the base class. This implementation
** delegates the call to the remote stream.
** 009 NVS 20070802 @34775 Implemented flush() which overrides the
** method in the base class. This implementation
** delegates the call to the remote stream.
** 010 SIY 20070928 @35325 Fix for PUT/DISPLAY handling regressions.
** Ref: #35314
** 011 SIY 20071003 @35354 Fix for NPE during remote call.
** Ref: #35347.
** 012 EVL 20071005 @35375 Fixed isOut() method.
** 013 SIY 20071009 @35407 Fix for delimiter handling regression.
** Ref: #35404
** 014 EVL 20071010 @35419 Change to isIn()/isOut to check the stream is
** valid.
** 015 SIY 20071017 @35489 Promoted setControl() method, added forgotten
** endOutput() method.
** Ref: #35486
** 016 GES 20071218 @36430 Switched to a safer method of determining if
** close was called from within finished()
** processing. This is needed because the
** stream level close can be called during
** finished processing of any arbitrary class
** and the previous method was only safe if
** called from within Stream.finished.
** 017 CA 20071228 @36616 Added method putUnformatted.
** 018 CA 20080304 @37294 Added method setUnnamed.
** 019 SIY 20080317 @37459 Added support for terminal stream.
** Ref: #37453
** 020 GES 20080412 @38020 Added method setConvert().
** 021 GES 20080425 @38122 Refactored to eliminate round trips to the
** client by passing more data in a single
** invocation. This improves performance.
** 022 GES 20080430 @38177 Removed unnecessary calls to endOutput().
** 023 SVL 20090612 @42676 Added setUnbuffered() method.
** 024 SIY 20090925 @44033 Added getNextLineNum() method.
** 025 SIY 20091009 @44129 Added caching of isOut/isIn in order to minimize
** round-trips to client.
** 026 SVL 20110403 Added write(byte[]) function.
** 027 GES 20110812 Added readChunk() method.
** 028 CA 20140730 Protected stream close/cleanup code from raised conditions, to allow
** close/cleanup fully complete even if conditions were raised.
** 029 GES 20141030 Added setConvertSource/Target() methods.
** 030 EVL 20160225 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 031 OM 20170313 Propagated [binary] property to peer stream. Added peekCh() method.
** 032 HC 20171024 Added FONT option to the OUTPUT TO PRINTER statement this is a FWD
** extension to the 4GL language.
** 033 ECF 20171026 Added write(byte[], int, int) method.
** 034 RFB 20200419 Added setKeepMessages in support of I/O option KEEP-MESSAGES.
** 035 CA 20210317 Improved performance of the readBlock(memptr) API.
** CA 20210709 Added APIs to read pristine bytes.
** CA 20220515 memptr and library calls are supported on server-side, too. Added TODO's for
** client-side APIs which will need to call back to server-side, when memptr is used
** as argument.
** EVL 20221212 Adding LOB-DIR runtime support.
** 036 PMP 20240509 Modified putWorker method to check for null field entries.
** 037 GBB 20240912 Accessing LowLevelStream via OSResourceManager. WorkArea field renamed.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.io.*;
import com.goldencode.p2j.security.ContextLocal;
import com.goldencode.p2j.util.osresource.*;
/**
* A stream class which represents a stream instance that is on a remote node
* and which redirects all local method calls into operations to that
* instance.
*
* @author GES
*/
public class RemoteStream
extends Stream
{
/** The stream id on the remote node. */
private int id = -1;
/** The locally cached page size. */
private int sz = -1;
/** Stores context-local state variables. */
private static ContextContainer work = new ContextContainer();
/** Mark terminal stream. */
private boolean term = false;
/** Control caching of IsOut flag. */
private boolean cacheIsOut = false;
/** Control caching of IsIn flag. */
private boolean cacheIsIn = false;
/** Cached IsOut flag. */
private boolean savedIsOut = false;
/** Cached IsIn flag. */
private boolean savedIsIn = false;
/**
* Create an instance to represent a specific remote stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
public RemoteStream(int id)
{
this.id = id;
}
/**
* Create an instance to represent a specific remote stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param term
* If <code>true</code> then stream ID belongs to terminal
* stream.
*/
public RemoteStream(int id, boolean term)
{
this(id);
this.term = term;
}
/**
* Check if given instance is bound to terminal.
*
* @return <code>true</code> if remote stream is a terminal.
*/
public boolean isTerm()
{
return term;
}
/**
* State of the input side of the stream.
*
* @return <code>true</code> if the input side of the stream is active.
*/
public boolean isIn()
{
if (id == -1)
return false;
if (cacheIsIn)
return savedIsIn;
cacheIsIn = true;
return savedIsIn = work.obtain().streamsAccessor.isIn(id);
}
/**
* State of the output side of the stream.
*
* @return <code>true</code> if the output side of the stream is active.
*/
public boolean isOut()
{
if (id == -1)
return false;
if (cacheIsOut)
return savedIsOut;
cacheIsOut = true;
return savedIsOut = work.obtain().streamsAccessor.isOut(id);
}
/**
* Get the stream's remote id.
*
* @return The remote id used to access the stream. -1 is an invalid
* stream id and indicates that the stream is not properly
* initialized.
*/
public int getId()
{
return id;
}
/**
* The number of bytes available to be immediately read without blocking.
*
* @return The number of available bytes.
*
* @throws IOException
*/
public long available()
throws IOException
{
return work.obtain().streamsAccessor.available(id);
}
/**
* The 0-based offset into the stream at which the next read or write will
* occur. This does not work for "streams" that require sequential
* access.
*
* @return The current position in the stream.
*
* @throws UnsupportedOperationException
* @throws IOException
*/
public long getPos()
throws UnsupportedOperationException,
IOException
{
return work.obtain().streamsAccessor.getPos(id);
}
/**
* Moves the current read/write position to the specified absolute 0-based
* offset.
*
* @param pos
* The new read/write position in the stream.
*
* @throws UnsupportedOperationException
* @throws IOException
*/
public void setPos(long pos)
throws UnsupportedOperationException,
IOException
{
work.obtain().streamsAccessor.setPos(id, pos);
}
/**
* The length of the stream in bytes. This does not work for "streams"
* that require sequential access.
*
* @return The length of the stream.
*
* @throws UnsupportedOperationException
* @throws IOException
*/
public long getLen()
throws UnsupportedOperationException,
IOException
{
return work.obtain().streamsAccessor.getLen(id);
}
/**
* Truncates or extends the stream to the specified length if this stream
* supports such an operation. If truncation is requested, all data
* located after this point in the file is discarded. If extending the
* file is requested, the values of the data in the extended portion of
* the file is undefined.
*
* @param len
* The new length of the file.
*
* @throws UnsupportedOperationException
* @throws IOException
*/
public void setLen(long len)
throws UnsupportedOperationException,
IOException
{
work.obtain().streamsAccessor.setLen(id, len);
}
/**
* Sets the stream into binary mode which disables character translations
* during {@link #readChar}. It is important to note that field and line
* reading (<code>import</code>, <code>set</code>, <code>update</code>
* <code>prompt-for</code> and <code>import unformatted</code>) are
* unaffected by this setting (this is based on Progress testcases).
*/
@Override
public void setBinary()
{
super.setBinary();
work.obtain().streamsAccessor.setBinary(id);
}
/**
* Write the given character to the output stream.
*
* @param ch
* The character to be written.
*
* @throws IOException
*/
public void writeCh(char ch)
throws IOException
{
work.obtain().streamsAccessor.writeCh(id, ch);
}
/**
* Write the given byte to the output stream.
*
* @param b
* The byte to be written.
*
* @throws IOException
*/
public void writeByte(byte b)
throws IOException
{
work.obtain().streamsAccessor.writeByte(id, b);
}
/**
* Write the given string to the output stream.
*
* @param data
* The data to be written.
*
* @throws IOException
*/
public void write(String data)
throws IOException
{
work.obtain().streamsAccessor.write(id, data);
}
/**
* Write the given byte array to the output stream.
*
* @param data
* The data to be written.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(byte[] data) throws IOException
{
work.obtain().streamsAccessor.write(id, data);
}
/**
* Write the specified range of bytes from the given byte array to the output stream.
*
* @param data
* The data to be written.
* @param off
* Starting offset in data from which to read bytes to be written. Must be
* non-negative and {@code < data.length}.
* @param len
* Length of data to be written. Must be non-negative and {@code <= (data.length
* - offset)}.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(byte[] data, int off, int len)
throws IOException
{
work.obtain().streamsAccessor.write(id, data, off, len);
}
/**
* Peeks at the character from the current read position in the stream (reads a character from
* the current read position in the stream without incrementing stream read position. The next
* {@code peekCh()} and {@code readCh()} will return the same value).
* <p>
* The underlying stream subclass determines the content of the result. Byte oriented streams
* such as pipes or files will return a byte while streams that generate keystrokes or
* characters may return a DBCS or Unicode character.
*
* @return The next character read from the stream, -1 on any failure and -2 upon an
* {@code EOF}.
*/
public int peekCh()
{
return work.obtain().streamsAccessor.peekCh(id);
}
/**
* Read a character from the current read position in the stream.
* <p>
* The underlying stream subclass determines the content of the result.
* Byte oriented streams such as pipes or files will return a byte while
* streams that generate keystrokes or characters may return a DBCS or
* Unicode character.
*
* @return The next character read from the stream, -1 on any failure
* and -2 upon an <code>EOF</code>.
*/
public int readCh()
{
return work.obtain().streamsAccessor.readCh(id);
}
/**
* Read a single byte from the underlying stream.
*
* @return A single byte from the stream.
*/
@Override
public int readByte()
{
return work.obtain().streamsAccessor.readByte(id);
}
/**
* Read all characters from the current read position in the stream to the
* next line separator (as determined by the <code>File.separator</code>
* or to the <code>EOF</code>. Any line separator character(s) and the
* <code>EOF</code> character are not returned.
*
* @return The next line read from the stream.
*
* @throws EOFException
* @throws IOException
* @throws InterruptedException
*/
public String readLn()
throws EOFException,
IOException,
InterruptedException
{
return work.obtain().streamsAccessor.readLn(id);
}
/**
* Read a chunk of characters from the current read position in the stream to the
* offset calculated by given length or to the <code>EOF</code>, whichever
* comes first. The <code>EOF</code> character is not returned.
*
* @param len
* The number of characters to read.
*
* @return The chunk read from the stream or <code>null</code> if there
* is nothing to read (the <code>EOF</code> has been reached). The
* length of the returned array will be the smaller of the
* <code>len</code> parameter or the actual bytes left before the
* <code>EOF</code>.
*
* @throws IOException
* If an I/O error occurs.
*/
public byte[] readChunk(int len)
throws IOException
{
return work.obtain().streamsAccessor.readChunk(id, len);
}
/**
* Read a chunk of bytes from the current read position in the stream to the
* offset calculated by given length or to the <code>EOF</code>, whichever
* comes first. The <code>EOF</code> character is not returned.
*
* @param len
* The number of bytes to read.
*
* @return The chunk read from the stream or <code>null</code> if there
* is nothing to read (the <code>EOF</code> has been reached). The
* length of the returned array will be the smaller of the
* <code>len</code> parameter or the actual bytes left before the
* <code>EOF</code>.
*
* @throws IOException
* If an I/O error occurs.
*/
@Override
public byte[] readBytes(int len) throws IOException
{
return work.obtain().streamsAccessor.readBytes(id, len);
}
/**
* Closes the input stream and releases OS resources associated with it.
* Any output stream that is open is unaffected.
*/
public void closeIn()
{
try
{
cleanup(true, false);
work.obtain().streamsAccessor.closeIn(id);
}
catch (IOException io)
{
// silently exit
}
finally
{
cacheIsOut = false;
cacheIsIn = false;
}
}
/**
* Closes the output stream and releases OS resources associated with it.
* Any input stream that is open is unaffected.
*/
public void closeOut()
{
try
{
try
{
cleanup(false, true);
}
finally
{
work.obtain().streamsAccessor.closeOut(id);
}
}
catch (IOException io)
{
// silently exit
}
finally
{
cacheIsOut = false;
cacheIsIn = false;
}
}
/**
* Closes the stream and releases OS resources associated with it. After
* completion, further calls may not be made to use this instance, until
* an {@link #assign} is used to "re-open" the stream. This is safe to
* call when no stream is open (it will do nothing in that case).
*/
public void close()
{
try
{
cleanup(true, true);
if (id >= 0)
{
work.obtain().streamsAccessor.close(id);
id = -1;
}
}
catch (IOException io)
{
// silently exit
}
finally
{
cacheIsOut = false;
cacheIsIn = false;
}
}
/**
* Assigns the internal stream reference to the given reference. This
* must be called before any other usage of this class.
*
* @param s
* The new internal stream reference to use for all operations.
*
* @throws UnsupportedOperationException
*/
public void assign(Stream s)
throws UnsupportedOperationException
{
// cleanup
if (id >= 0)
{
work.obtain().streamsAccessor.close(id);
}
// reset
RemoteStream rs = (RemoteStream) s;
id = rs.id;
term = rs.term;
}
/**
* Inserts a page break if the stream is paged and if the next write
* position is not on the first line of a new page.
*
* @throws ErrorConditionException
*/
public void advancePage()
throws ErrorConditionException
{
work.obtain().streamsAccessor.advancePage(id);
}
/**
* Flushes the stream.
*/
public void flush()
{
work.obtain().streamsAccessor.flush(id);
}
/**
* Output a list of fields to the stream.
*
* @param data
* The list of fields to be output.
* @param mode
* <code>MODE_PUT_NORMAL</code>, <code>MODE_PUT_CONTROL</code>,
* <code>MODE_PUT_UNFORMATTED</code> or <code>MODE_EXPORT</code>.
* @param delim
* The delimiter to use in EXPORT mode.
*
* @throws ErrorConditionException
* On any error during I/O.
*/
public void putWorker(FieldEntry[] data, int mode, char delim)
throws ErrorConditionException
{
for (FieldEntry entry : data)
{
if (entry != null)
{
entry.resolveData();
}
}
work.obtain().streamsAccessor.putWorker(id, data, mode, delim);
}
/**
* Set the unnamed status for this stream.
*
* @param unnamed
* <code>true</code> if this is an unnamed stream.
*/
public void setUnnamed(boolean unnamed)
{
work.obtain().streamsAccessor.setUnnamed(id, unnamed);
}
/**
* Makes the stream UNBUFFERED.
*/
public void setUnbuffered()
{
work.obtain().streamsAccessor.setUnbuffered(id);
}
/**
* Sets the stream's character conversion mode.
*
* @param convert
* The new character conversion mode. <code>true</code> to
* enable character conversion, <code>false</code> to disable
* conversion.
*/
public void setConvert(boolean convert)
{
// save it locally as well
super.setConvert(convert);
work.obtain().streamsAccessor.setConvert(id, convert);
}
/**
* Sets the stream's source codepage and activates character conversion mode.
*
* @param cp
* The source codepage for character conversion mode.
*/
public void setConvertSource(String cp)
{
// save it locally as well
super.setConvertSource(cp);
work.obtain().streamsAccessor.setConvertSource(id, cp);
}
/**
* Sets the stream's target codepage and activates character conversion mode.
*
* @param cp
* The target codepage for character conversion mode.
*/
public void setConvertTarget(String cp)
{
// save it locally as well
super.setConvertTarget(cp);
work.obtain().streamsAccessor.setConvertTarget(id, cp);
}
/**
* Sets the stream's lob directory located as a subdirectory for opened current file stream directory.
*
* @param lobDir
* The name for subdirectory to store LOB files.
*/
@Override
public void setLobDirectory(String lobDir)
{
// save it locally as well
super.setLobDirectory(lobDir);
work.obtain().streamsAccessor.setLobDirectory(id, lobDir);
}
/**
* Sets the stream font number. The number denotes an index into the font table
* of the current environment.
* The method is part of the runtime support of the FONT option of the OUTPUT TO PRINTER
* (FWD extension to 4GL) statement. This method is only implemented by the printer stream.
*
* @param num
* Font number.
*/
@Override
public void setFont(int num)
{
work.obtain().streamsAccessor.setFont(id, num);
}
/**
* Set stream display mode to landscape
*/
@Override
public void setLandscape()
{
super.setLandscape();
work.obtain().streamsAccessor.setLandscape(id);
}
/**
* Sets the stream's <code>keepMessages</code> mode which causes certain UI statements that write
* data to the default window to be suppressed when output is being redirected.
* The types of UI statements are 4GL errors and warnings, and UI statements written
* using MESSAGE.
*/
@Override
public void setKeepMessages()
{
super.setKeepMessages();
work.obtain().streamsAccessor.setKeepMessages(id);
}
/**
* Set stream display mode to portrait
*/
@Override
public void setPortrait()
{
super.setPortrait();
work.obtain().streamsAccessor.setPortrait(id);
}
/**
* Write the current output buffer to the stream. This flushes all
* buffered output of the {@link #putField}, {@link #putSpace},
* {@link #putLineEnd} and {@link #writeField} methods to the stream.
* This method also resets the current delimiter to the default.
* <p>
* This method does nothing if the stream is in <code>control</code> mode.
*
* @param appendNewLine
* Automatically add new line at the end of output.
*
* @throws ErrorConditionException
* On any error during I/O.
* @throws StopConditionException
* If any interruption occurs during I/O processing.
*/
public void endOutput(boolean appendNewLine)
throws ErrorConditionException,
StopConditionException
{
work.obtain().streamsAccessor.endOutput(id, appendNewLine);
}
/**
* Read a block of data from the stream with the block size equal to the size of the binary data array in
* the given <code>memptr</code> variable. The data will be written into the memptr starting at the first
* byte in the array. The read data will be the smaller of the block size or the file size. Only an error
* or end of file condition will cause this read to terminate before the block size has been read.
*
* @param var
* The variable to which the result is assigned.
*
* @return The number of the bytes read, -1 on any failure and -2 upon an <code>EOF</code>.
*/
@Override
protected int readBlockRemote(memptr var)
{
// TODO: memptr may be on server-side, improve this
return work.obtain().streamsAccessor.readBlock(id, var);
}
/**
* Perform common close <b>preprocessing</b> for all streams.
*
* @param in
* <code>true</code> if this was triggered during a call to
* {@link #closeIn} or {@link #close}. Input resources are
* to be cleaned up.
* @param out
* <code>true</code> if this was triggered during a call to
* {@link #closeOut} or {@link #close}. Output resources are
* to be cleaned up.
*/
protected void cleanup(boolean in, boolean out)
throws IOException
{
// DON'T call the superclass, but mark the protected members so that
// other processing is safe
doneIn = in ? in : doneIn;
doneOut = out ? out : doneOut;
// redirect to the client
try
{
work.obtain().streamsAccessor.processCleanup(id, in, out);
}
finally
{
boolean finish = (TransactionManager.currentOperation() ==
TransactionManager.OP_FINISHED);
// external/explicit close requests must cleanup finalizable lists to
// ensure no memory leaks occur
if (!finish && doneIn && doneOut)
{
// remove from global or procedure finalizable lists (first instance
// found is removed, there shouldn't be more than 1 registered)
TransactionManager.removeFinalizable(this);
}
}
}
/**
* Detects when a page break is needed.
*
* @return <code>true</code> if a page break should be output.
*/
boolean needsPageBreak()
{
return work.obtain().streamsAccessor.needsPageBreak(id);
}
/**
* Gets the current page size in lines.
*
* @return The number of lines per page.
*/
int rawGetPageSize()
{
// check our cache, if not valid then obtain the latest value
if (sz == -1)
sz = work.obtain().streamsAccessor.rawGetPageSize(id);
return sz;
}
/**
* Sets the current page size in lines.
*
* @param sz
* The number of lines per page.
*/
void rawSetPageSize(int sz)
{
// clear the cache
this.sz = -1;
work.obtain().streamsAccessor.rawSetPageSize(id, sz);
}
/**
* Gets the current page number.
*
* @return The current page number.
*/
int rawGetPageNum()
{
return work.obtain().streamsAccessor.rawGetPageNum(id);
}
/**
* Increments the current page number.
*/
void rawIncrementPageNum()
{
work.obtain().streamsAccessor.rawIncrementPageNum(id);
}
/**
* Gets the current page's next line number.
*
* @return The next line number.
*/
int rawGetNextLineNum()
{
return work.obtain().streamsAccessor.rawGetNextLineNum(id);
}
/**
* Gets the current page's next logical line number.
*
* @return The next line number.
*/
public integer getNextLineNum()
{
return new integer(work.obtain().streamsAccessor.getNextLineNum(id));
}
/**
* Increments the current page's next line number.
*/
void rawIncrementNextLineNum()
{
work.obtain().streamsAccessor.rawIncrementNextLineNum(id);
}
/**
* Resets the current page's next line number to 1.
*/
void rawResetNextLineNum()
{
work.obtain().streamsAccessor.rawResetNextLineNum(id);
}
/**
* Stores global data relating to the state of the current context.
*/
private static class WorkArea
{
/** The remote stream server instance. */
private LowLevelStream streamsAccessor = null;
}
/**
* Simple container that stores and returns a context-local instance of
* the global work area.
*/
private static class ContextContainer
extends ContextLocal<WorkArea>
{
/**
* Obtains the context-local instance of the contained global work
* area.
*
* @return The work area associated with this context.
*/
public WorkArea obtain()
{
return this.get();
}
/**
* Initializes the work area, the first time it is requested within a
* new context.
*
* @return The newly instantiated work area.
*/
protected synchronized WorkArea initialValue()
{
WorkArea wa = new WorkArea();
// obtain the proxy
wa.streamsAccessor = OSResourceManager.getStreamHelper();
return wa;
}
}
}