/*
* Copyright 2012, CMM, University of Queensland.
*
* This file is part of Paul.
*
* Paul is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Paul is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Paul. If not, see <http://www.gnu.org/licenses/>.
*/
package au.edu.uq.cmm.paul.queue;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.TypedQuery;
import javax.servlet.http.HttpServletResponse;
import org.apache.abdera.factory.Factory;
import org.apache.abdera.i18n.iri.IRI;
import org.apache.abdera.model.Content;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;
import org.apache.abdera.model.Link;
import org.apache.abdera.model.Person;
import org.apache.abdera.protocol.server.RequestContext;
import org.apache.abdera.protocol.server.context.ResponseContextException;
import org.apache.abdera.protocol.server.impl.AbstractEntityCollectionAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import au.edu.uq.cmm.eccles.FacilitySession;
import au.edu.uq.cmm.paul.PaulConfiguration;
import au.edu.uq.cmm.paul.PaulControl;
import au.edu.uq.cmm.paul.PaulException;
import au.edu.uq.cmm.paul.grabber.DatafileMetadata;
import au.edu.uq.cmm.paul.grabber.DatasetMetadata;
import au.edu.uq.cmm.paul.grabber.HashUtils;
/**
* This class is an Abdera feed adapter that maps the data grabber's output queue as
* an atom feed. Note that we override some of the superclasses protected methods
* in order to implement paging and to add categories to the feed entries.
*
* @author scrawley
*/
public class QueueFeedAdapter extends AbstractEntityCollectionAdapter<DatasetMetadata> {
private static final Logger LOG = LoggerFactory.getLogger(QueueFeedAdapter.class);
private static final String ID_PREFIX = "urn:uuid:";
private EntityManagerFactory entityManagerFactory;
private PaulConfiguration config;
private boolean holdDatasetsWithNoUser;
private PaulControl control;
public QueueFeedAdapter(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
config = PaulConfiguration.load(entityManagerFactory);
control = PaulControl.load(entityManagerFactory);
holdDatasetsWithNoUser = config.isHoldDatasetsWithNoUser();
}
@Override
public String getTitle(RequestContext request) {
return config.getFeedTitle();
}
@Override
public void deleteEntry(String resourceName, RequestContext request)
throws ResponseContextException {
throw new ResponseContextException(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
@Override
public Object getContent(DatasetMetadata record, RequestContext request)
throws ResponseContextException {
return "dataset for " + record.getSourceFilePathnameBase() +
", capture timestamp = " + record.getCaptureTimestamp() +
", dataset uuid = " + record.getRecordUuid() +
", session uuid = " + record.getSessionUuid();
}
@Override
public Iterable<DatasetMetadata> getEntries(RequestContext request)
throws ResponseContextException {
if (!control.isAtomFeedEnabled()) {
throw new ResponseContextException(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
EntityManager entityManager = entityManagerFactory.createEntityManager();
try {
TypedQuery<DatasetMetadata> query;
Long id;
try {
String from = request.getParameter("from");
id = from == null ? null : Long.valueOf(from);
} catch (NumberFormatException ex) {
throw new ResponseContextException(HttpServletResponse.SC_BAD_REQUEST);
}
if (id != null) {
LOG.debug("Fetching from id " + id);
query = entityManager.createQuery(
"from DatasetMetadata m where m.id <= :id " +
(holdDatasetsWithNoUser ? "and m.userName is not null " : "") +
"order by m.updateTimestamp desc, m.id desc",
DatasetMetadata.class).setParameter("id", id);
} else {
LOG.debug("Fetching from start of queue");
query = entityManager.createQuery(
"from DatasetMetadata m " +
(holdDatasetsWithNoUser ? "where m.userName is not null " : "") +
"order by m.updateTimestamp desc, m.id desc",
DatasetMetadata.class);
}
query.setMaxResults(config.getFeedPageSize() + 1);
List<DatasetMetadata> res = new ArrayList<DatasetMetadata>(query.getResultList());
LOG.debug("Max page size " + config.getFeedPageSize() +
", fetched " + res.size());
return res;
} finally {
entityManager.close();
}
}
@Override
public DatasetMetadata getEntry(String resourceName, RequestContext request)
throws ResponseContextException {
if (!control.isAtomFeedEnabled()) {
throw new ResponseContextException(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
String[] parts = resourceName.split("-");
EntityManager entityManager = entityManagerFactory.createEntityManager();
try {
DatasetMetadata record =
entityManager.createQuery("from DatasetMetadata a where a.id = :id",
DatasetMetadata.class).setParameter("id", parts[0]).getSingleResult();
if (record == null) {
throw new ResponseContextException(HttpServletResponse.SC_NOT_FOUND);
} else {
return record;
}
} finally {
entityManager.close();
}
}
@Override
public String getId(DatasetMetadata record) throws ResponseContextException {
return ID_PREFIX + record.getRecordUuid();
}
@Override
public String getName(DatasetMetadata record) throws ResponseContextException {
return record.getId() + "-" + record.getRecordUuid();
}
@Override
public String getTitle(DatasetMetadata record) throws ResponseContextException {
return record.getFacilityFilePathnameBase();
}
@Override
public Date getUpdated(DatasetMetadata record) throws ResponseContextException {
return record.getCaptureTimestamp();
}
@Override
public DatasetMetadata postEntry(String title, IRI id, String summary, Date updated,
List<Person> authors, Content content, RequestContext rc)
throws ResponseContextException {
throw new ResponseContextException(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
@Override
public void putEntry(DatasetMetadata record, String title, Date updated,
List<Person> authors, String summary, Content content, RequestContext rc)
throws ResponseContextException {
throw new ResponseContextException(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
@Override
public String getAuthor(RequestContext rc)
throws ResponseContextException {
return config.getFeedAuthor();
}
@Override
public List<Person> getAuthors(DatasetMetadata record, RequestContext request)
throws ResponseContextException {
Person author = request.getAbdera().getFactory().newAuthor();
author.setName(record.getUserName());
if (record.getEmailAddress() != null) {
author.setEmail(record.getEmailAddress());
}
return Arrays.asList(author);
}
@Override
protected String addEntryDetails(RequestContext request, Entry entry,
IRI feedIri, DatasetMetadata record)
throws ResponseContextException {
String res = super.addEntryDetails(request, entry, feedIri, record);
for (DatafileMetadata datafile : record.getDatafiles()) {
Link link = entry.addLink(config.getBaseFileUrl() +
new File(datafile.getCapturedFilePathname()).getName(),
"enclosure", datafile.getMimeType(),
new File(datafile.getSourceFilePathname()).getName(),
"en", datafile.getFileSize());
if (datafile.getDatafileHash() != null) {
link.setAttributeValue("hash", "sha-512:" + datafile.getDatafileHash());
}
}
File file = new File(record.getMetadataFilePathname());
if (file.exists()) {
Link link = entry.addLink(config.getBaseFileUrl() +
file.getName(),
"enclosure");
// We can't use the admin file's internal checksum because that excludes the
// checksum itself ... and therefore won't be the same as what the feed
// consumer would calculate from the file itself.
try {
link.setAttributeValue("hash", "sha-512:" + HashUtils.fileHash(file));
} catch (IOException ex) {
throw new PaulException("IO error while calculating admin file hash", ex);
}
link.setLength(file.length());
link.setMimeType("application/json");
}
return res;
}
@Override
public String getId(RequestContext rc) {
return config.getFeedId();
}
/**
* Create the base feed for the requested collection. This override allows
* us to add the author email and so forth.
*/
@Override
protected Feed createFeedBase(RequestContext request)
throws ResponseContextException {
Factory factory = request.getAbdera().getFactory();
Feed feed = factory.newFeed();
feed.setId(getId(request));
feed.setTitle(getTitle(request));
feed.addLink(config.getFeedUrl(), "self");
Person author = factory.newAuthor();
author.setName(getAuthor(request));
String email = config.getFeedAuthorEmail();
if (email != null && !email.isEmpty()) {
author.setEmail(email);
}
feed.addAuthor(author);
feed.setUpdated(new Date());
return feed;
}
/**
* Adds the selected entries to the Feed document. It also sets
* the feed's atom:updated element to the current date and time,
* and adds a link to the next "page" of the feed.
*/
@Override
protected void addFeedDetails(Feed feed, RequestContext request)
throws ResponseContextException {
feed.setUpdated(new Date());
Iterable<DatasetMetadata> entries = getEntries(request);
if (entries != null) {
int count = 0;
for (DatasetMetadata record : entries) {
LOG.debug("count = " + count + ", entry id = " + record.getId());
if (++count > config.getFeedPageSize()) {
String nextPageUrl = config.getFeedUrl() +
"?from=" + record.getId();
LOG.debug("Adding 'next' link - " + nextPageUrl);
feed.addLink(nextPageUrl, "next");
break;
}
Entry entry = feed.addEntry();
IRI feedIri = new IRI(getFeedIriForEntry(record, request));
addEntryDetails(request, entry, feedIri, record);
if (isMediaEntry(record)) {
addMediaContent(feedIri, entry, record, request);
} else {
addContent(entry, record, request);
}
String operator = record.getOperatorName() == null ?
record.getUserName() : record.getOperatorName();
if (!operator.equals(FacilitySession.UNKNOWN)) {
String sessionTitle = "Session of " + operator + "/" + record.getAccountName() +
" started on " + record.getSessionStartTimestamp();
entry.addCategory(
"http://mytardis.org/schemas/atom-import#experiment-ExperimentID",
record.getSessionUuid(), "experiment");
entry.addCategory(
"http://mytardis.org/schemas/atom-import#experiment-ExperimentTitle",
sessionTitle, "experiment title");
}
}
}
}
}