org.atmosphere.util.IOUtils Maven / Gradle / Ivy
/*
* Copyright 2014 Jeanfrancois Arcand
*
* Licensed 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.atmosphere.util;
import org.atmosphere.config.service.DeliverTo;
import org.atmosphere.cpr.AtmosphereConfig;
import org.atmosphere.cpr.AtmosphereFramework;
import org.atmosphere.cpr.AtmosphereRequest;
import org.atmosphere.cpr.AtmosphereResource;
import org.atmosphere.cpr.AtmosphereServlet;
import org.atmosphere.cpr.Broadcaster;
import org.atmosphere.cpr.MeteorServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.atmosphere.cpr.HeaderConfig.FORCE_BINARY;
import static org.atmosphere.cpr.HeaderConfig.X_ATMO_BINARY;
public class IOUtils {
private final static Logger logger = LoggerFactory.getLogger(IOUtils.class);
private final static List knownClasses;
private final static Pattern SERVLET_PATH_PATTERN = Pattern.compile("([\\/]?[\\w-[.]]+|[\\/]\\*\\*)+");
static {
knownClasses = new ArrayList() {
{
add(AtmosphereServlet.class.getName());
add(MeteorServlet.class.getName());
add("com.vaadin.server.VaadinServlet");
add("org.primefaces.push.PushServlet");
}
};
}
/**
*
* Delivers the given message according to the specified {@link org.atmosphere.config.service.DeliverTo configuration).
*
*
* @param o the message
* @param deliverConfig the annotation state
* @param defaultDeliver the strategy applied if deliverConfig is {@code null}
* @param r the resource
*/
public static void deliver(final Object o,
final DeliverTo deliverConfig,
final DeliverTo.DELIVER_TO defaultDeliver,
final AtmosphereResource r) {
final DeliverTo.DELIVER_TO deliverTo = deliverConfig == null ? defaultDeliver : deliverConfig.value();
switch (deliverTo) {
case RESOURCE:
if (o != null) {
try {
synchronized (r) {
if (String.class.isAssignableFrom(o.getClass())) {
r.write(o.toString()).getResponse().flushBuffer();
} else if (byte[].class.isAssignableFrom(o.getClass())) {
r.write((byte[]) o).getResponse().flushBuffer();
}
}
} catch (Exception ex) {
logger.warn("", ex);
}
}
break;
case BROADCASTER:
r.getBroadcaster().broadcast(o);
break;
case ALL:
for (Broadcaster b : r.getAtmosphereConfig().getBroadcasterFactory().lookupAll()) {
b.broadcast(o);
}
break;
}
}
public static Object readEntirely(AtmosphereResource r) throws IOException {
AtmosphereRequest request = r.getRequest();
return isBodyBinary(request) ? readEntirelyAsByte(r) : readEntirelyAsString(r).toString();
}
public final static boolean isBodyBinary(AtmosphereRequest request) {
if (request.getContentType() != null
&& request.getContentType().equalsIgnoreCase(FORCE_BINARY) || request.getHeader(X_ATMO_BINARY) != null)
return true;
return false;
}
public final static boolean isBodyEmpty(Object o) {
if (o != null && (String.class.isAssignableFrom(o.getClass()) && String.class.cast(o).isEmpty())
|| (Byte[].class.isAssignableFrom(o.getClass()) && Byte[].class.cast(o).length == 0)) {
return true;
} else {
return false;
}
}
public static StringBuilder readEntirelyAsString(AtmosphereResource r) throws IOException {
final StringBuilder stringBuilder = new StringBuilder();
AtmosphereRequest request = r.getRequest();
if (request.body().isEmpty()) {
BufferedReader bufferedReader = null;
try {
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
}
} catch (IllegalStateException ex) {
logger.trace("", ex);
Reader reader = request.getReader();
if (reader != null) {
bufferedReader = new BufferedReader(reader);
}
}
if (bufferedReader != null) {
char[] charBuffer = new char[8192];
int bytesRead = -1;
try {
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} catch (NullPointerException ex) {
// https://java.net/jira/browse/GRIZZLY-1676
}
} else {
stringBuilder.append("");
}
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
logger.warn("", ex);
}
}
}
} else {
AtmosphereRequest.Body body = request.body();
try {
stringBuilder.append(body.hasString() ? body.asString() : new String(body.asBytes(), body.byteOffset(), body.byteLength(), request.getCharacterEncoding()));
} catch (UnsupportedEncodingException e) {
logger.error("", e);
}
}
return stringBuilder;
}
public static byte[] readEntirelyAsByte(AtmosphereResource r) throws IOException {
AtmosphereRequest request = r.getRequest();
AtmosphereRequest.Body body = request.body();
if (request.body().isEmpty()) {
BufferedInputStream bufferedStream = null;
ByteArrayOutputStream bbIS = new ByteArrayOutputStream();
try {
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedStream = new BufferedInputStream(inputStream);
}
} catch (IllegalStateException ex) {
logger.trace("", ex);
Reader reader = request.getReader();
if (reader != null) {
bufferedStream = new BufferedInputStream(new ReaderInputStream(reader));
}
}
if (bufferedStream != null) {
byte[] bytes = new byte[8192];
int bytesRead = 0;
while (bytesRead != -1) {
bytesRead = bufferedStream.read(bytes);
if (bytesRead > 0)
bbIS.write(bytes, 0, bytesRead);
}
} else {
bbIS.write("".getBytes());
}
} finally {
if (bufferedStream != null) {
try {
bufferedStream.close();
} catch (IOException ex) {
logger.warn("", ex);
}
}
}
return bbIS.toByteArray();
} else if (body.hasString()) {
try {
return readEntirelyAsString(r).toString().getBytes(request.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
logger.error("", e);
}
} else if (body.hasBytes()) {
return Arrays.copyOfRange(body.asBytes(), body.byteOffset(), body.byteLength());
}
throw new IllegalStateException("No body " + r);
}
public static String guestServletPath(AtmosphereConfig config) {
String servletPath = "";
try {
// TODO: pick up the first one, will fail if there are two
servletPath = config.getServletContext().getServletRegistration(config.getServletConfig().getServletName()).getMappings().iterator().next();
servletPath = getCleanedServletPath(servletPath);
} catch (Exception ex) {
logger.trace("", ex);
}
return servletPath;
}
/**
* Used to remove trailing slash and wildcard from a servlet path.
* Examples :
* - "/foo/" becomes "/foo"
* - "foo/bar" becomes "/foo/bar"
*
* @param fullServletPath : Servlet mapping
* @return Servlet mapping without trailing slash and wildcard
*/
public static String getCleanedServletPath(String fullServletPath) {
if (fullServletPath.equalsIgnoreCase("/*")) return "";
Matcher matcher = SERVLET_PATH_PATTERN.matcher(fullServletPath);
// It should not happen if the servlet path is valid
if (!matcher.find()) return fullServletPath;
String servletPath = matcher.group(0);
if (!servletPath.startsWith("/")) {
servletPath = "/" + servletPath;
}
return servletPath;
}
private static boolean scanForAtmosphereFramework(Class> classToScan) {
if (classToScan == null) return false;
logger.trace("Scanning {}", classToScan.getName());
// Before doing the Siberian traversal, look locally
if (knownClasses.contains(classToScan.getName())) {
return true;
}
try {
Field[] fields = classToScan.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
if (AtmosphereFramework.class.isAssignableFrom(f.getType())) {
return true;
}
}
} catch (Exception ex) {
logger.trace("", ex);
}
// Now try with parent
if (scanForAtmosphereFramework(classToScan.getSuperclass())) return true;
return false;
}
public static Class> loadClass(Class thisClass, String className) throws Exception {
try {
return Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (Throwable t) {
return thisClass.getClassLoader().loadClass(className);
}
}
public static boolean isAtmosphere(String className) {
Class extends AtmosphereServlet> clazz;
try {
clazz = (Class extends AtmosphereServlet>) Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (Throwable t) {
try {
clazz = (Class extends AtmosphereServlet>) IOUtils.class.getClassLoader().loadClass(className);
} catch (Exception ex) {
return false;
}
}
return AtmosphereServlet.class.isAssignableFrom(clazz);
}
/**
*
* This method reads the given file stored under "META-INF/services" and accessed through the framework's class loader
* to specify a list of {@link org.atmosphere.cpr.AtmosphereFramework.MetaServiceAction actions} to be done on different
* service classes ({@link org.atmosphere.cpr.AtmosphereInterceptor}, {@link org.atmosphere.cpr.BroadcastFilter}, etc).
*
*
*
* The file content should follows the following format:
*
* INSTALL
* com.mycompany.MyInterceptor
* com.mycompany.MyFilter
* EXCLUDE
* org.atmosphere.interceptor.HeartbeatInterceptor
*
*
*
*
* If you don't specify any {@link org.atmosphere.cpr.AtmosphereFramework.MetaServiceAction} before a class, then
* default action will be {@link org.atmosphere.cpr.AtmosphereFramework.MetaServiceAction#INSTALL}.
*
*
*
* Important note: you must specify a class declared inside a package. Since creating classes in the source root is
* a bad practice, the method does not deal with it to improve its performances.
*
*
* @param path the service file to read
* @return the map associating class to action
*/
public static Map readServiceFile(final String path) {
final Map b = new LinkedHashMap();
String line;
InputStream is = null;
BufferedReader reader = null;
AtmosphereFramework.MetaServiceAction action = AtmosphereFramework.MetaServiceAction.INSTALL;
try {
is = AtmosphereFramework.class.getClassLoader().getResourceAsStream("META-INF/services/" + path);
if (is == null) {
logger.info("META-INF/services/{} not found in class loader", path);
return b;
}
reader = new BufferedReader(new InputStreamReader(is));
while (true) {
line = reader.readLine();
if (line == null) {
break;
} else if (line.isEmpty()) {
continue;
} else if (line.indexOf('.') == -1) {
action = AtmosphereFramework.MetaServiceAction.valueOf(line);
} else {
b.put(line, action);
}
}
} catch (IOException e) {
logger.debug("Unable to read META-INF/services/{} from class loader", path, e);
} finally {
close(is, reader);
}
return b;
}
/**
*
* Tries to close the given objects and log the {@link IOException} at INFO level
* to make the code more readable when we assume that the {@link IOException} won't be managed.
*
*
*
* Also ignore {@code null} parameters.
*
*
* @param closeableArray the objects to close
*/
public static void close(final Closeable... closeableArray) {
for (Closeable closeable : closeableArray) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException ioe) {
logger.info("Can't close the object", ioe);
}
}
}
public static String realPath(ServletContext servletContext, String targetPath) throws MalformedURLException {
String realPath = servletContext.getRealPath(targetPath);
if (realPath == null) {
URL u = servletContext.getResource(targetPath);
if (u != null) {
realPath = u.getPath();
} else {
return "";
}
}
return realPath;
}
}