Java tutorial
/* * Copyright 2002-2015 the original author or authors. * * 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 com.onedrive.api.internal; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.StreamingHttpOutputMessage; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.util.Assert; import org.springframework.util.MimeTypeUtils; import org.springframework.util.MultiValueMap; /** * Adaptation of {@link AllEncompassingFormHttpMessageConverter} and {@link FormHttpMessageConverter} to write (but not read) multipart/related data (e.g. file uploads). * * @author Carlos Guzman * @see MultiValueMap */ public class MultipartRelatedHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private static final MediaType MULTIPART_RELATED_MEDIA_TYPE = MediaType.valueOf("multipart/related"); private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>(); private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>(); public MultipartRelatedHttpMessageConverter() { this.supportedMediaTypes.add(MULTIPART_RELATED_MEDIA_TYPE); this.partConverters.add(new ByteArrayHttpMessageConverter()); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); this.partConverters.add(stringHttpMessageConverter); this.partConverters.add(new ResourceHttpMessageConverter()); this.partConverters.add(new MappingJackson2HttpMessageConverter()); } /** * Set the list of {@link MediaType} objects supported by this converter. */ public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) { this.supportedMediaTypes = supportedMediaTypes; } public List<MediaType> getSupportedMediaTypes() { return Collections.unmodifiableList(this.supportedMediaTypes); } /** * Set the message body converters to use. These converters are used to * convert objects to MIME parts. */ public void setPartConverters(List<HttpMessageConverter<?>> partConverters) { Assert.notEmpty(partConverters, "'partConverters' must not be empty"); this.partConverters = partConverters; } /** * Add a message body converter. Such a converter is used to convert objects * to MIME parts. */ public void addPartConverter(HttpMessageConverter<?> partConverter) { Assert.notNull(partConverter, "'partConverter' must not be null"); this.partConverters.add(partConverter); } public boolean canWrite(Class<?> clazz, MediaType mediaType) { if (!MultiValueMap.class.isAssignableFrom(clazz)) { return false; } if (mediaType == null || MediaType.ALL.equals(mediaType)) { return true; } for (MediaType supportedMediaType : getSupportedMediaTypes()) { if (supportedMediaType.isCompatibleWith(mediaType)) { return true; } } return false; } @SuppressWarnings("unchecked") public void write(MultiValueMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { writeMultipart((MultiValueMap<String, Object>) map, outputMessage); } private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage) throws IOException { final byte[] boundary = generateMultipartBoundary(); Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII")); MediaType contentType = new MediaType(MULTIPART_RELATED_MEDIA_TYPE, parameters); HttpHeaders headers = outputMessage.getHeaders(); headers.setContentType(contentType); if (outputMessage instanceof StreamingHttpOutputMessage) { StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage; streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() { public void writeTo(OutputStream outputStream) throws IOException { writeParts(outputStream, parts, boundary); writeEnd(outputStream, boundary); } }); } else { writeParts(outputMessage.getBody(), parts, boundary); writeEnd(outputMessage.getBody(), boundary); } } private void writeParts(OutputStream os, MultiValueMap<String, Object> parts, byte[] boundary) throws IOException { for (Map.Entry<String, List<Object>> entry : parts.entrySet()) { String name = entry.getKey(); for (Object part : entry.getValue()) { if (part != null) { writeBoundary(os, boundary); writePart(name, getHttpEntity(part), os); writeNewLine(os); } } } } @SuppressWarnings("unchecked") private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException { Object partBody = partEntity.getBody(); Class<?> partType = partBody.getClass(); HttpHeaders partHeaders = partEntity.getHeaders(); MediaType partContentType = partHeaders.getContentType(); for (HttpMessageConverter<?> messageConverter : this.partConverters) { if (messageConverter.canWrite(partType, partContentType)) { HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os); StringBuilder builder = new StringBuilder("<").append(name).append('>'); multipartMessage.getHeaders().set("Content-ID", builder.toString()); if (!partHeaders.isEmpty()) { multipartMessage.getHeaders().putAll(partHeaders); } ((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage); return; } } throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " + "found for request type [" + partType.getName() + "]"); } /** * Generate a multipart boundary. * <p>This implementation delegates to * {@link MimeTypeUtils#generateMultipartBoundary()}. */ protected byte[] generateMultipartBoundary() { return MimeTypeUtils.generateMultipartBoundary(); } /** * Return an {@link HttpEntity} for the given part Object. * @param part the part to return an {@link HttpEntity} for * @return the part Object itself it is an {@link HttpEntity}, * or a newly built {@link HttpEntity} wrapper for that part */ protected HttpEntity<?> getHttpEntity(Object part) { if (part instanceof HttpEntity) { return (HttpEntity<?>) part; } else { return new HttpEntity<Object>(part); } } private void writeBoundary(OutputStream os, byte[] boundary) throws IOException { os.write('-'); os.write('-'); os.write(boundary); writeNewLine(os); } private static void writeEnd(OutputStream os, byte[] boundary) throws IOException { os.write('-'); os.write('-'); os.write(boundary); os.write('-'); os.write('-'); writeNewLine(os); } private static void writeNewLine(OutputStream os) throws IOException { os.write('\r'); os.write('\n'); } /** * Implementation of {@link org.springframework.http.HttpOutputMessage} used * to write a MIME multipart. */ private static class MultipartHttpOutputMessage implements HttpOutputMessage { private final OutputStream outputStream; private final HttpHeaders headers = new HttpHeaders(); private boolean headersWritten = false; public MultipartHttpOutputMessage(OutputStream outputStream) { this.outputStream = outputStream; } public HttpHeaders getHeaders() { return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); } public OutputStream getBody() throws IOException { writeHeaders(); return this.outputStream; } private void writeHeaders() throws IOException { if (!this.headersWritten) { for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) { byte[] headerName = getAsciiBytes(entry.getKey()); for (String headerValueString : entry.getValue()) { byte[] headerValue = getAsciiBytes(headerValueString); this.outputStream.write(headerName); this.outputStream.write(':'); this.outputStream.write(' '); this.outputStream.write(headerValue); writeNewLine(this.outputStream); } } writeNewLine(this.outputStream); this.headersWritten = true; } } private byte[] getAsciiBytes(String name) { try { return name.getBytes("US-ASCII"); } catch (UnsupportedEncodingException ex) { // Should not happen - US-ASCII is always supported. throw new IllegalStateException(ex); } } } public boolean canRead(Class<?> clazz, MediaType mediaType) { return false; } public MultiValueMap<String, ?> read(Class<? extends MultiValueMap<String, ?>> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } }