blob: 8faad3a8efe4743d980301dd6cb25a94afe97d31 [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.businessobjects.control;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.StreamingOutput;
import org.eclipse.mdm.api.base.Transaction;
import org.eclipse.mdm.api.base.file.FileService;
import org.eclipse.mdm.api.base.model.ContextComponent;
import org.eclipse.mdm.api.base.model.ContextDescribable;
import org.eclipse.mdm.api.base.model.ContextRoot;
import org.eclipse.mdm.api.base.model.ContextType;
import org.eclipse.mdm.api.base.model.Entity;
import org.eclipse.mdm.api.base.model.Environment;
import org.eclipse.mdm.api.base.model.FileLink;
import org.eclipse.mdm.api.base.model.FileLink.Format;
import org.eclipse.mdm.api.base.model.FilesAttachable;
import org.eclipse.mdm.api.base.model.Measurement;
import org.eclipse.mdm.api.base.model.MimeType;
import org.eclipse.mdm.api.base.model.TestStep;
import org.eclipse.mdm.api.base.model.Value;
import org.eclipse.mdm.api.base.model.ValueType;
import org.eclipse.mdm.api.dflt.ApplicationContext;
import org.eclipse.mdm.api.dflt.EntityManager;
import org.eclipse.mdm.businessobjects.entity.FileSize;
import org.eclipse.mdm.businessobjects.service.ContextService;
import org.eclipse.mdm.businessobjects.service.DescribableContexts;
import org.eclipse.mdm.connector.boundary.ConnectorService;
import com.google.common.io.ByteStreams;
import io.vavr.Tuple;
import io.vavr.Tuple2;
@Stateless
public class FileLinkActivity {
@Inject
private ConnectorService connectorService;
@EJB
private ContextService contextService;
@EJB
private ContextActivity contextActivity;
/**
* Loads size of File with given remotePath.
*
* @param sourceName name of the source (MDM {@link Environment})
* @param filesAttachable the {@link FilesAttachable} {@link Entity} the file is
* attached to
* @param remotePath the remotePath
* @return the {@link FileSize}
*/
public FileSize loadFileSize(String sourceName, Entity entity, FileLink fileLink) {
FileService fileService = getFileService(sourceName);
return loadSize(entity, fileLink, fileService);
}
/**
* Loads sizes for all files attached to the given entity.
*
* @param sourceName name of the source (MDM {@link Environment})
* @param filesAttachable the {@link FilesAttachable} {@link Entity} the files
* are attached to
* @return the {@link FileSize}s as {@link List}
*/
public List<FileSize> loadFileSizes(String sourceName, FilesAttachable fileAttachable) {
FileService fileService = getFileService(sourceName);
return Arrays.asList(fileAttachable.getFileLinks()).stream()
.map(fileLink -> loadSize(fileAttachable, fileLink, fileService)).collect(Collectors.toList());
}
/**
* Helping function to trigger the actual file size loading process via given
* {@link FileService}
*
* @param filesAttachable the {@link FilesAttachable} {@link Entity} the file is
* attached to
* @param fileLink the {@link FileLink}
* @param fileService the {@link FileService}
* @return the {@link FileSize}
*/
private FileSize loadSize(Entity entity, FileLink fileLink, FileService fileService) {
FileSize fileSize = new FileSize();
try {
fileService.loadSize(entity, fileLink);
fileSize.setRemotePath(fileLink.getRemotePath());
fileSize.setSize(fileLink.getSize(Format.BINARY));
} catch (IOException e) {
throw new MDMFileAccessException(e.getMessage(), e);
}
return fileSize;
}
/**
* Persists file at file server. Creates a new {@link FileLink} (pointing at the
* new file) and adds it to the {@link FilesAttachable}
*
* @param sourceName name of the source (MDM {@link Environment})
* @param filesAttachable the {@link FilesAttachable} {@link Entity} the file is
* attached to
* @param fileName the name of the file
* @param fis the {@code InputStream} with the file data
* @param description the file description. Default value is the file name.
* @param mimeType the mimeType of the file.
* @return the {@link FileLink} to the created File
*/
public FileLink createFile(String sourceName, FilesAttachable filesAttachable, String fileName, InputStream fis,
String description, MimeType mimeType) {
FileLink fileLink = null;
try {
// Create new local FileLink
fileLink = newLocalFileLink(fileName, fis, description, mimeType);
// Upload fileLink. Upload will create remote path.
uploadFile(sourceName, filesAttachable, fileLink);
// Add file link to fileAttachable
filesAttachable.addFileLink(fileLink);
// Persist changes
persistEntity(sourceName, filesAttachable);
} catch (IOException e) {
throw new MDMFileAccessException(
String.format("File (%s, %s, %s) could not be created.", fileName, description, mimeType), e);
}
return fileLink;
}
/**
* Persists file at file server. Creates a new {@link FileLink} (pointing at the
* new file) and adds it to the specified context attribute
*
* @param sourceName name of the source (MDM {@link Environment})
* @param contextDescribable the {@link ContextDescribable}
* @param fileName the name of the file
* @param fis the {@code InputStream} with the file data
* @param description the file description. Default value is the file
* name.
* @param mimeType the mimeType of the file.
* @param contextType the {@link ContextType} holding the component
* @param contextComponentName the name of the component holding the file link
* attribute
* @param attributeName the name of the attribute holding the file link
* @return
*/
public FileLink createFile(String sourceName, ContextDescribable contextDescribable, String fileName,
InputStream fis, String description, MimeType mimeType, ContextType contextType,
String contextComponentName, String attributeName) {
Map<ContextType, ContextRoot> contextMap = getContextMap(sourceName, contextDescribable, contextType);
Value value = findValueInContext(contextType, contextComponentName, contextMap, attributeName);
FileLink fileLink = null;
try {
// Create new local FileLink
fileLink = newLocalFileLink(fileName, fis, description, mimeType);
// Upload file to file server. Upload creates and sets remote path in fileLink.
uploadFile(sourceName, contextDescribable, fileLink);
// Since attributes of type FILE_LINK can hold a maximum of one file, the old
// file has to be deleted from fileServer
if (ValueType.FILE_LINK.equals(value.getValueType())) {
deleteFile(sourceName, contextDescribable, value.extract(ValueType.FILE_LINK));
}
// Adds file link to the context attribute
setOrAddFileLinkToValue(value, fileLink);
// Persist changes
DescribableContexts dc = createDescribableContext(contextDescribable, contextMap);
contextService.persist(dc);
} catch (IOException e) {
throw new MDMFileAccessException(
String.format("File (%s, %s, %s) could not be created.", fileName, description, mimeType), e);
}
return fileLink;
}
/**
* Deletes file from file server and removes the related {@link FileLink} from
* the {@link FilesAttachable}
*
* @param sourceName name of the source (MDM {@link Environment})
* @param filesAttachable the {@link FilesAttachable} {@link Entity} the file is
* attached to
* @param remotePath the remotePath
* @return the {@link FileLink} to the deleted File
*/
public FileLink deleteFileLink(String sourceName, FilesAttachable filesAttachable, String remotePath) {
// Find file link
FileLink fileLink = findFileLinkAtFileAttachable(remotePath, filesAttachable);
// Delete file from FileServer
deleteFile(sourceName, filesAttachable, fileLink);
// Remove FileLink from FileAttachable
filesAttachable.removeFileLink(fileLink);
// Persist changes
persistEntity(sourceName, filesAttachable);
return fileLink;
}
/**
* Deletes file from file server and removes the related {@link FileLink} from
* the specified context attribute.
*
* @param sourceName name of the source (MDM {@link Environment})
* @param contextDescribable the context describable
* @param contextType the {@link ContextType} holding the component
* @param contextComponentName the name of the component holding the file link
* attribute
* @param attributeName the name of the attribute holding the file link
* @param remotePath the remotePath
* @return
*/
public FileLink deleteFileLink(String sourceName, ContextDescribable contextDescribable, ContextType contextType,
String contextComponentName, String attributeName, String remotePath) {
// Find link attribute in context
Map<ContextType, ContextRoot> contextMap = getContextMap(sourceName, contextDescribable, contextType);
Value value = findValueInContext(contextType, contextComponentName, contextMap, attributeName);
FileLink fileLink = extractFileLink(remotePath, value);
// Delete the file from file Server
deleteFile(sourceName, contextDescribable, fileLink);
// Removes file link from the context attribute
removeFileLinkfromValue(value, fileLink);
// Persist changes
DescribableContexts dc = createDescribableContext(contextDescribable, contextMap);
contextService.persist(dc);
return fileLink;
}
/**
* Helping function to find the {@link FileLink} with the specified remotePath
* attached to the given entity.
*
* @param remotePath the remotePath
* @param filesAttachable the {@link ContextDescribable} the file is attached to
* @return the specified {@link FileLink}
*/
public FileLink findFileLinkInContext(String remotePath, String sourceName, ContextDescribable contextDescribable,
ContextType contextType, String contextComponentName, String attributeName) {
Map<ContextType, ContextRoot> contextMap = getContextMap(sourceName, contextDescribable, contextType);
Value value = findValueInContext(contextType, contextComponentName, contextMap, attributeName);
return extractFileLink(remotePath, value);
}
/**
* Helping function to find the {@link FileLink} with the specified remotePath
* attached to the given entity.
*
* @param remotePath the remotePath
* @param filesAttachable the {@link FilesAttachable} the file is attached to
* @return the specified {@link FileLink}
*/
public FileLink findFileLinkAtFileAttachable(String remotePath, FilesAttachable entity) {
for (FileLink l : ((FilesAttachable) entity).getFileLinks()) {
if (l.isRemote() && l.getRemotePath().equals(remotePath)) {
return l;
}
}
throw new MDMEntityAccessException("FileLink with remotePath " + remotePath + " not found!");
}
public StreamingOutput toStreamingOutput(String sourceName, Entity entity, FileLink fileLink) {
return new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException, WebApplicationException {
streamFileLink(sourceName, entity, fileLink, output);
}
};
}
/**
* Creates {@link StreamingOutput} for file. Guesses mime-type to ensure proper
* display/download functionality at client side.
*
* @TODO Mime-type guess works only for limited types, but CorbaFileServer does
* not offer methods to properly load mime-type. Find a solution!
*
* @param sourceName
* @param entity
* @param fileLink
* @return
*/
public Tuple2<StreamingOutput, String> toStreamingOutputGuessMimeType(String sourceName, Entity entity,
FileLink fileLink) {
String m = null;
StreamingOutput o = null;
try {
InputStream in = getFileService(sourceName).openStream(entity, fileLink);
m = URLConnection.guessContentTypeFromStream(in);
o = new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException {
ByteStreams.copy(in, output);
}
};
} catch (IOException e) {
throw new MDMFileAccessException(e.getMessage(), e);
}
return Tuple.of(o, m);
}
/**
* Helping function to physically delete file from file server
*
* @param sourceName name of the source (MDM {@link Environment})
* @param entity the {@link Entity} the file is attached to or holding the
* context with the file attribute
* @param fileLink the {@link FileLink} related to the file
*/
private void deleteFile(String sourceName, Entity entity, FileLink fileLink) {
FileService fileService = getFileService(sourceName);
fileService.delete(entity, fileLink);
}
/**
* Helping function to physically upload file to file server
*
* @param sourceName name of the source (MDM {@link Environment})
* @param entity the {@link Entity} the file is attached to or holding the
* context with the file attribute
* @param fileLink the {@link FileLink} related to the file
*/
private void uploadFile(String sourceName, Entity entity, FileLink fileLink) throws IOException {
FileService fileService = getFileService(sourceName);
List<FileLink> fileLinks = new ArrayList<>();
fileLinks.add(fileLink);
fileService.uploadSequential(entity, fileLinks, null);
}
/**
* Helping function to persist an {@link Entity}
*
* @param sourceName name of the source (MDM {@link Environment})
* @param entity the {@link Entity} the file is attached to or holding the
* context with the file attribute
*/
private void persistEntity(String sourceName, Entity entity) {
List<Entity> enteties = new ArrayList<>();
enteties.add(entity);
EntityManager em = getEntityManager(sourceName);
Transaction t = em.startTransaction();
t.update(enteties);
t.commit();
}
/**
* Helping function to create new local {@FileLink}. Sets filename as default
* description, if no description is provided.
*
* @param fileName the file name
* @param fis the file {@link InputStream}
* @param description the file description
* @param mimeType the files {@link MimeType}
* @return the new {@FileLink}
* @throws IOException
*/
private FileLink newLocalFileLink(String fileName, InputStream fis, String description, MimeType mimeType)
throws IOException {
String desc = (description == null || description.isEmpty()) ? fileName : description;
return FileLink.newLocal(fis, fileName, -1, mimeType, desc);
}
/**
* Helping function to load context for {@link ContextDescribable}s.
*
* Notice: For {@link TestStep}s only the ordered context is loaded. For
* {@link Measurement}s only the measured context is loaded.
*
* @param sourceName name of the source (MDM {@link Environment})
* @param contextDescribable the {@link ContextDescribable}
* @param contextType the {@link ContextType} holding the component
* @return
*/
private Map<ContextType, ContextRoot> getContextMap(String sourceName, ContextDescribable contextDescribable,
ContextType contextType) {
if (contextDescribable instanceof TestStep) {
return contextActivity.getTestStepContext(sourceName, contextDescribable.getID(), contextType)
.get(ContextActivity.CONTEXT_GROUP_ORDERED);
} else if (contextDescribable instanceof Measurement) {
return contextActivity.getMeasurementContext(sourceName, contextDescribable.getID(), contextType)
.get(ContextActivity.CONTEXT_GROUP_MEASURED);
}
throw new MDMEntityAccessException(
"No context of type " + contextType + " not found for " + contextDescribable.toString());
}
/**
* Helping function to add {@link FileLink} to a {@link Value}
*
* @param value the {@link Value}
* @param fileLink the {@link FileLink}
*/
private void setOrAddFileLinkToValue(Value value, FileLink fileLink) {
if (ValueType.FILE_LINK.equals(value.getValueType())) {
value.set(fileLink);
} else if (ValueType.FILE_LINK_SEQUENCE.equals(value.getValueType())) {
// delete if exists or create if not exists.
FileLink[] old = value.extract(ValueType.FILE_LINK_SEQUENCE);
FileLink[] current = Arrays.copyOf(old, old.length + 1);
current[old.length] = fileLink;
value.set(current);
}
}
/**
* Helping function to remove {@link FileLink} from a {@link Value}
*
* @param value the {@link Value}
* @param fileLink the {@link FileLink}
*/
private void removeFileLinkfromValue(Value value, FileLink fileLink) {
if (ValueType.FILE_LINK.equals(value.getValueType())) {
value.set(null);
} else if (ValueType.FILE_LINK_SEQUENCE.equals(value.getValueType())) {
FileLink[] links = Stream.of(value.extract(ValueType.FILE_LINK_SEQUENCE))
.filter(link -> !link.equals(fileLink)).toArray(FileLink[]::new);
value.set(links);
}
}
/**
* Helping function to create {@link DescribableContexts}
*
* @param contextDescribable the {@link ContextDescribable}
* @param contextMap the context
* @return
*/
private DescribableContexts createDescribableContext(ContextDescribable contextDescribable,
Map<ContextType, ContextRoot> contextMap) {
DescribableContexts dc = new DescribableContexts();
dc.setTestStep(contextService.getTestStep(contextDescribable));
if (contextDescribable instanceof TestStep) {
dc.setOrdered(contextMap);
}
if (contextDescribable instanceof Measurement) {
List<Measurement> list = new ArrayList<>();
list.add((Measurement) contextDescribable);
dc.setMeasurements(list);
dc.setMeasured(contextMap);
}
return dc;
}
/**
*
* Helping function to extract {@link Value} from context
*
* @param contextType
* @param componentName
* @param typeRootMap
* @param attributeName
* @return
*/
private Value findValueInContext(ContextType contextType, String componentName,
Map<ContextType, ContextRoot> typeRootMap, String attributeName) {
ContextRoot contextRoot = typeRootMap.get(contextType);
if (contextRoot != null) {
List<ContextComponent> components = contextRoot.getContextComponents().stream()
.filter(cc -> cc.getName().equals(componentName)).collect(Collectors.toList());
if (components != null && !components.isEmpty()) {
return components.get(0).getValue(attributeName);
}
}
throw new MDMEntityAccessException(
"ContextComponent with name " + componentName + " not found in context of type " + contextType);
}
/**
*
* @param remotePath
* @param value
* @return
*/
private FileLink extractFileLink(String remotePath, Value value) {
FileLink fileLink = null;
if (ValueType.FILE_LINK.equals(value.getValueType())) {
fileLink = value.extract(ValueType.FILE_LINK);
} else if (ValueType.FILE_LINK_SEQUENCE.equals(value.getValueType())) {
List<FileLink> fileLinks = Stream.of(value.extract(ValueType.FILE_LINK_SEQUENCE))
.filter(link -> link.getRemotePath().equals(remotePath)).collect(Collectors.toList());
fileLink = fileLinks.get(0);
}
return fileLink;
}
/**
* Opens an {@code InputStream} for given {@link FileLink} and copies it to
* {@param outputStream}.
*
* @param sourceName name of the source (MDM {@link Environment})
* @param filesAttachable the {@link FilesAttachable} {@link Entity} the file is
* attached to
* @param fileLink the {@link FileLink}
* @param outputStream the {link OutputStream}
* @throws IOException Thrown if unable to provide as stream
*/
private void streamFileLink(String sourceName, Entity entity, FileLink fileLink, OutputStream outputStream)
throws IOException {
try (InputStream in = getFileService(sourceName).openStream(entity, fileLink)) {
ByteStreams.copy(in, outputStream);
}
}
/**
* Helping function to load an {@link FileService}
*
* @param sourceName name of the source (MDM {@link Environment})
* @return the {@link FileService}
*/
private FileService getFileService(String sourceName) {
ApplicationContext context = this.connectorService.getContextByName(sourceName);
return context.getFileService()
.orElseThrow(() -> new MDMFileAccessException("FileService not present in '" + sourceName + "'."));
}
/**
* Helping function to load an {@link EntityManager}
*
* @param sourceName name of the source (MDM {@link Environment})
* @return the {@link EntityManager}
*/
private EntityManager getEntityManager(String sourceName) {
ApplicationContext context = this.connectorService.getContextByName(sourceName);
return context.getEntityManager()
.orElseThrow(() -> new MDMEntityAccessException("Entity manager not present in '" + sourceName + "'."));
}
}