/* ***** 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-2014
* 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.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import org.dcm4che3.net.Device;
import org.dcm4che3.util.SafeClose;
import org.dcm4che3.util.TagUtils;
import org.dcm4chee.storage.ContainerEntry;
import org.dcm4chee.storage.ExtractTask;
import org.dcm4chee.storage.ObjectNotFoundException;
import org.dcm4chee.storage.RetrieveContext;
import org.dcm4chee.storage.conf.StorageDevice;
import org.dcm4chee.storage.conf.StorageDeviceExtension;
import org.dcm4chee.storage.conf.StorageSystem;
import org.dcm4chee.storage.service.RetrieveService;
import org.dcm4chee.storage.service.VerifyContainerException;
import org.dcm4chee.storage.spi.ContainerProvider;
import org.dcm4chee.storage.spi.FileCacheProvider;
import org.dcm4chee.storage.spi.StorageSystemProvider;
/**
* @author Gunter Zeilinger<gunterze@gmail.com>
* @author Steve Kroetsch<stevekroetsch@hotmail.com>
*
*/
@ApplicationScoped
public class RetrieveServiceImpl implements RetrieveService {
@Inject @StorageDevice
private Device device;
@Inject
private Instance<StorageSystemProvider> storageSystemProviders;
@Inject
private Instance<ContainerProvider> containerProviders;
@Inject
private Instance<FileCacheProvider> fileCacheProviders;
private final ConcurrentHashMap<ExtractTaskKey, ExtractTask> extractTasks =
new ConcurrentHashMap<ExtractTaskKey, ExtractTask>();
public StorageSystem getStorageSystem(String groupID, String systemID) {
StorageDeviceExtension devExt =
device.getDeviceExtension(StorageDeviceExtension.class);
return devExt.getStorageSystem(groupID, systemID);
}
@Override
public RetrieveContext createRetrieveContext(StorageSystem storageSystem) {
RetrieveContext ctx = new RetrieveContext();
ctx.setStorageSystemProvider(
storageSystem.getStorageSystemProvider(storageSystemProviders));
ctx.setContainerProvider(
storageSystem.getContainerProvider(containerProviders));
ctx.setFileCacheProvider(
storageSystem.getFileCacheProvider(fileCacheProviders));
ctx.setStorageSystem(storageSystem);
return ctx;
}
@Override
public InputStream openInputStream(final RetrieveContext ctx, String name)
throws IOException {
InputStream in;
if (ctx.getFileCacheProvider() != null) {
in = Files.newInputStream(getFile(ctx, name));
} else {
StorageSystemProvider provider = ctx.getStorageSystemProvider();
in = provider.openInputStream(ctx, name);
}
String digestAlgorithm = ctx.getStorageSystem().getStorageSystemGroup().getDigestAlgorithm();
if (digestAlgorithm != null) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance(digestAlgorithm);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Invalid digest algorithm,"
+ " check configuration for storage group "
+ ctx.getStorageSystem().getStorageSystemGroup().getGroupID());
}
in = new DigestInputStream(in, digest) {
@Override
public void close() throws IOException {
super.close();
ctx.setDigest(TagUtils.toHexString(getMessageDigest().digest()));
}
};
}
return in;
}
@Override
public InputStream openInputStream(RetrieveContext ctx, String name,
String entryName) throws IOException, InterruptedException {
if (ctx.getFileCacheProvider() != null)
return Files.newInputStream(getFile(ctx, name, entryName));
ContainerProvider containerProvider = ctx.getContainerProvider();
if (containerProvider == null)
throw new UnsupportedOperationException();
StorageSystemProvider provider = ctx.getStorageSystemProvider();
InputStream in = provider.openInputStream(ctx, name);
try {
return containerProvider.seekEntry(ctx, name, entryName, in);
} catch (IOException e) {
SafeClose.close(in);
throw e;
}
}
@Override
public Path getFile(RetrieveContext ctx, String name) throws IOException {
StorageSystemProvider provider = ctx.getStorageSystemProvider();
FileCacheProvider fileCacheProvider = ctx.getFileCacheProvider();
if (fileCacheProvider == null)
return provider.getFile(ctx, name);
Path path = fileCacheProvider.toPath(ctx, name);
if (fileCacheProvider.access(path))
return path;
try ( InputStream in = provider.openInputStream(ctx, name) ) {
Files.createDirectories(path.getParent());
Files.copy(in, path);
}
fileCacheProvider.register(ctx, name, path);
return path;
}
@Override
public Path getFile(RetrieveContext ctx, String name, String entryName)
throws IOException, InterruptedException {
ContainerProvider containerProvider = ctx.getContainerProvider();
if (containerProvider == null)
throw new UnsupportedOperationException();
FileCacheProvider fileCacheProvider = ctx.getFileCacheProvider();
if (fileCacheProvider == null)
throw new UnsupportedOperationException();
Path path = fileCacheProvider.toPath(ctx, name).resolve(entryName);
if (fileCacheProvider.access(path))
return path;
if (getExtractTask(ctx, name).getFile(entryName) == null
&& !fileCacheProvider.access(path))
throw new ObjectNotFoundException(
ctx.getStorageSystem().getStorageSystemPath(), name, entryName);
return path;
}
private ExtractTask getExtractTask(final RetrieveContext ctx, final String name) {
final ExtractTaskKey key = new ExtractTaskKey(ctx.getStorageSystem(), name);
final ExtractTask newTask = new ExtractTaskImpl(ctx, name);
ExtractTask prevTask = extractTasks.putIfAbsent(key, newTask);
if (prevTask != null)
return prevTask;
device.execute(new Runnable(){
@Override
public void run() {
try (InputStream in = ctx.getStorageSystemProvider()
.openInputStream(ctx, name)) {
ctx.getContainerProvider().extractEntries(ctx, name,
newTask, in);
} catch (IOException ex) {
newTask.exception(ex);
}
newTask.finished();
extractTasks.remove(key);
}});
return newTask;
}
@Override
public void verifyContainer(RetrieveContext ctx, String name,
List<ContainerEntry> expectedEntries) throws IOException,
VerifyContainerException {
ContainerProvider archiverProvider = ctx.getContainerProvider();
if (archiverProvider == null)
throw new UnsupportedOperationException();
StorageSystemProvider provider = ctx.getStorageSystemProvider();
InputStream in = provider.openInputStream(ctx, name);
TestExtractTask extractTask = new TestExtractTask();
try {
archiverProvider.extractEntries(ctx, name, extractTask, in);
} catch (IOException e) {
throw new VerifyContainerException("Extract failed for " + name, e);
} finally {
SafeClose.close(in);
}
List<String> entryNames = extractTask.getEntryNames();
for (ContainerEntry entry : expectedEntries)
if (!entryNames.contains(entry.getName()))
throw new VerifyContainerException("Missing container entry: "
+ entry.getName() + " from " + name);
}
@Override
public void resolveContainerEntries(List<ContainerEntry> entries) throws IOException,
InterruptedException {
for (ContainerEntry entry : entries) {
if (entry.getSourceName() != null && entry.getSourceStorageSystemID() != null
&& entry.getSourceStorageSystemGroupID() != null) {
StorageSystem storageSystem = getStorageSystem(
entry.getSourceStorageSystemGroupID(),
entry.getSourceStorageSystemID());
if (storageSystem == null) {
throw new IllegalStateException(
"StorageSystem not found for Source! StorageSystemGroupID="
+ entry.getSourceStorageSystemGroupID()
+ "StorageSystemID="
+ entry.getSourceStorageSystemID());
}
RetrieveContext retrieveCtx = createRetrieveContext(storageSystem);
Path path = entry.getSourceEntryName() == null ? getFile(retrieveCtx,
entry.getSourceName()) : getFile(retrieveCtx,
entry.getSourceName(), entry.getSourceEntryName());
entry.setSourcePath(path);
} else if (entry.getSourcePath() == null)
throw new IllegalStateException(
"Source path could not be resolved for container entry: " + entry);
}
}
@Override
public boolean calculateDigestAndMatch(RetrieveContext ctx, String digest, String name)
throws IOException {
try (InputStream in = openInputStream(ctx, name)) {
byte[] buffer = new byte[1024];
// read fully just to calculate digest
while (in.read(buffer) != -1)
;
}
String calculatedDigest = ctx.getDigest();
return calculatedDigest != null && calculatedDigest.equals(digest);
}
@Override
public <E extends Enum<E>> E queryStatus(RetrieveContext ctx, String name,
Class<E> enumType) throws IOException {
return ctx.getStorageSystemProvider().queryStatus(ctx, name, enumType);
}
}