package ameba.message.internal;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.RandomStringUtils;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.StreamingOutput;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Random;
/**
* <p>MediaStreaming class.</p>
*
* @author icode
*
*/
public class MediaStreaming implements StreamingOutput {
/**
* Constant <code>RANGE="Range"</code>
*/
public static final String RANGE = "Range";
/** Constant <code>CONTENT_RANGE="Content-Range"</code> */
public static final String CONTENT_RANGE = "Content-Range";
private static final String EMPTY_LINE = "\r\n";
private static final String MULTIPART_BYTERANGES = "multipart/byteranges; boundary=%s";
private static final String BOUNDARY_LINE_FORMAT = "--%s" + EMPTY_LINE;
private static final String CONTENT_TYPE_LINE_FORMAT = HttpHeaders.CONTENT_TYPE + ": %s" + EMPTY_LINE;
private static final String CONTENT_RANGE_FORMAT = "%s %d-%d/%d";
private static final String CONTENT_RANGE_LINE_FORMAT = CONTENT_RANGE + ": " + CONTENT_RANGE_FORMAT + EMPTY_LINE;
private static final Random RANDOM = new Random();
private MultivaluedMap<String, Object> headers;
private String contentType;
private Object entity;
private String range;
private StreamingProcess<Object> streamingProcess;
/**
* <p>Constructor for MediaStreaming.</p>
*
* @param entity a {@link java.lang.Object} object.
* @param range a {@link java.lang.String} object.
* @param streamingProcess a {@link ameba.message.internal.StreamingProcess} object.
* @param contentType a {@link javax.ws.rs.core.MediaType} object.
* @param headers a {@link javax.ws.rs.core.MultivaluedMap} object.
*/
public MediaStreaming(Object entity,
String range,
StreamingProcess<Object> streamingProcess,
javax.ws.rs.core.MediaType contentType,
MultivaluedMap<String, Object> headers) {
this.entity = entity;
this.streamingProcess = streamingProcess;
this.contentType = contentType.toString();
this.headers = headers;
this.range = range;
}
/** {@inheritDoc} */
@Override
public void write(OutputStream output) throws IOException, WebApplicationException {
List<Range> ranges = Lists.newArrayList();
String[] acceptRanges = range.split("=");
if (acceptRanges.length != 2) {
throw new BadRequestException(RANGE + " header error");
}
String accept = acceptRanges[0];
for (String range : acceptRanges[1].split(",")) {
String[] bounds = range.split("-");
ranges.add(new Range(Long.valueOf(bounds[0]), bounds.length == 2 ? Long.valueOf(bounds[1]) : null));
}
boolean multipart = ranges.size() > 1;
Object len = headers.getFirst(HttpHeaders.CONTENT_LENGTH);
long contentLength;
if (len != null) {
if (len instanceof String) {
contentLength = Long.parseLong((String) len);
} else if (len instanceof Long) {
contentLength = (Long) len;
} else {
contentLength = Long.parseLong(String.valueOf(len));
}
} else {
contentLength = streamingProcess.length(entity);
}
if (multipart) {
int count = RANDOM.nextInt(11) + 20;
String boundary = RandomStringUtils.randomAlphanumeric(count);
headers.remove(HttpHeaders.CONTENT_LENGTH);
headers.putSingle(HttpHeaders.CONTENT_TYPE, String.format(MULTIPART_BYTERANGES, boundary));
for (Range range : ranges) {
output.write(String.format(BOUNDARY_LINE_FORMAT, boundary).getBytes());
output.write(String.format(CONTENT_TYPE_LINE_FORMAT, contentType).getBytes());
long to = range.getTo(contentLength - 1);
output.write(
String.format(CONTENT_RANGE_LINE_FORMAT,
accept, range.getFrom(),
to,
contentLength
).getBytes()
);
output.write(EMPTY_LINE.getBytes());
long currentLength = to - range.getFrom() + 1;
streamingProcess.write(entity, output, range.getFrom(), range.to == null ? null : currentLength);
output.write(EMPTY_LINE.getBytes());
}
output.write(String.format(BOUNDARY_LINE_FORMAT, boundary + "--").getBytes());
} else {
Range range = ranges.get(0);
long to = range.getTo(contentLength - 1);
headers.putSingle(CONTENT_RANGE,
String.format(CONTENT_RANGE_FORMAT,
accept, range.getFrom(),
to, contentLength)
);
long currentLength = to - range.getFrom() + 1;
headers.putSingle(HttpHeaders.CONTENT_LENGTH, currentLength);
streamingProcess.write(entity, output, range.getFrom(), range.to == null ? null : currentLength);
}
}
public class Range {
private Long from;
private Long to;
public Range(Long from, Long to) {
this.from = from;
this.to = to;
}
public boolean contains(Long i) {
if (this.to == null) {
return (this.from <= i);
}
return (this.from <= i && i <= this.to);
}
public Long getFrom() {
return this.from;
}
public Long getTo(Long ifNull) {
return this.to == null ? ifNull : this.to;
}
}
}