blob: d24d0eb93fb932ea75f38d07b4fbec39967927b9 [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.myfaces.tobago.internal.webapp;
import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
import org.apache.myfaces.tobago.renderkit.html.HtmlTypes;
import org.apache.myfaces.tobago.renderkit.html.MarkupLanguageAttributes;
import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.faces.component.UIComponent;
import java.io.IOException;
import java.io.Writer;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public abstract class TobagoResponseWriterBase extends TobagoResponseWriter {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected static final String XML_VERSION_1_0_ENCODING_UTF_8 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
protected static final char[] XML_VERSION_1_0_ENCODING_UTF_8_CHARS = XML_VERSION_1_0_ENCODING_UTF_8.toCharArray();
private int level = 0;
private int inlineStack = 0;
private UIComponent component;
private boolean startStillOpen;
private final Writer writer;
private final String contentType;
private final Charset charset;
/**
* @deprecated since 4.3.0
*/
@Deprecated
protected TobagoResponseWriterBase(final Writer writer, final String contentType, final String characterEncoding) {
this(writer, contentType, characterEncoding != null ? Charset.forName(characterEncoding) : StandardCharsets.UTF_8);
}
protected TobagoResponseWriterBase(final Writer writer, final String contentType, final Charset charset) {
this.writer = writer;
this.contentType = contentType;
this.charset = charset != null ? charset : StandardCharsets.UTF_8;
}
protected final Writer getWriter() {
return writer;
}
protected final UIComponent getComponent() {
return component;
}
protected final void setComponent(final UIComponent component) {
this.component = component;
}
protected final boolean isStartStillOpen() {
return startStillOpen;
}
protected final void setStartStillOpen(final boolean startStillOpen) {
this.startStillOpen = startStillOpen;
}
protected final String findValue(final Object value, final String property) {
if (value != null) {
return value instanceof String ? (String) value : value.toString();
} else if (property != null) {
if (component != null) {
final Object object = component.getAttributes().get(property);
if (object != null) {
return object instanceof String ? (String) object : object.toString();
} else {
return null;
}
} else {
final String trace = getCallingClassStackTraceElementString();
LOG.warn("Don't know what to do! "
+ "Property defined, but no component to get a value. (value=null, property='" + property + "') "
+ trace.substring(trace.indexOf('(')));
return null;
}
} else {
final String trace = getCallingClassStackTraceElementString();
LOG.warn("Don't know what to do! "
+ "No value and no property defined. (value=null, property=null)"
+ trace.substring(trace.indexOf('(')));
return null;
}
}
@Override
public void write(final char[] cbuf, final int off, final int len)
throws IOException {
writer.write(cbuf, off, len);
}
@Override
public void write(final String string) throws IOException {
writeInternal(writer, string);
}
protected final void writeInternal(final Writer sink, final String string) throws IOException {
closeOpenTag();
sink.write(string);
}
@Override
public void write(final int j) throws IOException {
closeOpenTag();
writer.write(j);
}
@Override
public void write(final char[] chars) throws IOException {
closeOpenTag();
writer.write(chars);
}
@Override
public void write(final String string, final int j, final int k) throws IOException {
closeOpenTag();
writer.write(string, j, k);
}
@Override
public void close() throws IOException {
closeOpenTag();
writer.close();
}
@Override
public void flush() throws IOException {
/*
From the api:
Flush any ouput buffered by the output method to the underlying Writer or OutputStream.
This method will not flush the underlying Writer or OutputStream;
it simply clears any values buffered by this ResponseWriter.
*/
closeOpenTag();
}
protected void closeOpenTag() throws IOException {
if (startStillOpen) {
writer.write('>');
startStillOpen = false;
}
}
@Override
public void startDocument() throws IOException {
// nothing to do
}
@Override
public void endDocument() throws IOException {
// nothing to do
}
@Override
public String getContentType() {
return contentType;
}
@Override
public String getCharacterEncoding() {
return charset.name();
}
@Override
public void startElement(final String name, final UIComponent currentComponent) throws IOException {
final boolean inline = HtmlElements.isInline(name);
if (inline) {
inlineStack++;
}
this.component = currentComponent;
startElementInternal(writer, name, HtmlElements.isInline(name));
}
@Override
public void startElement(final HtmlElements name) throws IOException {
final boolean inline = name.isInline();
if (inline) {
inlineStack++;
}
startElementInternal(writer, name.getValue(), name.isInline());
if (!name.isVoid()) {
level++;
}
}
protected void startElementInternal(final Writer sink, final String name, final boolean inline)
throws IOException {
if (startStillOpen) {
sink.write('>');
}
if (inlineStack <= 1) {
sink.write('\n');
for (int i = 0; i < level; i++) {
sink.write(' ');
}
}
sink.write('<');
sink.write(name);
startStillOpen = true;
}
@Override
public void endElement(final String name) throws IOException {
final boolean inline = HtmlElements.isInline(name);
if (HtmlElements.isVoid(name)) {
closeEmptyTag();
} else {
endElementInternal(writer, name, inline);
}
startStillOpen = false;
if (inline) {
inlineStack--;
assert inlineStack >= 0;
}
}
@Override
public void endElement(final HtmlElements name) throws IOException {
final boolean inline = name.isInline();
if (name.isVoid()) {
closeEmptyTag();
} else {
if (!name.isVoid()) {
level--;
}
endElementInternal(writer, name.getValue(), inline);
}
startStillOpen = false;
if (inline) {
inlineStack--;
assert inlineStack >= 0;
}
}
@Override
public void writeComment(final Object obj) throws IOException {
closeOpenTag();
final String comment = obj.toString();
writer.write('\n');
for (int i = 0; i < level; i++) {
writer.write(' ');
}
write("<!--");
write(comment);
write("-->");
}
/**
* @deprecated since 3.0.0
*/
@Override
@Deprecated
public void writeAttribute(final String name, final Object value, final String property)
throws IOException {
final String attribute = findValue(value, property);
writeAttribute(new MarkupLanguageAttributes() {
@Override
public String getValue() {
return name;
}
}, attribute, true);
}
protected final String getCallingClassStackTraceElementString() {
final StackTraceElement[] stackTrace = new Exception().getStackTrace();
int j = 1;
while (stackTrace[j].getClassName().contains("ResponseWriter")) {
j++;
}
return stackTrace[j].toString();
}
@Override
public void writeURIAttribute(final String name, final Object value, final String property)
throws IOException {
if (value != null) {
final URI uri = URI.create(value.toString());
writeAttribute(name, uri.toASCIIString(), property);
}
}
// interface TobagoResponseWriter //////////////////////////////////////////////////////////////////////////////////
@Override
public void writeAttribute(final MarkupLanguageAttributes name, final String value, final boolean escape)
throws IOException {
writeAttributeInternal(writer, name, value, escape);
}
@Override
public void writeAttribute(final MarkupLanguageAttributes name, final HtmlTypes types) throws IOException {
writeAttributeInternal(writer, name, types.getValue(), false);
}
@Override
public void writeURIAttribute(final MarkupLanguageAttributes name, final String value)
throws IOException {
if (value != null) {
final URI uri = URI.create(value);
writeAttribute(name, uri.toASCIIString(), true);
}
}
protected void endElementInternal(final Writer sink, final String name, final boolean inline) throws IOException {
if (startStillOpen) {
sink.write('>');
}
if (inline) {
sink.write("</");
} else {
sink.write('\n');
for (int i = 0; i < level; i++) {
sink.write(' ');
}
sink.write("</");
}
sink.write(name);
sink.write('>');
}
protected abstract void closeEmptyTag() throws IOException;
protected void writeAttributeInternal(
final Writer sink, final MarkupLanguageAttributes name, final String value, final boolean escape)
throws IOException {
if (!startStillOpen) {
final String trace = getCallingClassStackTraceElementString();
final String error = "Cannot write attribute when start-tag not open. "
+ "name = '" + name + "' "
+ "value = '" + value + "' "
+ trace.substring(trace.indexOf('('));
LOG.error(error);
throw new IllegalStateException(error);
}
if (value != null) {
sink.write(' ');
sink.write(name.getValue());
sink.write("='");
writerAttributeValue(value, escape);
sink.write('\'');
}
}
protected abstract void writerAttributeValue(String value, boolean escape) throws IOException;
}