/** * 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); } } }