/**
* DataCleaner (community edition)
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.datacleaner.actions;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import javax.swing.SwingWorker;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MIME;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.AbstractContentBody;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.metamodel.util.FileHelper;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.job.tasks.Task;
import org.datacleaner.user.MonitorConnection;
import org.datacleaner.user.UserPreferences;
import org.datacleaner.util.WidgetUtils;
import org.datacleaner.util.http.MonitorHttpClient;
import org.datacleaner.windows.FileTransferProgressWindow;
import org.datacleaner.windows.MonitorConnectionDialog;
import org.jdesktop.swingx.action.OpenBrowserAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
/**
* Abstract {@link SwingWorker} and {@link ActionListener} for publishing a file
* to the DataCleaner monitor webapp.
*/
public abstract class PublishFileToMonitorActionListener extends SwingWorker<Map<?, ?>, Task>
implements ActionListener {
private static final Logger logger = LoggerFactory.getLogger(PublishFileToMonitorActionListener.class);
private final WindowContext _windowContext;
private final UserPreferences _userPreferences;
private FileTransferProgressWindow _progressWindow;
public PublishFileToMonitorActionListener(final WindowContext windowContext,
final UserPreferences userPreferences) {
super();
_windowContext = windowContext;
_userPreferences = userPreferences;
}
/**
* Simple replacement function that replaces white spaces with "+" symbols,
* making a filename retreivable by URL.
*
* @param str
* @return
*/
protected static String encodeSpaces(final String str) {
return str.replaceAll(" ", "\\+");
}
protected abstract String getTransferredFilename();
protected abstract String getUploadUrl(MonitorConnection monitorConnection);
protected abstract InputStream getTransferStream();
protected abstract long getExpectedSize();
protected boolean openBrowserWhenDone() {
return false;
}
@Override
public final void actionPerformed(final ActionEvent e) {
final MonitorConnection monitorConnection = _userPreferences.getMonitorConnection();
if (monitorConnection == null) {
final MonitorConnectionDialog dialog = new MonitorConnectionDialog(_windowContext, _userPreferences);
dialog.open();
} else {
final boolean cont = doBeforeAction();
if (!cont) {
return;
}
final String transferredFilename = getTransferredFilename();
if (Strings.isNullOrEmpty(transferredFilename)) {
return;
}
_progressWindow =
new FileTransferProgressWindow(_windowContext, null, new String[] { transferredFilename });
_progressWindow.open();
// start the swing worker
execute();
}
}
/**
* Optionally overrideable method invoked before doing the publishing
* action.
*
* @return true if the publishing may continue.
*/
protected boolean doBeforeAction() {
// do nothing
return true;
}
@Override
protected Map<?, ?> doInBackground() throws Exception {
final MonitorConnection monitorConnection = _userPreferences.getMonitorConnection();
final String uploadUrl = getUploadUrl(monitorConnection);
logger.debug("Upload url: {}", uploadUrl);
final HttpPost request = new HttpPost(uploadUrl);
final long expectedSize = getExpectedSize();
publish((Task) () -> _progressWindow.setExpectedSize(getTransferredFilename(), expectedSize));
final ContentBody uploadFilePart = new AbstractContentBody(ContentType.APPLICATION_OCTET_STREAM) {
@Override
public String getCharset() {
return null;
}
@Override
public String getTransferEncoding() {
return MIME.ENC_BINARY;
}
@Override
public long getContentLength() {
return expectedSize;
}
@Override
public void writeTo(final OutputStream out) throws IOException {
long progress = 0;
try (InputStream in = getTransferStream()) {
final byte[] tmp = new byte[4096];
int length;
while ((length = in.read(tmp)) != -1) {
out.write(tmp, 0, length);
// update the visual progress
progress = progress + length;
final long updatedProgress = progress;
publish((Task) () -> _progressWindow.setProgress(getTransferredFilename(), updatedProgress));
}
out.flush();
}
}
@Override
public String getFilename() {
return getTransferredFilename();
}
};
final HttpEntity entity = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
.addPart("file", uploadFilePart).build();
request.setEntity(entity);
try (MonitorHttpClient monitorHttpClient = monitorConnection.getHttpClient()) {
final HttpResponse response;
try {
response = monitorHttpClient.execute(request);
} catch (final Exception e) {
throw new IllegalStateException(e);
}
final StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() == 200) {
logger.info("Upload response status: {}", statusLine);
// parse the response as a JSON map
final InputStream content = response.getEntity().getContent();
final String contentString;
try {
contentString = FileHelper.readInputStreamAsString(content, FileHelper.DEFAULT_ENCODING);
} finally {
FileHelper.safeClose(content);
}
final ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(contentString, Map.class);
} catch (final Exception e) {
logger.warn("Received non-JSON response:\n{}", contentString);
logger.error("Failed to parse response as JSON", e);
return null;
}
} else {
logger.warn("Upload response status: {}", statusLine);
final String reasonPhrase = statusLine.getReasonPhrase();
WidgetUtils.showErrorMessage("Server reported error",
"Server replied with status " + statusLine.getStatusCode() + ":\n" + reasonPhrase);
return null;
}
}
}
@Override
protected void process(final List<Task> chunks) {
for (final Task task : chunks) {
try {
task.execute();
} catch (final Exception e) {
WidgetUtils.showErrorMessage("Error processing transfer chunk: " + task, e);
}
}
}
@Override
protected void done() {
final Map<?, ?> responseMap;
try {
responseMap = get();
} catch (final Exception e) {
WidgetUtils.showErrorMessage("Error transfering file(s)!", e);
return;
}
_progressWindow.setFinished(getTransferredFilename());
if (openBrowserWhenDone() && responseMap != null) {
final MonitorConnection monitorConnection = _userPreferences.getMonitorConnection();
final String repositoryPath = responseMap.get("repository_path").toString();
final OpenBrowserAction openBrowserAction = new OpenBrowserAction(
monitorConnection.getBaseUrl() + "/repository" + encodeSpaces(repositoryPath));
openBrowserAction.actionPerformed(null);
}
}
}