blob: be075a5a51ef91aa819dd615756abdcac0307d13 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.util.core.internal.winrm.winrm4j;
import com.google.common.collect.ImmutableSet;
import java.io.*;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.text.StringEscapes;
import org.apache.brooklyn.util.text.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Filters XML output in a configurable way, such as subclasses
* ErrorFiltering for any S tag with an S attribute of "error" and extracts the text, one per line.
* Or if any error or not XML, switches to pass through.
*
* Supports streaming, so viewers get output as quickly as possible. Accepts # comment lines.
* But otherwise very rough-and-ready! */
public class FilteringXmlWriter extends Writer {
private static final Logger LOG = LoggerFactory.getLogger(FilteringXmlWriter.class);
public static class SelectedStreamsFilteringXmlWriter extends FilteringXmlWriter {
public final Predicate<String> allowStreams;
public SelectedStreamsFilteringXmlWriter(Predicate<String> allowStreams, Writer writer) {
super(writer);
this.allowStreams = allowStreams;
}
public SelectedStreamsFilteringXmlWriter(String stream, Writer writer) {
this(ImmutableSet.of(stream), writer);
}
public SelectedStreamsFilteringXmlWriter(Set<String> streams, Writer writer) {
this(s -> streams.contains(s), writer);
}
public SelectedStreamsFilteringXmlWriter(String stream, OutputStream out) {
this(stream, new OutputStreamWriter(out));
}
public SelectedStreamsFilteringXmlWriter(Predicate<String> allowStreams, OutputStream out) {
this(allowStreams, new OutputStreamWriter(out));
}
protected void processAttributeValue(String attribute, String value, String tag) {
if ("S".equalsIgnoreCase(tag) && "S".equalsIgnoreCase(attribute)
&& allowStreams.test(
StringEscapes.BashStringEscapes.unwrapBashQuotesAndEscapes(value.trim()).toLowerCase())
) {
allowTextHere();
}
}
}
private final Writer wrappedWriter;
// means we give up, probably not xml, just echo
private boolean passThrough;
private boolean waitingForContent = true;
private boolean inTag;
private boolean inTagName;
private String tag;
private boolean thisTagIsAnEndTag;
private boolean thisTagIsSelfClosing;
private boolean inAttribute;
private String attribute;
private boolean inValue;
private String value;
private boolean inComment;
private String textWrittenHere;
private boolean textAllowedHere;
private boolean isLastCharBackslashEscaped;
private boolean isLastCharLineStart;
private boolean cacheLastCharLineStart;
public FilteringXmlWriter(Writer writer) {
super(writer);
wrappedWriter = writer;
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
char c;
cacheLastCharLineStart = true;
for (int i = off; i < off + len; i++) {
isLastCharLineStart = cacheLastCharLineStart;
c = cbuf[i];
cacheLastCharLineStart = (c=='\n');
if (passThrough) {
writeChar(c);
continue;
}
try {
if (inComment) {
// Currently in a comment (weird MS comment not xml comment)
if (c == '\n') {
inComment = false;
}
continue;
}
if (isLastCharLineStart && c == '#') {
inComment = true;
continue;
}
if (inTag) {
if (inTagName) {
if (c == ' ') {
if (!tag.isEmpty()) {
onTagBegin(tag);
inAttribute = true;
attribute = "";
} else {
// ignore
}
} else if (c == '>') {
onTagBegin(tag);
processTagFinished(tag);
} else if (c == '/') {
// nothing
if (tag.isEmpty()) {
thisTagIsAnEndTag = true;
} else {
onTagBegin(tag);
}
} else {
tag = tag + c;
}
continue;
}
if (inAttribute) {
if (c == '=') {
inAttribute = false;
inValue = true;
value = "";
continue;
} else if (c == ' ') {
if (Strings.isBlank(attribute)) {
// skip
} else {
inAttribute = false;
}
} else if (c == '>') {
inAttribute = false;
inTag = false;
} else if (c == '/') {
// nothing
} else {
attribute = attribute + c;
}
continue;
}
if (inValue) {
if (c == '\\') {
isLastCharBackslashEscaped = true;
continue;
}
if (isLastCharBackslashEscaped) {
isLastCharBackslashEscaped = false;
value += "\\" + c;
continue;
}
if (c == '"') {
if (value.trim().isEmpty()) {
// string start
value += c;
continue;
} else {
// string end
value += c;
processAttributeValue(attribute, value, tag);
attribute = null;
value = null;
inValue = false;
continue;
}
}
value = value + c;
continue;
}
if (c == '=') {
inValue = true;
value = "";
continue;
}
if (c == '\n') {
continue;
}
if (c == ' ') {
continue;
}
if (c == '>') {
processTagFinished(tag);
continue;
}
// otherwise it starts a new attribute
inAttribute = true;
attribute = "";
continue;
}
if (c == '<') {
waitingForContent = false;
thisTagIsSelfClosing = false;
thisTagIsAnEndTag = false;
inTag = true;
inTagName = true;
thisTagIsAnEndTag = false;
tag = "";
endTextAllowed();
continue;
}
if (textAllowedHere) {
// could add to textWrittenHere here
writeChar(c);
}
if (waitingForContent && !Character.isWhitespace(c)) {
// not xml
writeChar(c);
passThrough = true;
continue;
}
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
LOG.debug("Error trying to process WinRM output as XML; switching to pass-through: "+e, e);
passThrough = true;
}
}
}
protected void endTextAllowed() throws IOException {
if (textAllowedHere && !isLastCharLineStart) {
writeChar('\n');
}
textWrittenHere = null;
textAllowedHere = false;
}
protected void onTagBegin(String tag) {
// might be start or end tag, we don't track that. we don't need this at all actually currently but maybe one day
// LOG.info(tag+":");
inTagName = false;
}
protected void processAttributeValue(String attribute, String value, String tag) {
// LOG.info(tag+" @"+attribute+ " = "+value);
}
protected void allowTextHere() {
textAllowedHere = true;
textWrittenHere = "";
}
protected void processTagFinished(String tag) {
// if (thisTagIsAnEndTag) LOG.info("/"+tag);
// else if (thisTagIsSelfClosing) LOG.info(" "+tag+"/");
// else LOG.info(" .");
inTag = false;
}
String buffered = null;
protected void writeChar(char c) throws IOException {
if (buffered !=null) {
buffered += c;
if (c=='_') {
if ("_x000A_".equalsIgnoreCase(buffered)) {
wrappedWriter.write('\n');
cacheLastCharLineStart = true;
} else if ("_x000D_".equalsIgnoreCase(buffered)) {
// suppress
} else {
wrappedWriter.write(buffered);
}
buffered = null;
}
return;
}
if (c=='_') {
buffered = ""+c;
return;
}
wrappedWriter.write(c);
}
@Override
public void flush() throws IOException {
wrappedWriter.flush();
}
@Override
public void close() throws IOException {
wrappedWriter.close();
}
}