package eu.europeana.cloud.service.mcs.persistent; import eu.europeana.cloud.common.model.*; import eu.europeana.cloud.common.response.RepresentationRevisionResponse; import eu.europeana.cloud.common.utils.FileUtils; import eu.europeana.cloud.common.utils.RevisionUtils; import eu.europeana.cloud.service.mcs.RecordService; import eu.europeana.cloud.service.mcs.UISClientHandler; import eu.europeana.cloud.service.mcs.exception.*; import eu.europeana.cloud.service.mcs.persistent.cassandra.CassandraDataSetDAO; import eu.europeana.cloud.service.mcs.persistent.cassandra.CassandraRecordDAO; import eu.europeana.cloud.service.mcs.persistent.exception.SystemException; import eu.europeana.cloud.service.mcs.persistent.swift.PutResult; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.*; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; /** * Implementation of record service using Cassandra as storage. */ @Service public class CassandraRecordService implements RecordService { private static final Logger LOGGER = LoggerFactory.getLogger(CassandraRecordService.class); @Autowired private CassandraRecordDAO recordDAO; @Autowired private CassandraDataSetService dataSetService; @Autowired private CassandraDataSetDAO dataSetDAO; @Autowired private DynamicContentDAO contentDAO; @Autowired private SolrRepresentationIndexer representationIndexer; @Autowired private UISClientHandler uis; /** * @inheritDoc */ @Override public Record getRecord(String cloudId) throws RecordNotExistsException { Record record = null; if (uis.existsCloudId(cloudId)) { record = recordDAO.getRecord(cloudId); if (record.getRepresentations().isEmpty()) { throw new RecordNotExistsException(cloudId); } } else { throw new RecordNotExistsException(cloudId); } return record; } /** * @inheritDoc */ @Override public void deleteRecord(String cloudId) throws RecordNotExistsException, RepresentationNotExistsException { if (uis.existsCloudId(cloudId)) { List<Representation> allRecordRepresentationsInAllVersions = recordDAO.listRepresentationVersions(cloudId); if (allRecordRepresentationsInAllVersions.isEmpty()) { throw new RepresentationNotExistsException(String.format( "No representation found for given cloudId %s", cloudId)); } sortByProviderId(allRecordRepresentationsInAllVersions); String dPId = null; for (Representation repVersion : allRecordRepresentationsInAllVersions) { if (!(repVersion.getDataProvider()).equalsIgnoreCase(dPId)) { dPId = repVersion.getDataProvider(); representationIndexer.removeRecordRepresentations(cloudId, uis.getProvider(dPId).getPartitionKey()); } for (File f : repVersion.getFiles()) { try { contentDAO.deleteContent(FileUtils.generateKeyForFile(cloudId, repVersion.getRepresentationName(), repVersion.getVersion(), f.getFileName()),f.getFileStorage()); } catch (FileNotExistsException ex) { LOGGER.warn( "File {} was found in representation {}-{}-{} but no content of such file was found", f.getFileName(), cloudId, repVersion.getRepresentationName(), repVersion.getVersion()); } } } recordDAO.deleteRecord(cloudId); } else { throw new RecordNotExistsException(cloudId); } } /** * @inheritDoc */ @Override public void deleteRepresentation(String globalId, String schema) throws RepresentationNotExistsException { List<Representation> listRepresentations = recordDAO.listRepresentationVersions(globalId, schema); sortByProviderId(listRepresentations); String dPId = null; for (Representation rep : listRepresentations) { if (!(rep.getDataProvider()).equalsIgnoreCase(dPId)) { // send only one message per DataProvider dPId = rep.getDataProvider(); representationIndexer.removeRepresentation(globalId, schema, uis.getProvider(dPId).getPartitionKey()); } for (File f : rep.getFiles()) { try { contentDAO.deleteContent(FileUtils.generateKeyForFile(globalId, schema, rep.getVersion(), f .getFileName()),f.getFileStorage()); } catch (FileNotExistsException ex) { LOGGER.warn("File {} was found in representation {}-{}-{} but no content of such file was found", f.getFileName(), globalId, rep.getRepresentationName(), rep.getVersion()); } } for (Revision r : rep.getRevisions()) { recordDAO.deleteRepresentationRevision(globalId, schema, rep.getVersion(), r.getRevisionProviderId(), r.getRevisionName(), r.getCreationTimeStamp()); } Collection<CompoundDataSetId> compoundDataSetIds = dataSetDAO.getDataSetAssignmentsByRepresentationVersion(globalId, schema, rep.getVersion()); if (!compoundDataSetIds.isEmpty()) { for (CompoundDataSetId compoundDataSetId : compoundDataSetIds) { try { dataSetService.removeAssignment(compoundDataSetId.getDataSetProviderId(), compoundDataSetId.getDataSetId(), globalId, schema, rep.getVersion()); } catch (DataSetNotExistsException e) { } } } } recordDAO.deleteRepresentation(globalId, schema); } /** * @inheritDoc */ @Override public Representation createRepresentation(String cloudId, String representationName, String providerId) throws ProviderNotExistsException, RecordNotExistsException { Date now = new Date(); DataProvider dataProvider; // check if data provider exists if ((dataProvider = uis.getProvider(providerId)) == null) { throw new ProviderNotExistsException(String.format("Provider %s does not exist.", providerId)); } if (uis.existsCloudId(cloudId)) { Representation rep = recordDAO.createRepresentation(cloudId, representationName, providerId, now); representationIndexer.insertRepresentation(rep, dataProvider.getPartitionKey()); return rep; } else { throw new RecordNotExistsException(cloudId); } } /** * @inheritDoc */ @Override public Representation getRepresentation(String globalId, String schema) throws RepresentationNotExistsException { Representation rep = recordDAO.getLatestPersistentRepresentation(globalId, schema); if (rep == null) { throw new RepresentationNotExistsException(); } else { return rep; } } /** * @inheritDoc */ @Override public Representation getRepresentation(String globalId, String schema, String version) throws RepresentationNotExistsException { Representation rep = recordDAO.getRepresentation(globalId, schema, version); if (rep == null) { throw new RepresentationNotExistsException(); } else { return rep; } } /** * @inheritDoc */ @Override public void deleteRepresentation(String globalId, String schema, String version) throws RepresentationNotExistsException, CannotModifyPersistentRepresentationException { Representation rep = recordDAO.getRepresentation(globalId, schema, version); if (rep == null) { throw new RepresentationNotExistsException(); } if (rep.isPersistent()) { throw new CannotModifyPersistentRepresentationException(); } representationIndexer.removeRepresentationVersion(version, uis.getProvider(rep.getDataProvider()) .getPartitionKey()); for (File f : rep.getFiles()) { try { contentDAO.deleteContent(FileUtils.generateKeyForFile(globalId, schema, version, f.getFileName()),f.getFileStorage()); } catch (FileNotExistsException ex) { LOGGER.warn("File {} was found in representation {}-{}-{} but no content of such file was found", f.getFileName(), globalId, rep.getRepresentationName(), rep.getVersion()); } } for (Revision r : rep.getRevisions()) { recordDAO.deleteRepresentationRevision(globalId, schema, version, r.getRevisionProviderId(), r.getRevisionName(), r.getCreationTimeStamp()); } Collection<CompoundDataSetId> compoundDataSetIds = dataSetDAO.getDataSetAssignmentsByRepresentationVersion(globalId, schema, version); if (!compoundDataSetIds.isEmpty()) { for (CompoundDataSetId compoundDataSetId : compoundDataSetIds) { try { dataSetService.removeAssignment(compoundDataSetId.getDataSetProviderId(), compoundDataSetId.getDataSetId(), globalId, schema, version); } catch (DataSetNotExistsException e) { } } } recordDAO.deleteRepresentation(globalId, schema, version); } /** * @inheritDoc */ @Override public Representation persistRepresentation(String globalId, String schema, String version) throws RepresentationNotExistsException, CannotModifyPersistentRepresentationException, CannotPersistEmptyRepresentationException { Date now = new Date(); Representation rep = recordDAO.getRepresentation(globalId, schema, version); if (rep == null) { throw new RepresentationNotExistsException(); } else if (rep.isPersistent()) { throw new CannotModifyPersistentRepresentationException(); } List<File> recordFiles = recordDAO.getFilesForRepresentation(globalId, schema, version); if (recordFiles == null) { throw new RepresentationNotExistsException(); } else if (recordFiles.isEmpty()) { throw new CannotPersistEmptyRepresentationException(); } recordDAO.persistRepresentation(globalId, schema, version, now); rep.setPersistent(true); rep.setCreationDate(now); representationIndexer.insertRepresentation(rep, uis.getProvider(rep.getDataProvider()).getPartitionKey()); return rep; } /** * @inheritDoc */ @Override public List<Representation> listRepresentationVersions(String globalId, String schema) throws RepresentationNotExistsException { return recordDAO.listRepresentationVersions(globalId, schema); } /** * @inheritDoc */ @Override public boolean putContent(String globalId, String schema, String version, File file, InputStream content) throws CannotModifyPersistentRepresentationException, RepresentationNotExistsException { DateTime now = new DateTime(); Representation representation = getRepresentation(globalId, schema, version); if (representation.isPersistent()) { throw new CannotModifyPersistentRepresentationException(); } boolean isCreate = true; // if it is create file operation or update // content for (File f : representation.getFiles()) { if (f.getFileName().equals(file.getFileName())) { isCreate = false; break; } } String keyForFile = FileUtils.generateKeyForFile(globalId, schema, version, file.getFileName()); PutResult result; try { result = contentDAO.putContent(keyForFile, content,file.getFileStorage()); } catch (IOException ex) { throw new SystemException(ex); } file.setMd5(result.getMd5()); DateTimeFormatter fmt = ISODateTimeFormat.dateTime(); file.setDate(fmt.print(now)); file.setContentLength(result.getContentLength()); recordDAO.addOrReplaceFileInRepresentation(globalId, schema, version, file); for (Revision revision : representation.getRevisions()) { // update information in extra table recordDAO.addOrReplaceFileInRepresentationRevision(globalId, schema, version, revision.getRevisionProviderId(), revision.getRevisionName(), revision.getCreationTimeStamp(), file); } return isCreate; } /** * @inheritDoc */ @Override public void getContent(String globalId, String schema, String version, String fileName, long rangeStart, long rangeEnd, OutputStream os) throws FileNotExistsException, WrongContentRangeException, RepresentationNotExistsException { File file = getFile(globalId, schema, version, fileName); if (rangeStart > file.getContentLength() - 1) { throw new WrongContentRangeException("Start range must be less than file length"); } try { contentDAO.getContent(FileUtils.generateKeyForFile(globalId, schema, version, fileName), rangeStart, rangeEnd, os, file.getFileStorage()); } catch (IOException ex) { throw new SystemException(ex); } } /** * @inheritDoc */ @Override public String getContent(String globalId, String schema, String version, String fileName, OutputStream os) throws FileNotExistsException, RepresentationNotExistsException { Representation rep = getRepresentation(globalId, schema, version); File file = findFileInRepresentation(rep, fileName); try { contentDAO.getContent(FileUtils.generateKeyForFile(globalId, schema, version, fileName), -1, -1, os, file.getFileStorage()); } catch (IOException ex) { throw new SystemException(ex); } return file.getMd5(); } /** * @inheritDoc */ @Override public void deleteContent(String globalId, String schema, String version, String fileName) throws FileNotExistsException, CannotModifyPersistentRepresentationException, RepresentationNotExistsException { Representation representation = getRepresentation(globalId, schema, version); if (representation.isPersistent()) { throw new CannotModifyPersistentRepresentationException(); } recordDAO.removeFileFromRepresentation(globalId, schema, version, fileName); File file = findFileInRepresentation(representation, fileName); contentDAO.deleteContent(FileUtils.generateKeyForFile(globalId, schema, version, fileName),file.getFileStorage()); } /** * @inheritDoc */ @Override public Representation copyRepresentation(String globalId, String schema, String version) throws RepresentationNotExistsException { Date now = new Date(); Representation srcRep = recordDAO.getRepresentation(globalId, schema, version); if (srcRep == null) { throw new RepresentationNotExistsException(); } Representation copiedRep = recordDAO.createRepresentation(globalId, schema, srcRep.getDataProvider(), now); representationIndexer.insertRepresentation(copiedRep, uis.getProvider(srcRep.getDataProvider()) .getPartitionKey()); for (File srcFile : srcRep.getFiles()) { File copiedFile = new File(srcFile); try { contentDAO.copyContent(FileUtils.generateKeyForFile(globalId, schema, version, srcFile.getFileName()), FileUtils.generateKeyForFile(globalId, schema, copiedRep.getVersion(), copiedFile.getFileName()), srcFile.getFileStorage()); } catch (FileNotExistsException ex) { LOGGER.warn("File {} was found in representation {}-{}-{} but no content of such file was found", srcFile.getFileName(), globalId, schema, version); } catch (FileAlreadyExistsException ex) { LOGGER.warn("File already exists in newly created representation?", copiedFile.getFileName(), globalId, schema, copiedRep.getVersion()); } catch (IOException e) { e.printStackTrace(); } recordDAO.addOrReplaceFileInRepresentation(globalId, schema, copiedRep.getVersion(), copiedFile); } // get version after all modifications return recordDAO.getRepresentation(globalId, schema, copiedRep.getVersion()); } /** * @inheritDoc */ @Override public File getFile(String globalId, String schema, String version, String fileName) throws RepresentationNotExistsException, FileNotExistsException { final Representation rep = getRepresentation(globalId, schema, version); return findFileInRepresentation(rep, fileName); } private File findFileInRepresentation(Representation representation, String fileName) throws FileNotExistsException { for (File file : representation.getFiles()) { if (file.getFileName().equals(fileName)) { return file; } } throw new FileNotExistsException(); } private static void sortByProviderId(List<Representation> input) { Collections.sort(input, new Comparator<Representation>() { @Override public int compare(Representation r1, Representation r2) { return r1.getDataProvider().compareToIgnoreCase(r2.getDataProvider()); } }); } /** * @inheritDoc */ @Override public void addRevision(String globalId, String schema, String version, Revision revision) throws RevisionIsNotValidException { recordDAO.addOrReplaceRevisionInRepresentation(globalId, schema, version, revision); } @Override public RepresentationRevisionResponse getRepresentationRevision(String globalId, String schema, String revisionProviderId, String revisionName, Date revisionTimestamp) { return recordDAO.getRepresentationRevision(globalId, schema, revisionProviderId, revisionName, revisionTimestamp); } @Override public void insertRepresentationRevision(String globalId, String schema, String revisionProviderId, String revisionName, String versionId, Date revisionTimestamp) { // add additional association between representation version and revision Representation representation = recordDAO.getRepresentation(globalId, schema, versionId); recordDAO.addRepresentationRevision(globalId, schema, versionId, revisionProviderId, revisionName, revisionTimestamp); for (File file : representation.getFiles()) recordDAO.addOrReplaceFileInRepresentationRevision(globalId, schema, versionId, revisionProviderId, revisionName, revisionTimestamp, file); } /** * @inheritDoc */ @Override public Revision getRevision(String globalId, String schema, String version, String revisionKey) throws RevisionNotExistsException, RepresentationNotExistsException { Representation rep = getRepresentation(globalId, schema, version); for (Revision revision : rep.getRevisions()) { if (revision != null) { if (RevisionUtils.getRevisionKey(revision).equals(revisionKey)) { return revision; } } } throw new RevisionNotExistsException(); } }