/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE file at the root of the source
* tree and available online at
*
* https://github.com/keeps/roda
*/
package org.roda.core.model;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.roda.core.common.iterables.CloseableIterable;
import org.roda.core.data.exceptions.AuthorizationDeniedException;
import org.roda.core.data.exceptions.GenericException;
import org.roda.core.data.exceptions.NotFoundException;
import org.roda.core.data.exceptions.RequestNotValidException;
import org.roda.core.data.v2.IsRODAObject;
import org.roda.core.data.v2.LiteOptionalWithCause;
import org.roda.core.data.v2.LiteRODAObject;
import org.roda.core.data.v2.common.OptionalWithCause;
import org.roda.core.data.v2.formats.Format;
import org.roda.core.data.v2.ip.AIP;
import org.roda.core.data.v2.ip.DIP;
import org.roda.core.data.v2.ip.DIPFile;
import org.roda.core.data.v2.ip.File;
import org.roda.core.data.v2.ip.IndexedAIP;
import org.roda.core.data.v2.ip.IndexedDIP;
import org.roda.core.data.v2.ip.IndexedFile;
import org.roda.core.data.v2.ip.IndexedRepresentation;
import org.roda.core.data.v2.ip.Representation;
import org.roda.core.data.v2.ip.TransferredResource;
import org.roda.core.data.v2.ip.metadata.DescriptiveMetadata;
import org.roda.core.data.v2.ip.metadata.PreservationMetadata;
import org.roda.core.data.v2.ip.metadata.PreservationMetadata.PreservationMetadataType;
import org.roda.core.data.v2.jobs.IndexedReport;
import org.roda.core.data.v2.jobs.Job;
import org.roda.core.data.v2.jobs.Report;
import org.roda.core.data.v2.log.LogEntry;
import org.roda.core.data.v2.notifications.Notification;
import org.roda.core.data.v2.risks.IndexedRisk;
import org.roda.core.data.v2.risks.Risk;
import org.roda.core.data.v2.risks.RiskIncidence;
import org.roda.core.data.v2.user.Group;
import org.roda.core.data.v2.user.User;
import org.roda.core.model.utils.ModelUtils;
import org.roda.core.util.IdUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for IsRODAObject <-> LiteRODAObject conversions. Brief information
* about how objects information is stored in a LiteRODAObject:
*
* <table>
* <tr>
* <td>AIP</td>
* <td>org.roda.core.data.v2.ip.AIP | aipId</td>
* </tr>
* <tr>
* <td>DIP</td>
* <td>org.roda.core.data.v2.ip.DIP | dipId</td>
* </tr>
* <tr>
* <td>Format</td>
* <td>org.roda.core.data.v2.formats.Format | formatId</td>
* </tr>
* <tr>
* <td>Job</td>
* <td>org.roda.core.data.v2.jobs.Job | jobId</td>
* </tr>
* <tr>
* <td>Report</td>
* <td>org.roda.core.data.v2.jobs.Report | jobId | reportId</td>
* </tr>
* <tr>
* <td>Notification</td>
* <td>org.roda.core.data.v2.notifications.Notification | notificationId</td>
* </tr>
* <tr>
* <td>Risk</td>
* <td>org.roda.core.data.v2.risks.Risk | riskId</td>
* </tr>
* <tr>
* <td>RiskIncidence</td>
* <td>org.roda.core.data.v2.risks.RiskIncidence | riskincidenceId</td>
* </tr>
* <tr>
* <td>Representation</td>
* <td>org.roda.core.data.v2.ip.Representation | aipId | representationId</td>
* </tr>
* <tr>
* <td>TransferredResource</td>
* <td>org.roda.core.data.v2.ip.TransferredResource | fullPath</td>
* </tr>
* <tr>
* <td>DescriptiveMetadata</td>
* <td>org.roda.core.data.v2.ip.DescriptiveMetadata | aipId |
* descriptiveMetadataId</td>
* </tr>
* <tr>
* <td>DescriptiveMetadata</td>
* <td>org.roda.core.data.v2.ip.DescriptiveMetadata | aipId | representationId |
* descriptiveMetadataId</td>
* </tr>
* <tr>
* <td>LogEntry</td>
* <td>org.roda.core.data.v2.log.LogEntry|logId</td>
* </tr>
* <tr>
* <td>User</td>
* <td>org.roda.core.data.v2.user.User|name</td>
* </tr>
* <tr>
* <td>Group</td>
* <td>org.roda.core.data.v2.user.Group|name</td>
* </tr>
* </table>
*
*
* @author Hélder Silva <hsilva@keep.pt>
*
*/
public final class LiteRODAObjectFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(LiteRODAObjectFactory.class);
public static final String SEPARATOR = "|";
public static final String SEPARATOR_REGEX = "\\|";
public static final String SEPARATOR_URL_ENCODED = "%7C";
private LiteRODAObjectFactory() {
// do nothing
}
public static <T extends IsRODAObject> Optional<LiteRODAObject> get(Class<T> objectClass, List<String> ids) {
return get(objectClass, ids, true);
}
public static <T extends IsRODAObject> Optional<LiteRODAObject> get(Class<T> objectClass, String... ids) {
return get(objectClass, Arrays.asList(ids), true);
}
public static <T extends IsRODAObject> Optional<LiteRODAObject> get(T object) {
Optional<LiteRODAObject> ret = Optional.empty();
if (object instanceof AIP || object instanceof IndexedAIP || object instanceof DIP || object instanceof IndexedDIP
|| object instanceof Format || object instanceof Job || object instanceof Notification || object instanceof Risk
|| object instanceof IndexedRisk || object instanceof RiskIncidence || object instanceof LogEntry) {
ret = get(ModelUtils.giveRespectiveModelClass(object.getClass()), Arrays.asList(object.getId()), false);
} else if (object instanceof DescriptiveMetadata) {
ret = getDescriptiveMetadata(object);
} else if (object instanceof PreservationMetadata) {
ret = getPreservationMetadata(object);
} else if (object instanceof DIPFile) {
ret = getDIPFile(object);
} else if (object instanceof File) {
ret = getFile(object);
} else if (object instanceof IndexedFile) {
ret = getFileFromIndex(object);
} else if (object instanceof TransferredResource) {
TransferredResource o = (TransferredResource) object;
ret = get(TransferredResource.class, Arrays.asList(o.getFullPath()), false);
} else if (object instanceof Report || object instanceof IndexedReport) {
Report o = (Report) object;
ret = get(Report.class, Arrays.asList(o.getJobId(), o.getId()), false);
} else if (object instanceof Representation || object instanceof IndexedRepresentation) {
Representation o = (Representation) object;
ret = get(Representation.class, Arrays.asList(o.getAipId(), o.getId()), false);
} else if (object instanceof User) {
User o = (User) object;
ret = get(User.class, Arrays.asList(o.getName()), false);
} else if (object instanceof Group) {
Group o = (Group) object;
ret = get(Group.class, Arrays.asList(o.getName()), false);
}
if (!ret.isPresent()) {
LOGGER.error("Unable to create {} from object with class '{}' & id '{}'", LiteRODAObject.class.getSimpleName(),
object.getClass().getName(), object.getId());
}
return ret;
}
public static <T extends IsRODAObject> Optional<LiteRODAObject> get(Class<T> objectClass, List<String> ids,
boolean logIfReturningEmpty) {
Optional<LiteRODAObject> ret = Optional.empty();
if (objectClass == AIP.class || objectClass == IndexedAIP.class || objectClass == DIP.class
|| objectClass == Format.class || objectClass == Job.class || objectClass == Notification.class
|| objectClass == Risk.class || objectClass == RiskIncidence.class || objectClass == User.class
|| objectClass == Group.class || objectClass == LogEntry.class) {
ret = create(objectClass, 1, ids);
} else if (objectClass == DescriptiveMetadata.class) {
if (ids.size() == 2 || ids.size() == 3) {
ret = create(objectClass, ids.size(), ids);
}
} else if (objectClass == DIPFile.class) {
if (ids.size() >= 2) {
ret = create(objectClass, ids.size(), ids);
}
} else if (objectClass == File.class) {
if (ids.size() >= 3) {
ret = create(objectClass, ids.size(), ids);
}
} else if (objectClass == Report.class || objectClass == Representation.class) {
ret = create(objectClass, 2, ids);
} else if (objectClass == TransferredResource.class || objectClass == PreservationMetadata.class) {
ret = create(objectClass, ids.size(), ids);
}
if (logIfReturningEmpty && !ret.isPresent()) {
LOGGER.error("Unable to create {} from objectClass '{}' with ids '{}'", LiteRODAObject.class.getSimpleName(),
objectClass.getName(), ids);
}
return ret;
}
private static <T extends IsRODAObject> Optional<LiteRODAObject> getDescriptiveMetadata(T object) {
Optional<LiteRODAObject> ret;
DescriptiveMetadata o = (DescriptiveMetadata) object;
if (o.getRepresentationId() == null) {
ret = get(DescriptiveMetadata.class, Arrays.asList(o.getAipId(), o.getId()), false);
} else {
ret = get(DescriptiveMetadata.class, Arrays.asList(o.getAipId(), o.getRepresentationId(), o.getId()), false);
}
return ret;
}
private static <T extends IsRODAObject> Optional<LiteRODAObject> getPreservationMetadata(T object) {
Optional<LiteRODAObject> ret;
PreservationMetadata o = (PreservationMetadata) object;
if (o.getAipId() == null) {
ret = get(PreservationMetadata.class, Arrays.asList(o.getId()), false);
} else if (o.getRepresentationId() == null) {
ret = get(PreservationMetadata.class, Arrays.asList(o.getAipId(), o.getId()), false);
} else if (o.getFileId() == null) {
ret = get(PreservationMetadata.class, Arrays.asList(o.getAipId(), o.getRepresentationId(), o.getId()), false);
} else {
List<String> list = new ArrayList<>();
list.add(o.getAipId());
list.add(o.getRepresentationId());
list.addAll(o.getFileDirectoryPath());
list.add(o.getFileId());
list.add(o.getId());
ret = get(DIPFile.class, list, false);
}
return ret;
}
private static <T extends IsRODAObject> Optional<LiteRODAObject> getDIPFile(T object) {
DIPFile o = (DIPFile) object;
List<String> list = new ArrayList<>();
list.add(o.getDipId());
list.addAll(o.getPath());
list.add(o.getId());
return get(DIPFile.class, list, false);
}
private static <T extends IsRODAObject> Optional<LiteRODAObject> getFile(T object) {
File o = (File) object;
List<String> list = new ArrayList<>();
list.add(o.getAipId());
list.add(o.getRepresentationId());
list.addAll(o.getPath());
list.add(o.getId());
return get(File.class, list, false);
}
private static <T extends IsRODAObject> Optional<LiteRODAObject> getFileFromIndex(T object) {
IndexedFile o = (IndexedFile) object;
List<String> list = new ArrayList<>();
list.add(o.getAipId());
list.add(o.getRepresentationId());
list.addAll(o.getPath());
list.add(o.getId());
return get(File.class, list, false);
}
public static <T extends IsRODAObject> OptionalWithCause<T> get(ModelService model, LiteRODAObject liteRODAObject) {
try {
T ret = null;
String[] split = liteRODAObject.getInfo().split(SEPARATOR_REGEX);
if (split.length >= 2) {
String clazz = split[0];
String firstId = decodeId(split[1]);
if (AIP.class.getName().equals(clazz) || IndexedAIP.class.getName().equals(clazz)) {
ret = (T) model.retrieveAIP(firstId);
} else if (DescriptiveMetadata.class.getName().equals(clazz)) {
ret = getDescriptiveMetadata(model, split);
} else if (DIP.class.getName().equals(clazz) || IndexedDIP.class.getName().equals(clazz)) {
ret = (T) model.retrieveDIP(firstId);
} else if (DIPFile.class.getName().equals(clazz)) {
ret = getDIPFile(model, split);
} else if (File.class.getName().equals(clazz) || IndexedFile.class.getName().equals(clazz)) {
ret = getFile(model, split);
} else if (Format.class.getName().equals(clazz)) {
ret = (T) model.retrieveFormat(firstId);
} else if (Job.class.getName().equals(clazz)) {
ret = (T) model.retrieveJob(firstId);
} else if (LogEntry.class.getName().equals(clazz)) {
// INFO 20161229 nvieira It is too complex to use model/storage and
// using index creates a circular dependency
} else if (Notification.class.getName().equals(clazz)) {
ret = (T) model.retrieveNotification(firstId);
} else if (PreservationMetadata.class.getName().equals(clazz)) {
ret = getPreservationMetadata(model, split);
} else if (Report.class.getName().equals(clazz) || IndexedReport.class.getName().equals(clazz)) {
if (split.length == 3) {
ret = (T) model.retrieveJobReport(firstId, decodeId(split[2]), false);
}
} else if (Risk.class.getName().equals(clazz) || IndexedRisk.class.getName().equals(clazz)) {
ret = (T) model.retrieveRisk(firstId);
} else if (RiskIncidence.class.getName().equals(clazz)) {
ret = (T) model.retrieveRiskIncidence(firstId);
} else if (Representation.class.getName().equals(clazz)
|| IndexedRepresentation.class.getName().equals(clazz)) {
if (split.length == 3) {
ret = (T) model.retrieveRepresentation(firstId, decodeId(split[2]));
}
} else if (TransferredResource.class.getName().equals(clazz)) {
ret = (T) model.retrieveTransferredResource(firstId);
} else if (User.class.getName().equals(clazz)) {
ret = (T) model.retrieveUser(firstId);
} else if (Group.class.getName().equals(clazz)) {
ret = (T) model.retrieveGroup(firstId);
}
}
return OptionalWithCause.of(ret);
} catch (RequestNotValidException | NotFoundException | GenericException | AuthorizationDeniedException e) {
LOGGER.error("Unable to create object from {}", liteRODAObject, e);
return OptionalWithCause.empty(e);
}
}
private static <T extends IsRODAObject> T getDescriptiveMetadata(ModelService model, String[] split)
throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException {
T ret = null;
if (split.length == 3) {
ret = (T) model.retrieveDescriptiveMetadata(decodeId(split[1]), decodeId(split[2]));
} else if (split.length == 4) {
ret = (T) model.retrieveDescriptiveMetadata(decodeId(split[1]), decodeId(split[2]), decodeId(split[3]));
}
return ret;
}
private static <T extends IsRODAObject> T getPreservationMetadata(ModelService model, String[] split)
throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException {
T ret = null;
int size = split.length;
PreservationMetadataType type = IdUtils.getPreservationTypeFromId(decodeId(split[size - 1]));
if (split.length == 2) {
ret = (T) model.retrievePreservationMetadata(null, null, null, null, type);
} else if (split.length == 3) {
ret = (T) model.retrievePreservationMetadata(decodeId(split[1]), null, null, null, type);
} else if (split.length == 4) {
ret = (T) model.retrievePreservationMetadata(decodeId(split[1]), decodeId(split[2]), null, null, type);
} else if (split.length > 4) {
List<String> directoryPath = new ArrayList<>();
String fileId = null;
for (int i = 2; i < split.length - 1; i++) {
if (i + 1 == split.length) {
fileId = decodeId(split[i]);
} else {
directoryPath.add(decodeId(split[i]));
}
}
ret = (T) model.retrievePreservationMetadata(decodeId(split[1]), decodeId(split[2]), directoryPath, fileId, type);
}
return ret;
}
private static <T extends IsRODAObject> T getDIPFile(ModelService model, String[] split)
throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException {
T ret = null;
if (split.length >= 3) {
List<String> directoryPath = new ArrayList<>();
String fileId = null;
for (int i = 2; i < split.length; i++) {
if (i + 1 == split.length) {
fileId = decodeId(split[i]);
} else {
directoryPath.add(decodeId(split[i]));
}
}
ret = (T) model.retrieveDIPFile(decodeId(split[1]), directoryPath, fileId);
}
return ret;
}
private static <T extends IsRODAObject> T getFile(ModelService model, String[] split)
throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException {
T ret = null;
if (split.length >= 4) {
List<String> directoryPath = new ArrayList<>();
String fileId = null;
for (int i = 3; i < split.length; i++) {
if (i + 1 == split.length) {
fileId = decodeId(split[i]);
} else {
directoryPath.add(decodeId(split[i]));
}
}
ret = (T) model.retrieveFile(decodeId(split[1]), decodeId(split[2]), directoryPath, fileId);
}
return ret;
}
private static <T extends IsRODAObject> Optional<LiteRODAObject> create(Class<T> objectClass, int numberOfIdsMustHave,
List<String> ids) {
LiteRODAObject ret = null;
if (numberOfIdsMustHave == ids.size()) {
StringBuilder sb = new StringBuilder();
sb.append(objectClass.getName());
for (String id : ids) {
sb.append(SEPARATOR);
try {
sb.append(encodeId(id));
} catch (GenericException e) {
// do nothing
}
}
ret = new LiteRODAObject(sb.toString());
}
return Optional.ofNullable(ret);
}
private static String encodeId(String id) throws GenericException {
if (id != null) {
return id.replaceAll(SEPARATOR_REGEX, SEPARATOR_URL_ENCODED);
} else {
throw new GenericException("Was trying to encode an 'id' but it is NULL");
}
}
private static String decodeId(String id) throws GenericException {
if (id != null) {
return id.replaceAll(SEPARATOR_URL_ENCODED, SEPARATOR);
} else {
throw new GenericException("Was trying to decode an 'id' but it is NULL");
}
}
public static <T extends IsRODAObject> List<LiteRODAObject> transformIntoLite(ModelService model,
List<T> modelObjects) {
return modelObjects.stream().map(o -> model.retrieveLiteFromObject(o)).filter(o -> o.isPresent()).map(o -> o.get())
.collect(Collectors.toList());
}
public static <T extends IsRODAObject> List<LiteOptionalWithCause> transformIntoLiteWithCause(ModelService model,
List<T> modelObjects) {
return modelObjects.stream().map(o -> model.retrieveLiteFromObject(o)).filter(o -> o.isPresent())
.map(o -> LiteOptionalWithCause.of(o.get())).collect(Collectors.toList());
}
public static <T extends IsRODAObject> CloseableIterable<OptionalWithCause<LiteRODAObject>> transformIntoLite(
final CloseableIterable<OptionalWithCause<T>> list) {
CloseableIterable<OptionalWithCause<LiteRODAObject>> it;
final Iterator<OptionalWithCause<T>> iterator = list.iterator();
it = new CloseableIterable<OptionalWithCause<LiteRODAObject>>() {
@Override
public Iterator<OptionalWithCause<LiteRODAObject>> iterator() {
return new Iterator<OptionalWithCause<LiteRODAObject>>() {
@Override
public boolean hasNext() {
if (iterator == null) {
return true;
}
return iterator.hasNext();
}
@Override
public OptionalWithCause<LiteRODAObject> next() {
OptionalWithCause<T> next = iterator.next();
if (next.isPresent()) {
return OptionalWithCause.of(get(next.get()));
} else {
return OptionalWithCause.empty(next.getCause());
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public void close() throws IOException {
list.close();
}
};
return it;
}
public static <T extends IsRODAObject> CloseableIterable<OptionalWithCause<T>> transformFromLite(ModelService model,
final CloseableIterable<OptionalWithCause<LiteRODAObject>> list) {
CloseableIterable<OptionalWithCause<T>> it;
final Iterator<OptionalWithCause<LiteRODAObject>> iterator = list.iterator();
it = new CloseableIterable<OptionalWithCause<T>>() {
@Override
public Iterator<OptionalWithCause<T>> iterator() {
return new Iterator<OptionalWithCause<T>>() {
@Override
public boolean hasNext() {
if (iterator == null) {
return true;
}
return iterator.hasNext();
}
@Override
public OptionalWithCause<T> next() {
OptionalWithCause<LiteRODAObject> next = iterator.next();
if (next.isPresent()) {
return get(model, next.get());
} else {
return OptionalWithCause.empty(next.getCause());
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public void close() throws IOException {
list.close();
}
};
return it;
}
}