package controllers; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.FileOutputStream; import java.net.URLDecoder; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.concurrent.TimeUnit; import com.avaje.ebean.Ebean; import com.avaje.ebean.Query; import com.avaje.ebean.SqlUpdate; import com.fasterxml.jackson.databind.JsonNode; import models.Alert; import models.AssignableArk; import models.BlCollectionSubset; import models.Book; import models.Document; import models.DocumentFilter; import models.FlashMessage; import models.Journal; import models.JournalTitle; import models.Portal; import models.Target; import models.User; import models.WatchedTarget; import play.Logger; import play.data.Form; import play.data.validation.ValidationError; import play.libs.F.Function; import play.libs.F.Function0; import play.libs.F.Promise; import play.libs.Json; import play.libs.ws.WS; import play.libs.ws.WSRequestHolder; import play.libs.ws.WSResponse; import play.libs.XPath; import play.mvc.BodyParser; import play.mvc.Result; import play.mvc.Security; import play.Play; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.tika.io.IOUtils; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import uk.bl.Const; import uk.bl.configurable.BlCollectionSubsetList; import uk.bl.documents.DocumentAnalyser; import views.html.documents.compare; import views.html.documents.edit; import views.html.documents.list; @Security.Authenticated(SecuredController.class) public class Documents extends AbstractController { public static BlCollectionSubsetList blCollectionSubsetList = new BlCollectionSubsetList(); public static Result view(Long id) { return render(id, false); } public static Result edit(Long id) { return render(id, true); } private static Result render(Long id, boolean editable) { Logger.debug("Documents.render()"); Document document = getDocumentFromDB(id); if (document == null) return ok("Document not found: The requested document is no longer available."); if (document.status == Document.Status.SUBMITTED) editable = false; User user = User.findByEmail(request().username()); if (!document.isEditableFor(user)) editable = false; Form<Document> documentForm = Form.form(Document.class).fill(document); setRelatedEntitiesOfView(documentForm, document); return ok(edit.render("Document" + id, document, documentForm, user, editable)); } public static Result continueEdit() { Logger.debug("Documents.continueEdit()"); if (flash("journalTitle") != null) session("journal.journalTitleId", flash("journalTitle")); Form<Document> documentForm = Form.form(Document.class).bind(session()); documentForm.discardErrors(); return ok(edit.render("Document", Document.find.byId(new Long(session("id"))), documentForm, User.findByEmail(request().username()), true)); } public static Result save(Long id) { Logger.debug("Documents.save()"); String journalTitle = getFormParam("journalTitle"); Form<Document> documentForm = Form.form(Document.class).bindFromRequest(); if (journalTitle != null) { session().putAll(documentForm.data()); return redirect(routes.JournalTitles.addJournalTitle( new Long(documentForm.apply("watchedTarget.id").value()), true)); } Logger.debug("Errors: " + documentForm.hasErrors()); for (String key : documentForm.errors().keySet()) { Logger.debug("Key: " + key); for (ValidationError error : documentForm.errors().get(key)) { for (String message : error.messages()) { Logger.debug("Message: " + message); } } } if (documentForm.hasErrors()) { Logger.debug("Show errors in html"); FlashMessage.validationWarning.send(); return status(303, edit.render("Document", Document.find.byId(new Long(documentForm.field("id").value())), documentForm, User.findByEmail(request().username()), true)); } Logger.debug("Glob Errors: " + documentForm.hasGlobalErrors()); Document document = documentForm.get(); document.clearImproperFields(); setRelatedEntitiesOfModel(document, documentForm); // Record that there's been an update: if(document.status == Document.Status.NEW) { document.setStatus(Document.Status.SAVED); } Ebean.update(document); if (document.publicationDate == null) { Ebean.createUpdate(Document.class, "update document SET publication_date=null where id=:id") .setParameter("id", document.id).execute(); } if (!document.isBookOrBookChapter() && document.book.id != null) { Book book = Ebean.find(Book.class, document.book.id); Ebean.delete(book); } if (!document.isJournalArticleOrIssue() && document.journal.id != null) { Journal journal = Ebean.find(Journal.class, document.journal.id); Ebean.delete(journal); } if (document.isBookOrBookChapter()) { document.book.document = document; if (document.book.id == null) { Ebean.save(document.book); } else { Ebean.update(document.book); } } else if (document.isJournalArticleOrIssue()) { document.journal.document = document; document.journal.journalTitle = Ebean.find(JournalTitle.class, document.journal.journalTitleId); if (document.journal.id == null) { Ebean.save(document.journal); } else { Ebean.update(document.journal); } } FlashMessage.updateSuccess.send(); return redirect(routes.Documents.view(document.id)); } private static void getARKs(Document d) { List<AssignableArk> arks = requestNewArks(4); if( arks == null || arks.size() < 4) { return; } Logger.info("Minted four arks. ark[0] = "+arks.get(0).ark); // Add these ARKs to the document: d.ark = arks.get(0).ark; d.mets_d_ark = arks.get(1).ark; d.d_ark = arks.get(2).ark; d.md_ark = arks.get(3).ark; } public static Result submit(final Long id) { // Find the document: Document document = Document.find.byId(id); // Mint ARKs for the document: if( document.hasARKs() == false ){ Documents.getARKs(document); } else { Logger.warn("Re-using existing ARKs, e.g. doc.ark = "+ document.ark); } // Check it worked: if( document.hasARKs() == false ) { FlashMessage error = new FlashMessage(FlashMessage.Type.ERROR, "There was a problem minting ARK identifiers for this SIP!"); error.send(); return redirect(routes.Documents.view(id)); } else { Ebean.save(document); } final String fileName = "_sip_" + id; // Choose the save dir: final File saveDir; if( document.isJournalArticleOrIssue() ) { saveDir = new File(Play.application().configuration().getString("dls.documents.ejournal.sip.dir")); } else { // eBooks have a holding directory for each submission. Create a temporary one until the copy is known to have succeeded. saveDir = new File(Play.application().configuration().getString("dls.documents.ebook.sip.dir"), fileName); saveDir.mkdir(); } if(saveDir.exists()) { // Download it to a local file. String url = routes.DocumentSIPController.sip(id).absoluteURL(request()); Logger.info("Downloading " + url); final Promise<File> filePromise = WS.url(url).get().map( new Function<WSResponse, File>() { @Override public File apply(WSResponse response) throws Throwable { final File file = new File(saveDir, fileName + ".xml"); InputStream responseStream = null; OutputStream fileStream = null; try { IOUtils.copy(responseStream = response.getBodyAsStream(), fileStream = new FileOutputStream(file)); } catch(IOException e) { Logger.error("Exception while downloading file: " + e.getMessage(), e); return null; } finally { try { if(responseStream != null) { responseStream.close(); } } catch(Exception e){ Logger.warn("Failed to close stream on download.", e); } try { if(fileStream != null) { fileStream.close(); } } catch(Exception e) { Logger.warn("Failed to close stream on file.", e); } } return file; } }); // Wait for the file to download, up to thirty seconds: File file = filePromise.get(30, TimeUnit.SECONDS); // Check it's good: if(file != null && file.exists() && file.isFile() && file.length() != 0) { // Make a copy (in case something goes wrong: String copyDir = Play.application().configuration().getString("dls.documents.sip.copy.dir"); if(!StringUtils.isEmpty(copyDir)) { Calendar cal = Calendar.getInstance(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); String dateStr = dateFormat.format(cal.getTime()); String copyFilename = dateStr + "_" + document.type.toLowerCase().replace(' ', '_') + "_sip_" + id + ".xml"; File copyFile = new File(copyDir, copyFilename); try { FileUtils.copyFile(file, copyFile); if(copyFile.length() != file.length()) { throw new IOException("Copy failed - lengths to not match."); } } catch(IOException e) { Logger.error("Exception while copying SIP xml: " + e.getMessage(), e); FlashMessage downloadError = new FlashMessage(FlashMessage.Type.ERROR, "SIP XML could not be copied!"); downloadError.send(); return redirect(routes.Documents.view(id)); } } // And rename, this initiation submission: String newFileName = "sip_" + id; File targetFile = new File(saveDir, newFileName + ".xml"); Logger.info("Renaming "+file.getAbsolutePath()+" to "+targetFile); Boolean renameSuccess = file.renameTo(targetFile); // Rename the holding directory, if an eBook submission if(!document.isJournalArticleOrIssue()) { File targetDir = new File(Play.application().configuration().getString("dls.documents.ebook.sip.dir"), newFileName); Logger.info("Attempt to rename "+saveDir.getAbsolutePath()+" to "+ targetDir); renameSuccess &= saveDir.renameTo(targetDir); } else { Logger.info("Not attemting any further rename of " + newFileName); } if(!renameSuccess){ FlashMessage error = new FlashMessage(FlashMessage.Type.ERROR, "Could not rename the temporary download file."); error.send(); return redirect(routes.Documents.view(id)); } } else { if(file != null && file.exists()) { file.delete(); } FlashMessage downloadError = new FlashMessage(FlashMessage.Type.ERROR, "SIP XML could not be downloaded for submission!"); downloadError.send(); return redirect(routes.Documents.view(id)); } } else { FlashMessage error = new FlashMessage(FlashMessage.Type.ERROR, "XML save directory does not exist."); error.send(); return redirect(routes.Documents.view(id)); } // Record success: document.setStatus(Document.Status.SUBMITTED); Ebean.save(document); // And tell: FlashMessage submitSuccess = new FlashMessage(FlashMessage.Type.SUCCESS, "The document has been submitted."); submitSuccess.send(); return redirect(routes.Documents.view(id)); } public static Result compare(Long id1, Long id2) { Document d1 = Document.find.byId(id1); Document d2 = Document.find.byId(id2); return ok(compare.render(d1, d2, User.findByEmail(request().username()))); } private static List<AssignableArk> requestNewArks( int numArks ) { String piiUrl = Play.application().configuration().getString("pii_url"); WSRequestHolder holder = WS.url(piiUrl).setQueryParameter("arks", Integer.toString(numArks)); Logger.info("Requesting ARKs via POST to "+holder.getUrl()); Promise<List<AssignableArk>> arksPromise = holder.post("").map( new Function<WSResponse, List<AssignableArk>>() { @Override public List<AssignableArk> apply(WSResponse response) { Logger.debug("PII XML-Response: " + response.getBody()); List<AssignableArk> arks = new ArrayList<>(); try { org.w3c.dom.Document xml = response.asXml(); if (xml != null) { NodeList nodes = XPath.selectNodes("/pii/results/arkList/ark", xml); for (int i=0; i < nodes.getLength(); i++) { Node node = nodes.item(i); Logger.debug("node "+node.getNodeName()+" "+node.getTextContent()); arks.add(new AssignableArk(node.getTextContent())); } } } catch (Exception e) { Logger.error("Can't get ARKs from the PII server: " + e.getMessage()); } return arks; } } ); List<AssignableArk> arks = arksPromise.get(5000); return arks; } public static Result ignore(Long id, DocumentFilter documentFilter, int pageNo, String sortBy, String order, String filter, boolean filters) { Document document = Document.find.byId(id); if (documentFilter.status == Document.Status.NEW) document.setStatus(Document.Status.IGNORED); else document.setStatus(Document.Status.NEW); Ebean.save(document); if (filters) return redirect(routes.Documents.list(documentFilter, pageNo, sortBy, order, filter)); else return redirect(routes.Documents.overview(pageNo, sortBy, "asc")); } public static Result ignoreAll(DocumentFilter documentFilter, String filter, boolean filters) { Query<Document> query = Document.query(documentFilter, "title", "asc", filter); Document.Status status; if (documentFilter.status == Document.Status.NEW) status = Document.Status.IGNORED; else status = Document.Status.NEW; for (Document document : query.findList()) { document.setStatus(status); Ebean.save(document); } if (filters) return redirect(routes.Documents.list(documentFilter, 0, "title", "asc", filter)); else return redirect(routes.Documents.overview(0, "title", "asc")); } private static String truncator(String in) { if( in != null && in.length() > 255 ) { Logger.info("Truncating over-long field: "+in); return in.substring(0, 255); } else { return in; } } @BodyParser.Of(value = BodyParser.Json.class, maxLength = 1024 * 1024) public static Result importJson() { Logger.info("documents are imported"); JsonNode json = request().body().asJson(); List<Document> documents = new ArrayList<>(); for (JsonNode objNode : json) { // Parse: try { Document document = parseDocumentJson(objNode); // And save and add if not null: if( document != null) { // Attempt to extract critical data: DocumentAnalyser da = new DocumentAnalyser(); da.extractMetadata(document); // Look for other documents with the same non-empty hash: boolean unique = true; if( StringUtils.isNoneEmpty(document.sha256Hash)) { for (Document doc2 : document.watchedTarget.documents ) { if( document.sha256Hash.equals( doc2.sha256Hash)) { Logger.warn("Already seen this document at "+doc2.documentUrl); unique = false; break; } } } // Record unique documents: if( unique ) { // Allow unset titles: if( document.title == null){ document.title = ""; } // Avoid submitting over-long fields (current 255 char limit) document.title = truncator(document.title); document.author1Ln = truncator(document.author1Ln); Logger.info("Saving document metadata."); try { Ebean.save(document); if( document.book != null ) { Ebean.save(document.book); } } catch( Exception ex ) { Logger.error("Exception when calling Ebean.save: ", ex); Logger.error("Document was: "+document); Logger.error("Document.Book was: "+document.book); throw ex; } // Add to list for post-import checks: documents.add(document); } else { Logger.warn("Dropping document from " + document.documentUrl + " based on SHA-256 hash being identical to an existing document on this target."); } } } catch( Exception ex ) { Logger.error("Problem while importing document.", ex); Logger.error("JSON was: "+objNode.toString()); return badRequest("Problem during import: "+ex); } } if( documents.size() > 0 ) { Promise.promise(new DocumentAnalyser.SimilarityFunction(documents)); return ok("Documents added"); } else { return ok("No new documents added"); } } protected static Document parseDocumentJson(JsonNode objNode) throws Exception { Document document = new Document(); Long targetId = objNode.get("target_id").longValue(); Logger.info("TargetID: "+targetId); Target target = Target.find.byId(targetId); if( target == null ) { throw new Exception("No Target with ID "+targetId); } Logger.info("Target: "+target.title); document.watchedTarget = target.watchedTarget; Logger.info("WatchedTarget: "+document.watchedTarget); if( document.watchedTarget == null ) { throw new Exception("This is not a watched target!"); } document.waybackTimestamp = objNode.get("wayback_timestamp").textValue(); Logger.info("Comparing "+document.watchedTarget.waybackTimestamp+" to "+document.waybackTimestamp); if (document.watchedTarget.waybackTimestamp == null || document.waybackTimestamp.compareTo(document.watchedTarget.waybackTimestamp) > 0) { document.watchedTarget.waybackTimestamp = document.waybackTimestamp; Ebean.save(document.watchedTarget); } document.landingPageUrl = objNode.get("landing_page_url").textValue().replace("'", "%27"); document.documentUrl = objNode.get("document_url").textValue().replace("'", "%27"); document.filename = objNode.get("filename").textValue(); document.size = objNode.get("size").longValue(); document.setStatus(Document.Status.NEW); document.fastSubjects = target.watchedTarget.fastSubjects; if( documentAlreadyKnown(document)) { Logger.warn("This Document is already known to the system."); return null; } else { Logger.debug("attempting to add document " + document); } // // Optional fields // // Title: if(objNode.get("title") != null) { document.title = objNode.get("title").textValue(); } // Publisher: if( objNode.get("publisher") != null) { if( document.book == null) document.book = new Book(document); document.book.publisher = objNode.get("publisher").textValue(); } // Publication Date (in yyyy-MM-dd format): if( objNode.get("publication_date") != null ) { String ymd = objNode.get("publication_date").textValue(); try { document.publicationDate = new SimpleDateFormat("yyyy-MM-dd").parse(ymd); Calendar calendar = Calendar.getInstance(); calendar.setTime(document.publicationDate); document.publicationYear = calendar.get(Calendar.YEAR); } catch( ParseException e ) { throw new Exception("Could not parse publication date (should be yyyy-MM-dd format"+ymd); } } // Authors array processing: if( objNode.get("authors") != null ) { List<String> authors = new ArrayList<String>(); Iterator<JsonNode> it = objNode.get("authors").elements(); while( it.hasNext()) { JsonNode val = it.next(); authors.add(val.textValue()); } if (authors.size() >= 1) { String[] a = authors.get(0).trim().split("\\s+", 2); document.author1Fn = a[0]; document.author1Ln = a[1]; } if (authors.size() >= 2) { String[] a = authors.get(1).trim().split("\\s+", 2); document.author2Fn = a[0]; document.author2Ln = a[1]; } if (authors.size() >= 3) { String[] a = authors.get(2).trim().split("\\s+", 2); document.author3Fn = a[0]; document.author3Ln = a[1]; } } // ISBN if( objNode.get("isbn") != null ) { if( document.book == null) document.book = new Book(document); document.book.isbn = objNode.get("isbn").textValue(); } // DOI if( objNode.get("doi") != null ) { if( document.book == null) document.book = new Book(document); document.doi = objNode.get("doi").textValue(); } return document; } private static boolean documentAlreadyKnown(Document document) { String urlWithoutSchema = document.documentUrl.replaceFirst("^.*://", ""); if (Document.find.where().eq("regexp_replace(document_url,'^.*://','')", urlWithoutSchema).findRowCount() == 0) { return false; } else { return true; } } public static List<Document> filterNew(List<Document> documentList) { List<Document> newDocumentList = new ArrayList<>(); for (Document document : documentList) { if (! documentAlreadyKnown(document)) newDocumentList.add(document); } return newDocumentList; } public static Result export(DocumentFilter documentFilter, String sortBy, String order, String filter) { Query<Document> query = Document.query(documentFilter, sortBy, order, filter); StringBuilder builder = new StringBuilder(); builder.append( "id" + Const.CSV_SEPARATOR + "id_target" + Const.CSV_SEPARATOR + "title" + Const.CSV_SEPARATOR + "landing_page_url" + Const.CSV_SEPARATOR + "document_url" + Const.CSV_SEPARATOR + "wayback_timestamp" + Const.CSV_LINE_END ); for (Document document : query.findList()) { builder.append( document.id + Const.CSV_SEPARATOR + document.watchedTarget.target.id + Const.CSV_SEPARATOR + document.title + Const.CSV_SEPARATOR + document.landingPageUrl + Const.CSV_SEPARATOR + document.documentUrl + Const.CSV_SEPARATOR + document.waybackTimestamp + Const.CSV_LINE_END ); } response().setContentType("text/csv; charset=utf-8"); response().setHeader("Content-Disposition","attachment; filename=\"document-export.csv"); return ok(builder.toString()); } public static void addHashes(Document document) { File file = Play.application().getFile("conf/converter/" + document.id + ".sha256"); try { Scanner scanner = new Scanner(file); document.sha256Hash = scanner.next(); scanner.close(); file.delete(); } catch (Exception e) { Logger.warn("can't read sha256 hash: " + e.getMessage()); } file = Play.application().getFile("conf/converter/" + document.id + ".ctp"); try { Scanner scanner = new Scanner(file); document.ctpHash = scanner.next(); scanner.close(); file.delete(); } catch (Exception e) { Logger.warn("can't read ctp hash: " + e.getMessage()); } Ebean.save(document); } public static void addDuplicateAlert(String ctphFile) { File file = Play.application().getFile("conf/converter/" + ctphFile + ".out"); try { Scanner scanner = new Scanner(file); scanner.useDelimiter("[\r\n]+"); while (scanner.hasNext()) { String line = scanner.next(); String[] parts = line.split("[ :]"); if (parts.length == 6) { int similarity = Integer.parseInt(parts[5].replace("(", "").replace(")", "")); if (similarity >= 98 && similarity < 100) { long docId1 = Long.parseLong(parts[1].split("\\.")[0]); long docId2 = Long.parseLong(parts[4].split("\\.")[0]); Document doc1 = Document.find.byId(docId1); Document doc2 = Document.find.byId(docId2); Alert alert = new Alert(); alert.user = doc1.watchedTarget.target.authorUser; alert.text = "possible duplicate found: " + Alert.link(doc1) + " matches " + Alert.link(doc2) + " with " + similarity + "% " + "(" + Alert.compareLink(doc1, doc2) + ")"; Ebean.save(alert); } } } scanner.close(); file.delete(); } catch (Exception e) { Logger.warn("can't read ctph matches:", e); } } public static Document getDocumentFromDB(long id) { Document document = Ebean.find(Document.class, id); if (document == null) return null; if (document.type == null) document.type = ""; if (document.journal != null) document.journal.journalTitleId = document.journal.journalTitle.id; return document; } public static Map<String, String> getJournalTitles(Form<Document> form) { WatchedTarget watchedTarget = WatchedTarget.find.byId(new Long(form.apply("watchedTarget.id").value())); Map<String, String> titles = new LinkedHashMap<>(); titles.put("",""); for (JournalTitle journalTitle : watchedTarget.journalTitles) titles.put(""+journalTitle.id, journalTitle.title); return titles; } private static void setRelatedEntitiesOfModel(Document document, Form<Document> documentForm) { document.fastSubjects = FastSubjects.getFastSubjects(documentForm); document.portals = Portals.getPortals(documentForm); if (document.isBookOrBookChapter()) for (BlCollectionSubset blCollectionSubset : blCollectionSubsetList.getList()) if (documentForm.apply("blCollectionSubset_" + blCollectionSubset.id).value() != null) document.book.blCollectionSubsets.add(blCollectionSubset); } private static void setRelatedEntitiesOfView(Form<Document> documentForm, Document document) { documentForm.data().putAll(FastSubjects.getFormData(document.fastSubjects)); documentForm.data().putAll(Portals.getFormData(document.portals)); if (document.isBookOrBookChapter()) for (BlCollectionSubset portal : document.book.blCollectionSubsets) documentForm.data().put("blCollectionSubset_" + portal.id, "true"); } public static List<String> getPortalsSelection() { List<Portal> portals = Portals.portalList.getList(); List<String> portalTitles = new ArrayList<>(); portalTitles.add("All"); for (Portal portal : portals) portalTitles.add(portal.title); return portalTitles; } /** * Display the paginated list of Documents. * * @param page Current page number (starts from 0) * @param sortBy Column to be sorted * @param order Sort order (either asc or desc) * @param filter Filter applied on Documents */ public static Result list(DocumentFilter documentFilter, int pageNo, String sortBy, String order, String filter) { if (documentFilter.status == Document.Status.IGNORED) changeStatusOfIgnoredDocuments(); return renderList(documentFilter, pageNo, sortBy, order, filter, true); } private static void changeStatusOfIgnoredDocuments() { SqlUpdate su = Ebean.createSqlUpdate("update document" + " set status=" + Document.Status.DELETED.ordinal() + " where status=" + Document.Status.IGNORED.ordinal() + " and age(current_status_set) >= interval '1 month'"); Ebean.execute(su); Promise.promise(new Function0<Boolean>() { @Override public Boolean apply() { List<Document> documents = Document.find.where().eq("status", Document.Status.DELETED.ordinal()).findList(); for (Document document : documents) deleteHtmlFile(document.htmlFilename()); return true; } }); } public static Result overview(int pageNo, String sortBy, String order) { DocumentFilter documentFilter = new DocumentFilter(User.findByEmail(request().username()).id); return renderList(documentFilter, pageNo, sortBy, order, "", false); } public static Result renderList(DocumentFilter documentFilter, int pageNo, String sortBy, String order, String filter, boolean filters) { Logger.debug("Documents.list()"); Form<DocumentFilter> filterForm = Form.form(DocumentFilter.class).fill(documentFilter); for (String fastSubject : documentFilter.fastSubjects) filterForm.data().put(fastSubject, "true"); return ok( list.render( User.findByEmail(request().username()), filterForm, filter, Document.page(documentFilter, pageNo, 20, sortBy, order, filter), sortBy, order, filters) ); } public static Result filterByJson(String title) { JsonNode jsonData = null; if (title != null) { List<Document> documents = Document.find.where().icontains("title", title).findList(); jsonData = Json.toJson(documents); } return ok(jsonData); } public static Result html(String encodedFilename) { try { String filename = URLDecoder.decode(encodedFilename, "UTF-8"); File file = Play.application().getFile("../html/" + filename); return ok(file, filename); } catch (Exception e) { return ok("This file was not found on the system."); } } public static void deleteHtmlFiles(WatchedTarget watchedTarget) { for (Document document : watchedTarget.documents) deleteHtmlFile(document.htmlFilename()); } public static void deleteHtmlFile(String filename) { File file = Play.application().getFile("../html/" + filename); file.delete(); } public static List<Long> stringToLongList(String subject) { List<Long> subjectIds = new ArrayList<Long>(); if (subject != null && !subject.isEmpty()) { String[] subjects = subject.split(", "); for (String sId : subjects) { Long subjectId = Long.valueOf(sId); subjectIds.add(subjectId); } } return subjectIds; } public static String longListToString(List<Long> subjectIds) { return StringUtils.join(subjectIds, ", "); } }