StreamFactory.java
/*
** Module : StreamFactory.java
** Public : creates stream instances on a remote node
**
** Copyright (c) 2006-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 GES 20060211 @24554 Created initial version which creates file and process stream
** instances on a remote node.
** 002 SVL 20080219 @37121 openFileStream() returns null if filename equals "terminal".
** 003 SIY 20080317 @37456 Added support for terminal stream. Ref: #37453
** 004 SIY 20090331 @41707 Added more openFileStream() signatures and added detection of
** interactive terminal redirection. Ref: #41705
** 005 SIY 20090401 @41714 Fixed regression introduced by #004: wrong order of page size setting calls.
** 006 SIY 20090401 @41715 Do not call setPageSiz() if NO_PAGING is passed as a page size and
** paging flag is set to false. Yet another regression from #004.
** 007 GES 20111020 Modified per-session initialization to detect when the client code
** is running inside the server process AND to initialize for local
** redirection instead of using a remote object.
** 008 CS 20130103 Implement OS-DIR support as an INPUT stream
** 009 VIG 20120918 Added printer stream support for OUTPUT TO PRINTER statement.
** 010 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 011 EVK 20140601 Empty directory name should be current directory "."
** Added additional methods for openFileStream.
** Added explicit support for the terminal stream.
** 012 CA 20170829 Added openClipboardStream - not yet supported, returns NullStream.
** 013 HC 20171024 Runtime support for SYSTEM-DIALOG PRINT-SETUP and OUTPUT TO PRINTER.
** 014 GES 20180115 Added a signature for CLIPBOARD construction.
** 015 SBI 20180213 Linked Clipboard output streams with their implementations.
** 016 SBI 20180412 Fixed javadoc warnings.
** 017 HC 20190714 Implemented stream-by-name resolution.
** 018 OM 20210830 Added CachedInputStream and universal support for reading input streams from jar.
** CA 20220428 Added openWebStream stubs, for WebSpeed.
** 019 HC 20230427 Implemented server-side file-system resource. File operations
** performed on client can be optionally performed directly on server
** without the expensive remote call.
** 020 GBB 20231213 Adding flag hasDuplicatedLastLine.
** 021 GBB 20240826 Moving OSResourceManager to osresource package.
** 022 GBB 20240912 File & dir streams to be created as RemoteStream.
** 023 FER 20250127 Added openNullStream() method.
*/
/*
** 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 com.goldencode.p2j.security.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.util.osresource.*;
import java.io.*;
/**
* Creates file and process stream instances that are on the remote node associated with the current user's
* session and which are accessed via a {@link RemoteStream} instance.
*/
public class StreamFactory
{
/** Special page size which causes turning paging off.*/
private static final int NO_PAGING = -2;
/** Stores context-local state variables. */
private static final ContextContainer work = new ContextContainer();
/**
* Constructs a remote stream instance representing a file using a filename, this file or device will be
* opened for reading or writing based on the given {@code write} flag. The current read/write position
* will be at offset 0 unless in {@code write} mode and the {@code append} flag is on (in which case the
* write-position will be at the end of the file).
*
* @param filename
* The name of the file or device to open. Must not be {@code null}.
* @param write
* If {@code true}, open in write mode, otherwise open the file for reading.
* @param append
* In {@code write} mode, if {@code true}, the current file pointer is set to the end of the
* file. Otherwise, if in {@code write} mode, the file is truncated to 0 length. This value is
* ignored in {@code read} mode.
* @param pageSz
* Desired page size. {@code -1} means default paging.
* @param paged
* {@code true} means that stream is paged.
*
* @return The new stream instance or {@code null} if an error occurred while silent error mode was
* active.
*
* @throws ErrorConditionException
* If the file does not exist OR if the current user does not have the proper rights to open the
* file or device.
*/
public static Stream openFileStream(String filename,
boolean write,
boolean append,
int pageSz,
boolean paged)
{
return openFileStream(false, filename, write, append, pageSz, paged);
}
/**
* Constructs a remote stream instance representing a file using a filename, this file or device will be
* opened for reading or writing based on the given {@code write} flag. The current read/write position
* will be at offset 0 unless in {@code write} mode and the {@code append} flag is on (in which case the
* write-position will be at the end of the file).
*
* @param hasDuplicatedLastLine
* Flag to indicate if the last line in the file is duplicate (true for LOB files).
* @param filename
* The name of the file or device to open. Must not be {@code null}.
* @param write
* If {@code true}, open in write mode, otherwise open the file for reading.
* @param append
* In {@code write} mode, if {@code true}, the current file pointer is set to the end of the
* file. Otherwise, if in {@code write} mode, the file is truncated to 0 length. This value is
* ignored in {@code read} mode.
* @param pageSz
* Desired page size. {@code -1} means default paging.
* @param paged
* {@code true} means that stream is paged.
*
* @return The new stream instance or {@code null} if an error occurred while silent error mode was
* active.
*
* @throws ErrorConditionException
* If the file does not exist OR if the current user does not have the proper rights to open the
* file or device.
*/
public static Stream openFileStream(boolean hasDuplicatedLastLine,
String filename,
boolean write,
boolean append,
int pageSz,
boolean paged)
throws ErrorConditionException
{
boolean terminal = false;
if (filename != null)
{
String term = filename.toLowerCase();
if (term.equals("terminal") || term.equals("term"))
{
filename = "terminal";
terminal = true;
}
}
// terminal stream without paging is not allowed
if (terminal && !paged && pageSz == NO_PAGING)
{
return openTerminalStream();
}
if (!write)
{
// firstly attempt to locate the resource inside application's jar:
InputStream stream = LogicalTerminal.getResourceStreamFromApplication(filename);
if (stream != null)
{
// make sure these kinds of streams support stream mark/reset operations needed for some
// operations in FWD. To work around this, we will cache the whole content in a ByteArray
try
{
return new CachedInputStream(stream);
}
catch (IOException e)
{
throw new ErrorConditionException(e.getMessage());
}
}
}
int id = work.obtain().fileSystem.openFileStream(filename, write, append, hasDuplicatedLastLine);
Stream res = (id < 0) ? null : new RemoteStream(id, terminal);
if (res != null)
{
if ((pageSz < 0 && pageSz != NO_PAGING) || paged)
{
res.setPageSize();
}
if (pageSz >= 0)
{
res.setPageSize(pageSz);
}
}
return res;
}
/**
* Constructs a remote stream instance representing a file using a filename, this file or
* device will be opened for reading or writing based on the given <code>write</code> flag.
* The current read/write position will be at offset 0 unless in <code>write</code> mode and
* the <code>append</code> flag is on (in which case the write position will be at the end of
* the file).
*
* @param filename
* The name of the file or device to open. Must not be <code>null</code>.
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param append
* In <code>write</code> mode, if <code>true</code>, the current file pointer is
* set to the end of the file. Otherwise if in <code>write</code> mode, the file
* is truncated to 0 length. This value is ignored in <code>read</code> mode.
* @param sz
* Desired page size. <code>-1</code> means default paging.
*
* @return The new stream instance or <code>null</code> if an error occurred while silent
* error mode was active.
*
* @throws ErrorConditionException
* If the file does not exist OR if the current user does not have the proper rights
* to open the file or device.
*/
public static Stream openFileStream(String filename, boolean write, boolean append, int sz)
{
return openFileStream(filename, write, append, sz, false);
}
/**
* Constructs a remote stream instance representing a file using a filename, this file or
* device will be opened for reading or writing based on the given <code>write</code> flag.
* The current read/write position will be at offset 0 unless in <code>write</code> mode and
* the <code>append</code> flag is on (in which case the write position will be at the end of
* the file).
* <p>
* This stream will NOT have paging active.
*
* @param hasDuplicatedLastLine
* Flag to indicate if the last line in the file is duplicate (true for LOB files).
* @param filename
* The name of the file or device to open. Must not be <code>null</code>.
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param append
* In <code>write</code> mode, if <code>true</code>, the current file pointer is
* set to the end of the file. Otherwise if in <code>write</code> mode, the file
* is truncated to 0 length. This value is ignored in <code>read</code> mode.
*
* @return The new stream instance or <code>null</code> if an error occurred while silent
* error mode was active.
*
* @throws ErrorConditionException
* If the file does not exist OR if the current user does not have the proper rights
* to open the file or device.
*/
public static Stream openFileStream(boolean hasDuplicatedLastLine,
String filename,
boolean write,
boolean append)
{
return openFileStream(hasDuplicatedLastLine, filename, write, append, NO_PAGING, false);
}
/**
* Constructs a remote stream instance representing a file using a filename, this file or
* device will be opened for reading or writing based on the given <code>write</code> flag.
* The current read/write position will be at offset 0 unless in <code>write</code> mode and
* the <code>append</code> flag is on (in which case the write position will be at the end of
* the file).
* <p>
* This stream will NOT have paging active.
*
* @param filename
* The name of the file or device to open. Must not be <code>null</code>.
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param append
* In <code>write</code> mode, if <code>true</code>, the current file pointer is
* set to the end of the file. Otherwise if in <code>write</code> mode, the file
* is truncated to 0 length. This value is ignored in <code>read</code> mode.
*
* @return The new stream instance or <code>null</code> if an error occurred while silent
* error mode was active.
*
* @throws ErrorConditionException
* If the file does not exist OR if the current user does not have the proper rights
* to open the file or device.
*/
public static Stream openFileStream(String filename, boolean write, boolean append)
{
return openFileStream(filename, write, append, NO_PAGING, false);
}
/**
* Constructs a remote stream instance representing a file using a filename, this file or
* device will be opened for reading or writing based on the given <code>write</code> flag.
* The current read/write position will be at offset 0 unless in <code>write</code> mode and
* the <code>append</code> flag is on (in which case the write position will be at the end of
* the file).
*
* @param filename
* The name of the file or device to open. Must not be <code>null</code>.
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param append
* In <code>write</code> mode, if <code>true</code>, the current file pointer is
* set to the end of the file. Otherwise if in <code>write</code> mode, the file
* is truncated to 0 length. This value is ignored in <code>read</code> mode.
* @param paged
* <code>true</code> means that stream is paged with the default page size.
*
* @return The new stream instance or <code>null</code> if an error occurred while silent
* error mode was active.
*
* @throws ErrorConditionException
* If the file does not exist OR if the current user does not have the proper rights
* to open the file or device.
*/
public static Stream openFileStream(String filename,
boolean write,
boolean append,
boolean paged)
{
return openFileStream(filename, write, append, NO_PAGING, paged);
}
/**
* Constructs a remote stream instance representing a stream result of
* INPUT FROM OS-DIR statement.
*
* @param dir
* The <code>String</code> variable representing the directory for
* which OS-DIR is applied.
* @param noAttrList
* This indicates if type index will no be output to stream If
* <code>true</code> the index will not be output to the stream,
* if <code>false</code> it will be output.
*
* @return The <code>Stream</code> wrapping the <code>OsDirStream</code>
* containing the result of OS-DIR implementation, or null if
* an error has occurred.
*/
public static Stream openDirStream(String dir, boolean noAttrList)
{
String fixDir = "".equals(dir) ? "." : dir;
return new RemoteStream(work.obtain().fileSystem.openDirStream(fixDir, noAttrList));
}
/**
* Constructs a remote stream instance representing a stream result of
* INPUT FROM OS-DIR statement.
* <p>
* When no boolean variable representing the noAttrList which decides if
* file type will be added to the stream is present the file type will be
* added by default.
*
* @param dir
* The <code>String</code> variable representing the directory for
* which OS-DIR is applied.
*
* @return The <code>Stream</code> wrapping the <code>OsDirStream</code>
* containing the result of OS-DIR implementation, or null if
* an error has occurred.
*/
public static Stream openDirStream(String dir)
{
return openDirStream(dir, false);
}
/**
* Constructs a remote stream instance representing a stream result of
* INPUT FROM OS-DIR statement.
*
* @param dir
* The <code>character</code> variable representing the directory
* for which OS-DIR is applied.
* @param noAttrList
* This indicates if type index will no be output to stream If
* <code>true</code> the index will not be output to the stream,
* if <code>false</code> it will be output.
*
* @return The <code>Stream</code> wrapping the <code>OsDirStream</code>
* containing the result of OS-DIR implementation, or null if
* an error has occurred.
*/
public static Stream openDirStream(character dir, boolean noAttrList)
{
return openDirStream(dir.toStringMessage(), noAttrList);
}
/**
* Constructs a remote stream instance representing a stream result of
* INPUT FROM OS-DIR statement.
* <p>
* When no boolean variable representing the noAttrList which decides if
* file type will be added to the stream is present the file type will be
* added by default.
*
* @param dir
* The <code>character</code> variable representing the directory
* for which OS-DIR is applied.
*
* @return The <code>Stream</code> wrapping the <code>OsDirStream</code>
* containing the result of OS-DIR implementation, or null if
* an error has occurred.
*/
public static Stream openDirStream(character dir)
{
return openDirStream(dir.toStringMessage(), false);
}
/**
* Constructs a remote stream instance representing a process' standard I/O.
*
* @return The new stream instance.
*/
public static Stream openProcessStream()
{
return new RemoteStream(work.obtain().streamBuilder.openProcessStream());
}
/**
* Open a new stream as 'WEB'.
*
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
*
* @return The clipboard stream
*/
public static Stream openWebStream(boolean write)
{
return null;
}
/**
* Open a new stream as 'WEB'.
*
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param paged
* <code>true</code> if the stream is paged.
*
* @return The clipboard stream
*/
public static Stream openWebStream(boolean write, boolean paged)
{
UnimplementedFeature.missing("WEB streams are not supported.");
return null;
}
/**
* Open a new stream as 'WEB'.
*
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param pageSz
* Desired page size. <code>-1</code> means default paging.
*
* @return The clipboard stream
*/
public static Stream openWebStream(boolean write, int pageSz)
{
return null;
}
/**
* Open a new stream as 'WEB'.
*
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param pageSz
* Desired page size. <code>-1</code> means default paging.
* @param paged
* <code>true</code> if the stream is paged.
*
* @return The clipboard stream
*/
public static Stream openWebStream(boolean write, int pageSz, boolean paged)
{
return null;
}
/**
* Open a new stream targeting the OS clipboard.
*
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
*
* @return The clipboard stream
*/
public static Stream openClipboardStream(boolean write)
{
return openClipboardStream(write, -1, false);
}
/**
* Open a new stream targeting the OS clipboard.
*
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param paged
* <code>true</code> if the stream is paged.
*
* @return The clipboard stream
*/
public static Stream openClipboardStream(boolean write, boolean paged)
{
return openClipboardStream(write, -1, paged);
}
/**
* Open a new stream targeting the OS clipboard.
*
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param pageSz
* Desired page size. <code>-1</code> means default paging.
*
* @return The clipboard stream
*/
public static Stream openClipboardStream(boolean write, int pageSz)
{
return openClipboardStream(write, pageSz, true);
}
/**
* Open a new stream targeting the OS clipboard.
*
* @param write
* If <code>true</code>, open in write mode, otherwise open the file for reading.
* @param pageSz
* Desired page size. <code>-1</code> means default paging.
* @param paged
* <code>true</code> if the stream is paged.
*
* @return The clipboard stream
*/
public static Stream openClipboardStream(boolean write, int pageSz, boolean paged)
{
int id = work.obtain().streamBuilder.openClipboardStream();
if (id >= 0)
{
Stream remoteStream = new RemoteStream(id);
if (paged)
{
if (pageSz >= 0)
{
remoteStream.setPageSize(pageSz);
}
else
{
remoteStream.setPageSize();
}
}
return remoteStream;
}
return null;
}
/**
* Constructs a remote stream instance representing the terminal.
*
* @return The new stream instance.
*/
public static Stream openTerminalStream()
{
return openTerminalStream(false, NO_PAGING, false);
}
/**
* Constructs a remote stream instance representing the terminal.
*
* @param pageSz
* Desired page size. <code>-1</code> means default paging.
*
* @return The new stream instance.
*/
public static Stream openTerminalStream(int pageSz)
{
return openTerminalStream(false, pageSz, false);
}
/**
* Constructs a remote stream instance representing the terminal.
*
* @param append
* In <code>write</code> mode, if <code>true</code>, the current file pointer is
* set to the end of the file. Otherwise if in <code>write</code> mode, the file
* is truncated to 0 length. This value is ignored in <code>read</code> mode.
*
* @return null in any case.
*/
public static Stream openTerminalStream(boolean append)
{
return openTerminalStream(append, NO_PAGING, false);
}
/**
* Constructs a remote stream instance representing the terminal.
*
* @param append
* In <code>write</code> mode, if <code>true</code>, the current file pointer is
* set to the end of the file. Otherwise if in <code>write</code> mode, the file
* is truncated to 0 length. This value is ignored in <code>read</code> mode.
* @param paged
* <code>true</code> if the stream is paged.
*
* @return null in any case.
*/
public static Stream openTerminalStream(boolean append, boolean paged)
{
return openTerminalStream(append, NO_PAGING, paged);
}
/**
* Constructs a remote stream instance representing the terminal.
*
* @param append
* In <code>write</code> mode, if <code>true</code>, the current file pointer is
* set to the end of the file. Otherwise if in <code>write</code> mode, the file
* is truncated to 0 length. This value is ignored in <code>read</code> mode.
* @param pageSz
* Desired page size. <code>-1</code> means default paging.
*
* @return null in any case.
*/
public static Stream openTerminalStream(boolean append, int pageSz)
{
return openTerminalStream(append, pageSz, false);
}
/**
* Constructs a remote stream instance representing the terminal.
*
* @param append
* In <code>write</code> mode, if <code>true</code>, the current file pointer is
* set to the end of the file. Otherwise if in <code>write</code> mode, the file
* is truncated to 0 length. This value is ignored in <code>read</code> mode.
* @param pageSz
* Desired page size. <code>-1</code> means default paging.
* @param paged
* <code>true</code> means that stream is paged.
*
* @return null if stream is not paged.
*/
public static Stream openTerminalStream(boolean append, int pageSz, boolean paged)
{
if (!paged && pageSz == NO_PAGING)
{
return null;
}
else
{
return new RemoteStream(work.obtain().streamBuilder.openTerminalStream(), true);
}
}
/**
* Constructs a remote stream instance representing the Null Stream.
*
* @return The stream id which is used to route operations to the
* newly created instance.
*/
public static Stream openNullStream()
{
return new RemoteStream(work.obtain().streamBuilder.openNullStream());
}
/**
* Constructs a remote stream instance representing the printer Stream
* result of the OUTPUT TO PRINTER statement.
* It can be assinged as unnamed output (OUTPUT TO PRINTER statement)
* or as named stream (OUTPUT STREAM someStream TO PRINTER)
*
* @return The stream id which is used to route operations to the
* newly created instance.
*/
public static Stream openPrinterStream()
{
return openPrinterStream(null);
}
/**
* Constructs a remote stream instance representing the printer Stream
* result of the OUTPUT TO PRINTER statement.
* It can be assinged as unnamed output (OUTPUT TO PRINTER statement)
* or as named stream (OUTPUT STREAM someStream TO PRINTER)
*
* @param printerName
* name of target printer
*
* @return The stream id which is used to route operations to the
* newly created instance.
*/
public static Stream openPrinterStream(String printerName)
{
PrintingService.validatePrinterName(printerName);
PrintOptions opts = PrintingService.getPrintOptions();
return new RemoteStream(work.obtain().streamBuilder.openPrinterStream(printerName, opts));
}
/**
* Finds an output stream by its name. Pass {@code null} or an empty string for the unnamed output stream.
*
* @param name
* Stream name.
*
* @return The remote stream instance or {@code null} if no such stream exists.
*/
public static Stream findOutputStream(String name)
{
if (name == null || name.isEmpty())
{
return UnnamedStreams.output();
}
else
{
int id = StreamWrapper.findRemoteStreamId(name);
return id == -1 ? null : new RemoteStream(id);
}
}
/**
* Stores global data relating to the state of the current context.
*/
private static class WorkArea
{
/** The remote stream builder instance. */
private StreamBuilder streamBuilder = null;
/** The file system instance. */
private FileSystem fileSystem = 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 our proxy, server-side code is left-side by convention
wa.streamBuilder = OSResourceManager.getStreamHelper();
wa.fileSystem = OSResourceManager.getFileSystem();
return wa;
}
}
}