/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is part of dcm4che, an implementation of DICOM(TM) in
* Java(TM), hosted at https://github.com/gunterze/dcm4che.
*
* The Initial Developer of the Original Code is
* Agfa Healthcare.
* Portions created by the Initial Developer are Copyright (C) 2012-2015
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* See @authors listed below
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.dcm4chee.storage.test.unit.sftp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Properties;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;
import org.dcm4che3.net.Device;
import org.dcm4chee.storage.ObjectAlreadyExistsException;
import org.dcm4chee.storage.ObjectNotFoundException;
import org.dcm4chee.storage.RetrieveContext;
import org.dcm4chee.storage.StorageContext;
import org.dcm4chee.storage.conf.StorageDevice;
import org.dcm4chee.storage.conf.StorageDeviceExtension;
import org.dcm4chee.storage.conf.StorageSystem;
import org.dcm4chee.storage.conf.StorageSystemGroup;
import org.dcm4chee.storage.encrypt.StorageSystemProviderEncryptDecorator;
import org.dcm4chee.storage.sftp.SftpStorageSystemProvider;
import org.dcm4chee.storage.spi.StorageSystemProvider;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
/**
* @author Steve Kroetsch<stevekroetsch@hotmail.com>
*
*/
@RunWith(Arquillian.class)
public class SftpSystemStorageSystemProviderTest {
private static final String ID1 = "a/b/c";
private static final String ID2 = "x/y/z";
private static final Path LOCAL_FILE = Paths.get("target/test-storage/sftp/test");
private static final String DATA = "test";
@Deployment
public static JavaArchive createDeployment() {
return ShrinkWrap
.create(JavaArchive.class)
.addClass(SftpStorageSystemProvider.class)
.addClass(StorageSystemProviderEncryptDecorator.class)
.addAsManifestResource(
new StringAsset(
"<decorators><class>org.dcm4chee.storage.encrypt.StorageSystemProviderEncryptDecorator</class></decorators>"),
"beans.xml");
}
@Inject
@Named("org.dcm4chee.storage.sftp")
private StorageSystemProvider provider;
@Produces @StorageDevice
static Device device = new Device("test");
private String file1;
private String file2;
private StorageDeviceExtension ext;
private StorageSystemGroup sftpGroup;
private StorageSystem sftp;
private StorageContext storageCtx;
private RetrieveContext retrieveCtx;
private Session session;
private ChannelSftp channel;
@Before
public void setup() throws IOException, URISyntaxException, JSchException,
SftpException {
// Example: sftp://bob:secret@localhost/sftp-test
String prop = System.getProperty("sftpurl");
Assume.assumeNotNull(prop);
URI url = new URI(prop);
sftp = newStorageSystem(url);
ext = new StorageDeviceExtension();
device.addDeviceExtension(ext);
sftpGroup = new StorageSystemGroup();
sftpGroup.setGroupID("cifs");
ext.addStorageSystemGroup(sftpGroup);
sftp.setStorageSystemGroup(sftpGroup);
provider.init(sftp);
storageCtx = new StorageContext();
storageCtx.setStorageSystem(sftp);
storageCtx.setStorageSystemProvider(provider);
retrieveCtx = new RetrieveContext();
retrieveCtx.setStorageSystem(sftp);
retrieveCtx.setStorageSystemProvider(provider);
JSch jsch = new JSch();
int port = sftp.getStorageSystemPort();
session = jsch.getSession(sftp.getStorageSystemIdentity(),
sftp.getStorageSystemHostname(), port != -1 ? port : 22);
session.setPassword(sftp.getStorageSystemCredential());
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
file2 = resolvePath(ID2);
if (exists(file2))
delete(file2);
file1 = resolvePath(ID1);
if (!exists(file1)) {
mkdirs(getParentDir(file1));
try (Writer writer = new OutputStreamWriter(openOutputStream(file1))) {
writer.write(DATA);
}
}
if (!Files.exists(LOCAL_FILE)) {
Files.createDirectories(LOCAL_FILE.getParent());
try (Writer writer = Files.newBufferedWriter(LOCAL_FILE,
StandardCharsets.UTF_8)) {
writer.write(DATA);
}
}
}
private void mkdirs(String dir) throws SftpException {
String parent = getParentDir(dir);
if (parent == null)
return;
if (!exists(parent))
mkdirs(parent);
channel.mkdir(dir);
}
private String getParentDir(String path) {
int pos = path.lastIndexOf('/');
if (pos == -1)
return null;
String dir = path.substring(0, pos);
return dir.isEmpty() ? null : dir;
}
private boolean exists(String path) throws SftpException {
try {
channel.stat(path);
return true;
} catch (SftpException e) {
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
return false;
throw e;
}
}
private OutputStream openOutputStream(String dest) throws SftpException {
return channel.put(dest);
}
private void delete(String path) throws SftpException {
channel.rm(path);
}
private String resolvePath(String name) {
StringBuilder sb = new StringBuilder(sftp.getStorageSystemPath());
if (sb.charAt(sb.length() - 1) != '/')
sb.append('/');
sb.append(name);
return sb.toString();
}
private StorageSystem newStorageSystem(URI url) {
StorageSystem sftp = new StorageSystem();
sftp.setStorageSystemHostname(url.getHost());
sftp.setStorageSystemPort(url.getPort());
sftp.setStorageSystemPath(url.getPath());
String user = url.getUserInfo();
if (user != null) {
int colon = user.indexOf(':');
if (colon != -1) {
String pass = user.substring(colon + 1);
sftp.setStorageSystemCredential(pass);
user = user.substring(0, colon);
}
sftp.setStorageSystemIdentity(user);
}
return sftp;
}
@After
public void teardown() {
channel.disconnect();
session.disconnect();
device.removeDeviceExtension(ext);
ext = null;
sftpGroup = null;
sftp = null;
}
@Test
public void testOpenOutputStream() throws IOException, SftpException {
Assert.assertFalse(exists(file2));
try (OutputStream out = provider.openOutputStream(storageCtx, ID2)) {
Files.copy(LOCAL_FILE, out);
}
Assert.assertTrue(exists(file2));
Assert.assertEquals(Files.size(LOCAL_FILE),
storageCtx.getFileSize());
}
@Test(expected = ObjectAlreadyExistsException.class)
public void testOpenOutputStreamThrowsException() throws IOException {
provider.openOutputStream(storageCtx, ID1).close();
}
@Test
public void testStoreFile() throws IOException, SftpException {
Assert.assertFalse(exists(file2));
provider.storeFile(storageCtx, LOCAL_FILE, ID2);
Assert.assertTrue(exists(file2));
Assert.assertEquals(Files.size(LOCAL_FILE),
storageCtx.getFileSize());
}
@Test(expected = ObjectAlreadyExistsException.class)
public void testStoreFileThrowsException() throws IOException {
provider.storeFile(storageCtx, LOCAL_FILE, ID1);
}
@Test
public void testCopyInputStream() throws IOException {
try (InputStream in = Files.newInputStream(LOCAL_FILE, StandardOpenOption.READ)) {
provider.copyInputStream(storageCtx, in, ID2);
}
Assert.assertEquals(Files.size(LOCAL_FILE),
storageCtx.getFileSize());
}
@Test
public void testMoveFile() throws IOException, SftpException {
Assert.assertTrue(Files.exists(LOCAL_FILE));
Assert.assertFalse(exists(file2));
provider.moveFile(storageCtx, LOCAL_FILE, ID2);
Assert.assertTrue(exists(file2));
Assert.assertFalse(Files.exists(LOCAL_FILE));
}
@Test
public void testDeleteObject() throws IOException, SftpException {
Assert.assertTrue(exists(file1));
provider.deleteObject(storageCtx, ID1);
Assert.assertFalse(exists(file1));
Assert.assertFalse(exists(getParentDir(file1)));
}
@Test(expected = ObjectNotFoundException.class)
public void testDeleteObjectThrowsException() throws IOException {
provider.deleteObject(storageCtx, ID2);
}
@Test
public void testOpenInputStream() throws IOException {
provider.openInputStream(retrieveCtx, ID1).close();
}
@Test(expected = ObjectNotFoundException.class)
public void testOpenInputStreamWithException() throws IOException {
provider.openInputStream(retrieveCtx, ID2).close();
}
}