XMLCallbackWrapper.java
/*
** Module : XMLCallbackWrapper
** Abstract : SAX reader handler specific features implementation
**
** Copyright (c) 2013-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 EVK 20131030 Created initial version.
** 002 CA 20140108 Added the "validateModes" arg (set to true) to the reachableInternalEntry call.
** 003 CA 20220222 Fixed insertAttribute, to process a qualified attribute name properly.
** CA 20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
** allow caching of the resolved target.
** 004 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
*/
/*
** 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.xml;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import org.xml.sax.InputSource;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.*;
import java.util.*;
import java.util.logging.*;
import static com.goldencode.p2j.util.ControlFlowOps.ArgValidationErrors;
/**
* This class provides implementations for all of the possible 4gl-sax callbacks.
*
*/
class XMLCallbackWrapper
{
/** Anonymous log instance. */
private static final CentralLogger LOG = CentralLogger.get(XMLCallbackWrapper.class.getName());
/** The procedure name to be associated with the given handle object.*/
private final handle procHandle;
/** The SaxReader instance to be associated with the given handle object.*/
private final handle saxReader;
/**
* Character callback may use memptr and longchar in parameters, this variable will be
* initialize once in constructor. If memptr is not exists in character callback(or callback
* not exists), than will be use longchar by default.
*/
private final boolean isMemptrInCharacterCallback;
/**
* IgnorableWhitespace callback may use memptr and longchar in parameters, this variable will
* be initialize once in constructor. If memptr is not exists in ignorableWhitespace callback
* (or callback not exists), than will be use longchar by default.
*/
private final boolean isMemptrInIgnorableWhitespaceCallback;
/**
* Default constructor accepting a handle to procedure and SaxReader instance.
*
* @param procHandle
* The procedure name to be associated with the given handle object.
* @param saxReader
* The SaxReader instance to be associated with the given handle object.
*/
XMLCallbackWrapper(handle procHandle, handle saxReader)
{
this.procHandle = procHandle;
this.saxReader = saxReader;
ArgValidationErrors res = validateCallback(CallBack.Characters,
new memptr(),
new character());
isMemptrInCharacterCallback = res == ArgValidationErrors.NO_ERROR;
res = validateCallback(CallBack.IgnorableWhitespace,
new memptr(),
new character());
isMemptrInIgnorableWhitespaceCallback = res == ArgValidationErrors.NO_ERROR;
}
/**
* Receive notification of a notation declaration. If same callback is not exists in 4gl code
* then do nothing.
*
* @param notationDeclaration
* NotationDeclaration xml event which provide all information about notation.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
void notationDecl(NotationDeclaration notationDeclaration)
throws XMLStreamException
{
invokeProcedure(CallBack.NotationDecl,
new character(notationDeclaration.getName()),
new character(notationDeclaration.getPublicId()),
new character(notationDeclaration.getSystemId()));
}
/**
* Receive notification of the beginning of the document. If same callback is not exists in 4gl
* code then do nothing.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
void startDocument()
throws XMLStreamException
{
invokeProcedure(CallBack.StartDocument);
}
/**
* Receive notification of the ending of the document. If same callback is not exists in 4gl
* code then do nothing.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void endDocument()
throws XMLStreamException
{
invokeProcedure(CallBack.EndDocument);
}
/**
* Receive notification of the start of a Namespace mapping. If same callback is not exists in
* 4gl code then do nothing.
*
* @param prefix
* The Namespace prefix being declared.
* @param uri
* The Namespace URI mapped to the prefix
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void startPrefixMapping(String prefix, String uri)
throws XMLStreamException
{
invokeProcedure(CallBack.StartPrefixMapping,
new character(prefix == null ? "" : prefix),
new character(uri));
}
/**
* Receive notification of the end of a Namespace mapping. If same callback is not exists in
* 4gl code then do nothing.
*
* @param prefix
* The Namespace prefix being declared.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void endPrefixMapping(String prefix)
throws XMLStreamException
{
invokeProcedure(CallBack.EndPrefixMapping, new character(prefix));
}
/**
* Receive notification of the start of an element. If same callback is not exists in 4gl code
* then do nothing.
*
* @param startElement
* Provides access to information about start element.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void startElement(StartElement startElement)
throws XMLStreamException
{
QName qName = startElement.getName();
Iterator attributes = startElement.getAttributes();
SaxAttributes saxAttributes = new SaxAttributesImpl();
while(attributes.hasNext()) {
Attribute attribute = (Attribute)attributes.next();
QName qNameAttr = attribute.getName();
String prefix = qNameAttr.getPrefix();
String localPart = qNameAttr.getLocalPart();
String qn = prefix + (prefix.isEmpty() ? "" : ":") + localPart;
saxAttributes.insertAttribute(qn, attribute.getValue(), qNameAttr.getNamespaceURI());
}
invokeProcedure(CallBack.StartElement,
new character(qName.getNamespaceURI()),
new character(qName.getLocalPart()),
new character(qName.getPrefix() + qName.getLocalPart()),
new handle(saxAttributes));
}
/**
* Receive notification of the end of an element. If same callback is not exists in 4gl code
* then do nothing.
*
* @param endElement
* Provides access to information about end element.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void endElement(EndElement endElement)
throws XMLStreamException
{
QName qName = endElement.getName();
invokeProcedure(CallBack.EndElement,
new character(qName.getNamespaceURI()),
new character(qName.getLocalPart()),
new character(qName.getPrefix() + qName.getLocalPart()));
}
/**
* Receive notification of character data inside an element. If same callback is not exists in
* 4gl code then do nothing.
*
* @param characters
* Provides access to information about characters element.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void characters(Characters characters)
throws XMLStreamException
{
String data = characters.getData();
if (isMemptrInCharacterCallback)
{
byte[] arr = data.getBytes();
invokeProcedure(CallBack.Characters, new memptr(arr), arr.length);
}
else
{
invokeProcedure(CallBack.Characters, new longchar(data), data.length());
}
}
/**
* Receive notification of ignorable whitespace in element content. If same callback is not
* exists in 4gl code then do nothing.
*
* @param characters
* Provides access to information about characters element.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void ignorableWhitespace(Characters characters)
throws XMLStreamException
{
String data = characters.getData();
if (isMemptrInIgnorableWhitespaceCallback)
{
byte[] arr = data.getBytes();
invokeProcedure(CallBack.IgnorableWhitespace, new memptr(arr), arr.length);
}
else
{
invokeProcedure(CallBack.IgnorableWhitespace, new longchar(data), data.length());
}
}
/**
* Receive notification of a processing instruction. If same callback is not
* exists in 4gl code then do nothing.
*
* @param pi
* An interface that describes the data found in processing instructions.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void processingInstruction(ProcessingInstruction pi)
throws XMLStreamException
{
invokeProcedure(CallBack.ProcessingInstruction, new character(pi.getTarget()),
new character(pi.getData()));
}
/**
* Allow the application to resolve external entities. If this callback is not exists in 4gl
* code, than this method will always return null.
*
* @param publicId
* The public identifier, or null if none is available.
* @param systemId
* The system identifier provided in the XML document.
*
* @return The new input source, or null to require the default behaviour.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
InputSource resolveEntity(String publicId, String systemId)
throws XMLStreamException
{
character filePath = new character();
memptr memPointer = new memptr();
Boolean res = invokeProcedure(CallBack.ResolveEntity,
new character(publicId),
filePath,
memPointer);
if (res == null)
{
return null;
}
if (!filePath.isUnknown() || !memPointer.isUnknown())
{
throw new XMLStreamException();
}
InputSource is = null;
if (!filePath.isUnknown())
{
String file = filePath.toStringMessage();
is = new InputSource(new SaxReaderImpl.FileInputStreamWrapper(file));
}
else if (!memPointer.isUnknown())
{
is = new InputSource(new SaxReaderImpl.MemptrInputStream(memPointer));
}
return is;
}
/**
* Receive notification of a warning messages. If same callback is not
* exists in 4gl code then do nothing.
*
* @param msg
* warning message
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void warning(String msg)
throws XMLStreamException
{
invokeProcedure(CallBack.Warning, new character(msg));
}
/**
* Receive notification of a error messages. If same callback is not
* exists in 4gl code then do nothing.
*
* @param message
* error message
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public void error(String message)
throws XMLStreamException
{
invokeProcedure(CallBack.Error, new character(message));
}
/**
* Receive notification of a fatal errors. If same callback is not
* exists in 4gl code then do nothing. If the callback is exists and doesn't return error, than
* parser should stop parsing.
*
* @param e
* Fatal error exception
*
* @return {@code null} if the callback is not exists or a fatal error.
* {@code true} if the callback is successful executed but return some error.
* {@code false} if the callback is successful executed but doesn't return error.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
public Boolean fatalError(Exception e)
throws XMLStreamException
{
return invokeProcedure(CallBack.FatalError, new character(e.getMessage()));
}
/**
* Method can invoke procedure callback and valid input parameters. If specified callback is
* not exists then do nothing.
*
* @param callBack
* Callback entity.
* @param args
* Array of input/output arguments.
*
* @return {@code null} if the callback is not exists or a fatal error.
* {@code true} if the callback is successful executed but return some error.
* {@code false} if the callback is successful executed but doesn't return error.
*
* @throws XMLStreamException
* if any problem in callback, i.e. mismatch parameters or wrong types in callback.
*/
private Boolean invokeProcedure(CallBack callBack, Object... args)
throws XMLStreamException
{
if (procHandle == null || !ProcedureManager.isProcedure(procHandle))
{
return null;
}
ArgValidationErrors result = validateCallback(callBack, args);
if (result == null)
{
return null;
}
if (result == ArgValidationErrors.INCORRECT_COUNT)
{
throw new MismatchedParametersException(callBack.name());
}
if (result != ArgValidationErrors.NO_ERROR)
{
throw new MismatchedNumberParametersException(callBack.name());
}
SelfManager.pushSelf(saxReader);
character returnValue = null;
try
{
callBack.getCallSite()
.clone()
.setTarget(callBack.name())
.setInHandle(procHandle)
.setModes(callBack.getModes())
.setArguments(args)
.executeForResource((WrappedResource) saxReader.get());
returnValue = ControlFlowOps.getReturnValue();
}
catch (Exception exc)
{
LOG.logp(Level.SEVERE,
"XMLCallbackWrapper",
"invokeProcedure",
"Could not load!",
exc);
return null;
}
finally
{
SelfManager.popSelf();
}
return returnValue != null && !returnValue.isUnknown() && !returnValue.getValue().isEmpty();
}
/**
* Check if an internal procedure or function is reachable and valid. This searches the
* specified handle and all the super-procedures.
*
* @param callBack
* Callback entity.
* @param args
* Array of input/output arguments.
*
* @return Argument validation error, or NO_ERROR if errors is not exists.
*/
private ArgValidationErrors validateCallback(CallBack callBack, Object... args)
{
return ControlFlowOps.reachableInternalEntry(procHandle,
callBack.name(), false,
callBack.getModes(),
true,
args);
}
/**
* List of possible callbacks of SAX-READER object:
* <ul>
* <li>{@link #StartDocument} -Start Document callback.</li>
* <li>{@link #EndDocument} - End Document callback.</li>
* <li>{@link #ResolveEntity} - ResolveEntity callback.</li>
* <li>{@link #NotationDecl} - Notation Declaration callback.</li>
* <li>{@link #UnparsedEntityDecl} - Unparsed Entity Declaration callback.</li>
* <li>{@link #StartPrefixMapping} - Start Prefix Mapping callback.</li>
* <li>{@link #EndPrefixMapping} - End Prefix Mapping callback.</li>
* <li>{@link #StartElement} - Start Element callback.</li>
* <li>{@link #EndElement} - End Element callback.</li>
* <li>{@link #Characters} - Characters callback.</li>
* <li>{@link #IgnorableWhitespace} - Ignorable Whitespace callback.</li>
* <li>{@link #ProcessingInstruction} - Processing Instruction callback.</li>
* <li>{@link #Warning} - Warning callback.</li>
* <li>{@link #Error} - Error callback.</li>
* <li>{@link #FatalError} - FatalError callback.</li>
* </ul>
*/
enum CallBack
{
StartDocument(),
EndDocument(),
ResolveEntity("IIOO"), //maybe memptr
NotationDecl("III"),
UnparsedEntityDecl("IIII"),
StartPrefixMapping("II"),
EndPrefixMapping("I"),
StartElement("IIII"),
EndElement("III"),
Characters("II"),
IgnorableWhitespace("II"),
ProcessingInstruction("II"),
Warning("I"),
Error("I"),
FatalError("I");
/** defined input/output modes for procedure*/
private final String modes;
/** The call-site for this callback. */
private final InvokeConfig callSite;
/** default constructor*/
CallBack()
{
this(null);
}
/**
* Special constructor accepting array with possible input type parameters.
*
* @param modes
* String of possible input/output parameters type, e.g "I", "O", "U".
*/
CallBack(String modes)
{
this.modes = modes;
this.callSite = new InvokeConfig();
}
/**
* Gets the input/output parameter modes.
*
* @return see above.
*/
String getModes()
{
return modes;
}
/**
* Get the {@link #callSite}.
*
* @return Seea bove.
*/
public InvokeConfig getCallSite()
{
return callSite;
}
}
}