/*
* Copyright (c) 2014-2017 Red Hat, Inc. and/or its affiliates.
*
* 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
*
* Contributors:
* Cheng Fang - Initial API and implementation
*/
package org.jberet.support.io;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Set;
import javax.batch.api.BatchProperty;
import javax.inject.Inject;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.jberet.support._private.SupportLogger;
import org.jberet.support._private.SupportMessages;
import static org.jberet.support.io.CsvProperties.APPEND;
import static org.jberet.support.io.CsvProperties.FAIL_IF_DIRS_NOT_EXIST;
import static org.jberet.support.io.CsvProperties.FAIL_IF_EXISTS;
import static org.jberet.support.io.CsvProperties.OVERWRITE;
import static org.jberet.support.io.CsvProperties.RESOURCE_KEY;
import static org.jberet.support.io.CsvProperties.WRITE_MODE_KEY;
/**
* The base class for all implementations of {@code javax.batch.api.chunk.ItemReader} and
* {@code javax.batch.api.chunk.ItemWriter}. It also holds batch artifact properties common to all subclasses.
*
* @since 1.0.2
*/
public abstract class ItemReaderWriterBase {
protected static final String NEW_LINE = System.getProperty("line.separator");
/**
* The resource to read from (for batch readers), or write to (for batch writers). Some reader or writer
* implementations may choose to ignore this property and instead use other properties that are more appropriate.
*
*/
@Inject
@BatchProperty
protected String resource;
/**
* Indicates whether the current batch reader will invoke Bean Validation API to validate the incoming data POJO.
* Optional property and defaults to false, i.e., the reader will validate data POJO bean where appropriate.
*/
@Inject
@BatchProperty
protected boolean skipBeanValidation;
boolean skipWritingHeader;
private static class Holder {
private static final Validator validator = getValidator0();
}
/**
* Gets a cached {@code javax.validation.Validator}.
*
* @return {@code javax.validation.Validator}
*/
public static Validator getValidator() {
return Holder.validator;
}
/**
* Performs Bean Validation on the passed {@code object}. If any constraint validation errors are found,
* {@link javax.validation.ConstraintViolationException} is thrown that includes all violation description.
*
* @param object the object to be validated
*/
public static void validate(final Object object) {
if (object != null) {
final Set<ConstraintViolation<Object>> violations = getValidator().validate(object);
if (violations.size() > 0) {
final StringBuilder sb = new StringBuilder();
for (final ConstraintViolation<Object> vio : violations) {
sb.append(NEW_LINE).append(vio.getConstraintDescriptor()).append(NEW_LINE).append(NEW_LINE);
sb.append(vio.getRootBean()).append(NEW_LINE);
sb.append(vio.getLeafBean()).append(NEW_LINE);
sb.append(vio.getPropertyPath()).append(NEW_LINE);
sb.append(vio.getInvalidValue()).append(NEW_LINE);
sb.append(vio.getMessage()).append(NEW_LINE).append(NEW_LINE);
}
throw new ConstraintViolationException(sb.toString(), violations);
}
}
}
private static Validator getValidator0() {
Validator v;
try {
v = InitialContext.doLookup("java:comp/Validator");
} catch (final NamingException e) {
final ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
v = vf.getValidator();
}
return v;
}
/**
* Gets an instance of {@code java.io.InputStream} that represents the reader resource.
*
* @param inputResource the location of the input resource
* @param detectBOM if need to detect byte-order mark (BOM). If true, the {@code InputStream} is wrapped inside
* {@code UnicodeBOMInputStream}
* @return {@code java.io.InputStream} that represents the reader resource
*/
protected static InputStream getInputStream(final String inputResource, final boolean detectBOM) {
if (inputResource == null) {
throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, null, RESOURCE_KEY);
}
InputStream inputStream;
try {
try {
final URL url = new URL(inputResource);
inputStream = url.openStream();
} catch (final MalformedURLException e) {
SupportLogger.LOGGER.tracef("The resource %s is not a URL, %s%n", inputResource, e);
final File file = new File(inputResource);
if (file.exists()) {
inputStream = new FileInputStream(file);
} else {
SupportLogger.LOGGER.tracef("The resource %s is not a file %n", inputResource);
if (inputResource.startsWith("[") && inputResource.endsWith("]")) {
inputStream = new ByteArrayInputStream(inputResource.getBytes());
} else {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = ItemReaderWriterBase.class.getClassLoader();
}
inputStream = cl.getResourceAsStream(inputResource);
}
}
}
if (detectBOM) {
final UnicodeBOMInputStream bomin = new UnicodeBOMInputStream(inputStream);
bomin.skipBOM();
return bomin;
}
} catch (final IOException e) {
throw SupportMessages.MESSAGES.failToOpenStream(e, inputResource);
}
return inputStream;
}
protected OutputStream getOutputStream(final String writeMode) {
if (resource == null) {
throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, null, RESOURCE_KEY);
}
try {
final File file = new File(resource);
final boolean exists = file.exists();
// isDirectory check is done in FileOutputStream constructor, no need to do here
//if (exists && file.isDirectory()) {
// throw SupportLogger.LOGGER.writerResourceIsDirectory(file);
//}
if (writeMode == null || writeMode.equalsIgnoreCase(APPEND)) {
return newFileOutputStream(file, exists, true, false);
}
if (writeMode.equalsIgnoreCase(OVERWRITE)) {
return newFileOutputStream(file, exists, false, false);
}
if (writeMode.equalsIgnoreCase(FAIL_IF_EXISTS)) {
if (exists) {
throw SupportMessages.MESSAGES.writerResourceAlreadyExists(resource);
}
return newFileOutputStream(file, false, false, false);
}
if (writeMode.startsWith(FAIL_IF_DIRS_NOT_EXIST)) {
// writeMode can be specified as along with overwrite
// writeMode = "failIfDirsNotExist"
// writeMode = "failIfDirsNotExist overwrite"
return newFileOutputStream(file, exists, !writeMode.endsWith(OVERWRITE), true);
}
throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, writeMode, WRITE_MODE_KEY);
} catch (final IOException e) {
throw SupportMessages.MESSAGES.invalidReaderWriterProperty(e, resource, RESOURCE_KEY);
}
}
/**
* Creates a new {@code FileOutputStream}, depending on the settings in parameters.
* If the parent directories of the target {@code file} do not exist, they will be
* automatically created, unless {@code failIfDirsNotExist} is true.
*
* @param file the writer target file
* @param exists whether the {@code file} exists
* @param append append mode if true; overwrite mode if false
* @param failIfDirsNotExist if true and if the parent dirs of {@code file} do not exist, throw exception
* @return the created {@code FileOutputStream}
* @throws IOException if exception from file operations
*/
private FileOutputStream newFileOutputStream(final File file,
final boolean exists,
final boolean append,
final boolean failIfDirsNotExist) throws IOException {
if (!exists) {
final File parentFile = file.getParentFile();
if (parentFile == null) {
throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, resource, RESOURCE_KEY);
}
if (!parentFile.exists()) {
if (failIfDirsNotExist) {
throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, resource, RESOURCE_KEY);
}
if (!parentFile.mkdirs()) {
throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, resource, RESOURCE_KEY);
}
}
}
final FileOutputStream fos = new FileOutputStream(file, append);
if (append && file.length() > 0) {
skipWritingHeader = true;
fos.write(NEW_LINE.getBytes());
}
return fos;
}
}