Skip to content

Commit

Permalink
Created FlushedCloseable interface to be passed to Context#setOutputS…
Browse files Browse the repository at this point in the history
…tream

Signed-off-by: jansupol <jan.supol@oracle.com>
  • Loading branch information
jansupol committed Sep 2, 2024
1 parent 6da18bd commit d6829fd
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

/**
* Common Jersey core io classes.
*/
package org.glassfish.jersey.io;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.io.spi;

import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.OutputStream;

/**
* A marker interface that the stream provided to Jersey can implement,
* noting that the stream does not need to call {@link #flush()} prior to {@link #close()}.
* That way, {@link #flush()} method is not called twice.
*
* <p>
* Usable by {@link javax.ws.rs.client.ClientRequestContext#setEntityStream(OutputStream)}.
* Usable by {@link javax.ws.rs.container.ContainerResponseContext#setEntityStream(OutputStream)}.
* </p>
*
* <p>
* This marker interface can be useful for the customer OutputStream to know the {@code flush} did not come from
* Jersey before close. By default, when the entity stream is to be closed by Jersey, {@code flush} is called first.
* </p>
*/
public interface FlushedCloseable extends Flushable, Closeable {
/**
* Flushes this stream by writing any buffered output to the underlying stream.
* Then closes this stream and releases any system resources associated
* with it. If the stream is already closed then invoking this
* method has no effect.
*
* <p> As noted in {@link AutoCloseable#close()}, cases where the
* close may fail require careful attention. It is strongly advised
* to relinquish the underlying resources and to internally
* <em>mark</em> the {@code Closeable} as closed, prior to throwing
* the {@code IOException}.
*
* @throws IOException if an I/O error occurs
*/
public void close() throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

/**
* Common Jersey core io SPI classes.
*/
package org.glassfish.jersey.io.spi;
Original file line number Diff line number Diff line change
Expand Up @@ -20,43 +20,33 @@
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import javax.ws.rs.ProcessingException;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.ext.RuntimeDelegate;

import org.glassfish.jersey.CommonProperties;
import org.glassfish.jersey.internal.RuntimeDelegateDecorator;
import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.collection.GuardianStringKeyMultivaluedMap;
import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.io.spi.FlushedCloseable;

/**
* Base outbound message context implementation.
Expand Down Expand Up @@ -572,11 +562,13 @@ public void close() {
if (hasEntity()) {
try {
final OutputStream es = getEntityStream();
es.flush();
if (!FlushedCloseable.class.isInstance(es)) {
es.flush();
}
es.close();
} catch (IOException e) {
// Happens when the client closed connection before receiving the full response.
// This is OK and not interesting in vast majority of the cases
// This is OK and not interesting in the vast majority of the cases
// hence the log level set to FINE to make sure it does not flood the log unnecessarily
// (especially for clients disconnecting from SSE listening, which is very common).
Logger.getLogger(OutboundMessageContext.class.getName()).log(Level.FINE, e.getMessage(), e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand All @@ -16,6 +16,9 @@

package org.glassfish.jersey.tests.e2e.common.message.internal;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
Expand All @@ -33,10 +36,13 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.RuntimeDelegate;

import org.glassfish.jersey.io.spi.FlushedCloseable;
import org.glassfish.jersey.message.internal.CookieProvider;
import org.glassfish.jersey.message.internal.OutboundMessageContext;
import org.glassfish.jersey.tests.e2e.common.TestRuntimeDelegate;

import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.contains;
Expand Down Expand Up @@ -271,4 +277,38 @@ public void testCopyConstructor() {
newCtx.setMediaType(MediaType.APPLICATION_XML_TYPE); // new value
Assertions.assertEquals(MediaType.APPLICATION_XML_TYPE, newCtx.getMediaType());
}

@Test
public void OutboundMessageContextFlushTest() throws IOException {
FlushCountOutputStream os = new FlushCountOutputStream();
OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
ctx.setEntity("Anything");
ctx.setEntityStream(os);
os.flush();
ctx.close();
MatcherAssert.assertThat(os.flushedCnt, Matchers.is(2));

os = new FlushedClosableOutputStream();
ctx = new OutboundMessageContext((Configuration) null);
ctx.setEntity("Anything2");
ctx.setEntityStream(os);
os.flush();
ctx.close();
MatcherAssert.assertThat(os.flushedCnt, Matchers.is(1));
}

private static class FlushCountOutputStream extends ByteArrayOutputStream {
private int flushedCnt = 0;

@Override
public void flush() throws IOException {
flushedCnt++;
super.flush();
}
}

private static class FlushedClosableOutputStream extends FlushCountOutputStream implements FlushedCloseable {

}
}

0 comments on commit d6829fd

Please sign in to comment.