/*******************************************************************************
* Copyright (c) Feb 20, 2011 Zend Technologies Ltd.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.zend.webapi.internal.core.connection.request;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import org.restlet.data.Disposition;
import org.restlet.data.MediaType;
import org.restlet.engine.header.DispositionWriter;
import org.restlet.engine.header.HeaderConstants;
import org.restlet.engine.header.HeaderUtils;
import org.restlet.engine.io.BioUtils;
import org.restlet.representation.OutputRepresentation;
import org.zend.webapi.core.connection.request.NamedInputStream;
import org.zend.webapi.core.connection.request.RequestParameter;
import org.zend.webapi.core.progress.BasicStatus;
import org.zend.webapi.core.progress.IChangeNotifier;
import org.zend.webapi.core.progress.IStatus;
import org.zend.webapi.core.progress.StatusCode;
/**
* Defines the multipart/form- data, which can be used by a wide variety of
* applications and transported by a wide variety of protocols as a way of
* returning a set of values as the result of a user filling out a form.
*
* @see http://www.ietf.org/rfc/rfc2388.txt
* @author Roy, 2011
*
*/
public class MultipartRepresentation extends OutputRepresentation {
/**
* Boundary prefix constant
*/
protected static final byte[] BOUNDRY_PREFIX = "--".getBytes();
/**
* Parameters to list in this presentation
*/
protected final List<RequestParameter<?>> parameters;
/**
* a random boundary string
*/
protected String boundary;
/**
* Content type for binary content. Default is
* {@link MediaType.MULTIPART_FORM_DATA}.
*/
protected MediaType mediaType;
protected IChangeNotifier notifier;
/**
* The content to be represented
*/
final protected byte[] contents;
public MultipartRepresentation(List<RequestParameter<?>> parameters,
String boundary, MediaType mediaType) {
super(createMediaType(boundary));
if (boundary == null) {
throw new IllegalArgumentException(
"Error initializing MultipartRepresentation, null boundary");
}
this.mediaType = mediaType;
this.parameters = parameters;
this.boundary = boundary;
this.contents = resolveContent();
this.setSize(this.contents.length);
}
/**
* @param byteArrayOutputStream
* @return
*/
private byte[] resolveContent() {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
internalWrite(byteArrayOutputStream);
} catch (IOException e) {
// very unlikely to happen (in byte array case)
return null;
}
return byteArrayOutputStream.toByteArray();
}
/**
* @param boundary
* @return
*/
private static MediaType createMediaType(String boundary) {
return new MediaType("multipart/form-data; boundary=" + boundary, null,
"Multipart form data");
}
public MultipartRepresentation(String boundary) {
this(null, boundary, MediaType.MULTIPART_FORM_DATA);
}
public MultipartRepresentation(List<RequestParameter<?>> parameters) {
this(parameters, getRandomBoundary(), MediaType.MULTIPART_FORM_DATA);
}
public MultipartRepresentation(List<RequestParameter<?>> parameters,
String boundary) {
this(parameters, boundary, MediaType.MULTIPART_FORM_DATA);
}
public MultipartRepresentation(List<RequestParameter<?>> parameters,
MediaType mediaType) {
this(parameters, getRandomBoundary(), mediaType);
}
public MultipartRepresentation(String boundary, MediaType mediaType) {
this(null, boundary, mediaType);
}
public MultipartRepresentation(MediaType mediaType) {
this(null, getRandomBoundary(), mediaType);
}
public void setNotifier(IChangeNotifier notifier) {
this.notifier = notifier;
}
/**
* Generates a random boundary
*/
private static String getRandomBoundary() {
Random r = new Random();
return Long.toString(Math.abs(r.nextLong()), 36);
}
/*
* (non-Javadoc)
*
* @see
* org.restlet.representation.Representation#write(java.io.OutputStream)
*/
@Override
public void write(OutputStream outputStream) throws IOException {
statusChanged(new BasicStatus(StatusCode.STARTING, "Package Sending",
"Sending package...", WebApiInputStream.STEPS));
BioUtils.copy(new WebApiInputStream(this.contents, notifier),
outputStream);
statusChanged(new BasicStatus(StatusCode.STOPPING, "Package Sending",
"Package has been sent successfully."));
}
/**
* internal writer method to prepare the content to write
*
* @param outputStream
* @throws IOException
*/
protected void internalWrite(OutputStream outputStream) throws IOException {
header(outputStream);
for (RequestParameter<?> parameter : parameters) {
writeParameter(outputStream, parameter);
writeSeparator(outputStream);
}
footer(outputStream);
}
private void header(OutputStream outputStream) throws IOException {
outputStream.write(HeaderConstants.HEADER_CONTENT_TYPE.getBytes());
outputStream.write(": ".getBytes());
outputStream.write(this.getMediaType().getName().getBytes());
writeNewLine(outputStream, 2);
writeSeparator(outputStream);
}
private void writeParameter(OutputStream outputStream,
RequestParameter<?> parameter) throws IOException {
final Object value = parameter.getValue();
if (value instanceof Map<?, ?>) {
writeHashMapParameter(outputStream, parameter, value);
} else if (value instanceof String[]) {
writeArrayParameter(outputStream, parameter, value);
}else {
writeNewLine(outputStream, 1);
Disposition d = createContentDisposition(outputStream);
d.getParameters().add("name", parameter.getKey());
if (value instanceof NamedInputStream) {
d.setFilename(((NamedInputStream) value).getName());
}
final String write = DispositionWriter.write(d);
outputStream.write(write.getBytes());
// content type parameter
if (parameter.getValue() instanceof NamedInputStream) {
writeNewLine(outputStream, 1);
outputStream.write(HeaderConstants.HEADER_CONTENT_TYPE
.getBytes());
outputStream.write(": ".getBytes());
outputStream.write(mediaType.getName().getBytes());
}
// writing parameter's value
writeNewLine(outputStream, 2);
BioUtils.copy(parameter.getValueAsStream(), outputStream);
writeNewLine(outputStream, 1);
}
}
private Disposition createContentDisposition(OutputStream outputStream)
throws IOException {
outputStream.write(HeaderConstants.HEADER_CONTENT_DISPOSITION
.getBytes());
outputStream.write(": ".getBytes());
// disposition
Disposition d = new Disposition("form-data");
return d;
}
private void writeArrayParameter(OutputStream outputStream,
RequestParameter<?> parameter, final Object value)
throws IOException {
writeNewLine(outputStream, 1);
List<String> values = new ArrayList<String>();
for (Object val : (Object[]) value) {
values.add((String) val);
}
int iterator = values.size();
for (String param : values) {
Disposition d = createContentDisposition(outputStream);
d.getParameters().add("name", parameter.getFirst() + "[]");
final String write = DispositionWriter.write(d);
outputStream.write(write.getBytes());
writeNewLine(outputStream, 2);
outputStream.write(param.getBytes());
writeNewLine(outputStream, 1);
if (--iterator != 0) {
writeSeparator(outputStream);
writeNewLine(outputStream, 1);
}
}
}
@SuppressWarnings("unchecked")
private void writeHashMapParameter(OutputStream outputStream,
RequestParameter<?> parameter, final Object value)
throws IOException {
Map<String, String> map = (Map<String, String>) value;
String mapName = parameter.getKey();
Set<Entry<String, String>> entries = map.entrySet();
writeNewLine(outputStream, 1);
int iterator = entries.size();
for (Entry<String, String> entry : entries) {
Disposition d = createContentDisposition(outputStream);
d.getParameters().add("name", mapName + "[" + entry.getKey() + "]");
final String write = DispositionWriter.write(d);
outputStream.write(write.getBytes());
writeNewLine(outputStream, 2);
outputStream.write(entry.getValue().getBytes());
writeNewLine(outputStream, 1);
if (--iterator != 0) {
writeSeparator(outputStream);
writeNewLine(outputStream, 1);
}
}
}
private void footer(OutputStream outputStream) throws IOException {
outputStream.write(BOUNDRY_PREFIX);
}
/**
* @param outputStream
* @throws IOException
*/
private void writeSeparator(OutputStream outputStream) throws IOException {
outputStream.write(BOUNDRY_PREFIX);
outputStream.write(this.boundary.getBytes());
}
/**
* @param outputStream
* @param times
* @throws IOException
*/
private void writeNewLine(OutputStream outputStream, int times)
throws IOException {
for (int j = 0; j < times; j++) {
HeaderUtils.writeCRLF(outputStream);
}
}
/**
* updates the size of the representation by a given size
*
* @param i
*/
protected void incrementSize(int i) {
if (getSize() == UNKNOWN_SIZE) {
return;
}
setSize(getSize() + i);
}
private void statusChanged(IStatus status) {
if (notifier != null) {
notifier.statusChanged(status);
}
}
}