/*
* Copyright 2015 Max Schuster.
*
* 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 eu.maxschuster.vaadin.signaturefield.converter;
import com.vaadin.data.util.converter.Converter;
import eu.maxschuster.dataurl.DataUrl;
import eu.maxschuster.dataurl.DataUrlEncoding;
import eu.maxschuster.dataurl.DataUrlSerializer;
import eu.maxschuster.dataurl.IDataUrlSerializer;
import eu.maxschuster.vaadin.signaturefield.shared.MimeType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URLConnection;
import java.util.Locale;
/**
* A converter that converts from an RFC 2397 data url {@link String} to
* a {@code byte[]} and back.
*
* @author Max Schuster
*/
public class StringToByteArrayConverter implements Converter<String, byte[]> {
private static final long serialVersionUID = 1L;
/**
* Images are always encoded using Base64.
*/
private final DataUrlEncoding encoding = DataUrlEncoding.BASE64;
/**
* An optional {@link MimeType} that is used while converting.
* If it is {@code null} the converter will try to guess the
* {@link MimeType} while converting to {@link DataUrl}.
*/
private final MimeType mimeType;
/**
* {@link IDataUrlSerializer} used to extract the binary contents of the
* RFC 2397 data url {@link String}.
*/
private final IDataUrlSerializer serializer;
/**
* Creates a new {@link StringToByteArrayConverter} with the given
* {@link IDataUrlSerializer} and {@link MimeType}.
*
* @param serializer {@link IDataUrlSerializer} used to extract the binary contents of the
* RFC 2397 data url {@link String}. Must not be {@code null}!
* @param mimeType {@link MimeType} that is used while converting. Must not
* be {@code null}!
* @throws NullPointerException If {@code serializer} or {@code mimeType} is
* {@code null}
*/
public StringToByteArrayConverter(IDataUrlSerializer serializer,
MimeType mimeType) throws NullPointerException {
if (serializer == null) {
throw new NullPointerException("The searializer is mandatory and "
+ "mustn't be null");
}
if (mimeType == null) {
throw new NullPointerException("The mime Type is mandatory and "
+ "mustn't be null inside this constructor");
}
this.serializer = serializer;
this.mimeType = mimeType;
}
/**
* Creates a new {@link StringToByteArrayConverter} with the given
* {@link IDataUrlSerializer} and automatic {@link MimeType} guessing.
*
* @param serializer {@link IDataUrlSerializer} used to extract the binary contents of the
* RFC 2397 data url {@link String}. Must not be {@code null}!
* @throws NullPointerException If {@code serializer} is {@code null}
*/
public StringToByteArrayConverter(IDataUrlSerializer serializer)
throws NullPointerException {
if (serializer == null) {
throw new NullPointerException("The searializer is mandatory and "
+ "mustn't be null");
}
this.serializer = serializer;
this.mimeType = null;
}
/**
* Creates a new {@link StringToByteArrayConverter} with the default
* {@link IDataUrlSerializer} and automatic {@link MimeType} guessing.
*
* @param mimeType {@link MimeType} that is used while converting. Must not
* be {@code null}!
* @throws NullPointerException If {@code mimeType} is {@code null}
*/
public StringToByteArrayConverter(MimeType mimeType)
throws NullPointerException {
this(new DataUrlSerializer(), mimeType);
}
/**
* Creates a new {@link StringToByteArrayConverter} with the default
* {@link IDataUrlSerializer} and automatic {@link MimeType} guessing.
*/
public StringToByteArrayConverter() {
this(new DataUrlSerializer());
}
@Override
public byte[] convertToModel(String value,
Class<? extends byte[]> targetType, Locale locale)
throws ConversionException {
if (value == null) {
return null;
}
try {
DataUrl dataUrl = serializer.unserialize(value);
// If a MimeType was defined make sure that the data url has the
// same MIME-Type
if (mimeType != null && !matchMimeType(dataUrl, mimeType)) {
throw new ConversionException("The MIME-Type of the given "
+ "RFC 2397 data url String (" + dataUrl.getMimeType()
+ ") doesn't match the required MimeType ("
+ mimeType.getMimeType() + ")" );
}
return dataUrl.getData();
} catch (MalformedURLException ex) {
throw new ConversionException(ex);
}
}
@Override
public String convertToPresentation(byte[] value,
Class<? extends String> targetType, Locale locale)
throws ConversionException {
if (value == null) {
return null;
}
MimeType appliedMimeType;
if (mimeType != null) {
appliedMimeType = mimeType;
} else {
try {
appliedMimeType = guessMimeType(value);
} catch (IOException e) {
throw new ConversionException(
"There was a problem with the stream", e);
} catch (IllegalArgumentException e) {
throw new ConversionException("Properly the MIME-Type guessing "
+ "returned an unsupported MIME-Type", e);
} catch (NullPointerException e) {
throw new ConversionException("Properly the MIME-Type guessing "
+ "failed and returned null", e);
}
}
DataUrl dataUrl = new DataUrl(value, encoding,
appliedMimeType.getMimeType());
try {
return serializer.serialize(dataUrl);
} catch (MalformedURLException e) {
throw new ConversionException(e);
}
}
@Override
public Class<byte[]> getModelType() {
return byte[].class;
}
@Override
public Class<String> getPresentationType() {
return String.class;
}
/**
* Guesses the {@link MimeType} of the given {@code byte[]} contents.
*
* @param data The image data.
* @return The matching {@link MimeType}.
* @see URLConnection#guessContentTypeFromStream(InputStream)
* @throws IOException If something goes wrong with the data stream.
* @throws IllegalArgumentException If the guessing resulted in an
* unsupported MIME-Type.
* @throws NullPointerException If the guessing did not find any matching
* MIME-Type.
*/
protected MimeType guessMimeType(byte[] data)
throws IOException, IllegalArgumentException, NullPointerException {
String mimeTypeString;
InputStream is = null;
try {
is = new ByteArrayInputStream(data);
mimeTypeString = URLConnection.guessContentTypeFromStream(is);
} finally {
if (is != null) {
is.close();
}
}
return MimeType.valueOfMimeType(mimeTypeString);
}
/**
* Matches the MIME-Type of the given {@code DataUrl} with the given
* {@link MimeType}.
*
* @param dataUrl The {@link DataUrl} to match.
* @param mimeType The {@link MimeType} to match against.
* @return MIME-Type matches the {@link MimeType}.
*/
protected boolean matchMimeType(DataUrl dataUrl, MimeType mimeType) {
MimeType dataUrlMimeType;
try {
dataUrlMimeType = MimeType.valueOfMimeType(dataUrl.getMimeType());
} catch (IllegalArgumentException e) {
// The MIME-Type is not supported
return false;
}
return mimeType.equals(dataUrlMimeType);
}
}