Skip to content

Commit eaf03fe

Browse files
author
toddn
committed
Merge branch '2.0-work-in-progress' into for-2.0-rabbitmq-service
# Conflicts: # app/api/Datasets.scala # app/api/Files.scala # app/api/Indexes.scala # app/api/Metadata.scala # app/api/Status.scala # app/controllers/Files.scala # app/services/DI.scala # app/services/mongodb/MongoDBFileService.scala # app/services/mongodb/MongoDBMetadataService.scala # doc/src/sphinx/admin/customizing.rst # docker/play.plugins
2 parents 4ba1b26 + 7f2162c commit eaf03fe

File tree

83 files changed

+1440
-1920
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+1440
-1920
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,16 @@ Clowder runs in a location that doesn't not have access to https://clowderframew
3333

3434
### Changed
3535
- Include collection prefix in path when saving to S3.
36+
- Include length of file in `FileService` when saving the bytes to any backend service. This helps optimize S3 implementation.
3637
- Upgraded sbt from 0.13.0 to 0.13.6 to fix build failures.
3738
[CATS-1038](https://opensource.ncsa.illinois.edu/jira/browse/CATS-1038)
39+
- Remove Elasticsearch plugin and replace it with a `SearchService` framework and implementation. This separates out the
40+
logic that handles queuing and the logic that handles searching and indexing to be more modular and includes a memory-
41+
based queuing service in addition to the MongoDB service.
42+
43+
### Fixed
44+
- Calling api/Files.removeFile should no longer decrement related counters twice.
45+
[CATS-929](https://opensource.ncsa.illinois.edu/jira/browse/CATS-929)
3846

3947
## 1.8.0 - 2019-11-06
4048
**_Warning:_ This update adds a new permission for archiving files and adds it to the Admin role. Please make sure

app/api/Admin.scala

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import java.util.Date
55
import javax.inject.Inject
66
import models._
77
import org.apache.commons.lang3.StringEscapeUtils
8-
import play.api.libs.concurrent.Akka
98
import play.api.mvc.Controller
109
import play.api.Play.current
1110
import play.api.libs.json.Json.toJson
@@ -15,8 +14,6 @@ import services.mongodb.MongoSalatPlugin
1514
import play.api.Logger
1615
import util.Mail
1716

18-
import scala.concurrent.duration._
19-
import play.api.libs.concurrent.Execution.Implicits._
2017
import play.api.libs.json.{JsString, JsUndefined, JsValue}
2118

2219
/**
@@ -27,14 +24,14 @@ class Admin @Inject() (userService: UserService,
2724
collections: CollectionService,
2825
files: FileService,
2926
events: EventService,
30-
esqueue: ElasticsearchQueue) extends Controller with ApiController {
27+
searches: SearchService) extends Controller with ApiController {
3128

3229
/**
3330
* DANGER: deletes all data, keep users.
3431
*/
3532
def deleteAllData(resetAll: Boolean) = ServerAdminAction { implicit request =>
3633
current.plugin[MongoSalatPlugin].map(_.dropAllData(resetAll))
37-
current.plugin[ElasticsearchPlugin].map(_.deleteAll)
34+
searches.deleteAll
3835

3936
Ok(toJson("done"))
4037
}
@@ -174,8 +171,7 @@ class Admin @Inject() (userService: UserService,
174171
}
175172

176173
def reindex = ServerAdminAction { implicit request =>
177-
val success = esqueue.queue("index_all")
178-
if (success) Ok(toJson(Map("status" -> "reindex successfully queued")))
179-
else BadRequest(toJson(Map("status" -> "reindex queuing failed, Elasticsearch may be disabled")))
174+
val msg = searches.indexAll()
175+
Ok(toJson(Map("status" -> msg)))
180176
}
181177
}

app/api/Collections.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ class Collections @Inject() (datasets: DatasetService,
4343
folders : FolderService,
4444
files: FileService,
4545
metadataService : MetadataService,
46-
adminsNotifierService: AdminsNotifierService,
47-
esqueue: ElasticsearchQueue) extends ApiController {
46+
adminsNotifierService: AdminsNotifierService) extends ApiController {
4847

4948
def createCollection() = PermissionAction(Permission.CreateCollection) (parse.json) { implicit request =>
5049
Logger.debug("Creating new collection")
@@ -116,9 +115,8 @@ class Collections @Inject() (datasets: DatasetService,
116115
def reindex(id: UUID, recursive: Boolean) = PermissionAction(Permission.CreateCollection, Some(ResourceRef(ResourceRef.collection, id))) { implicit request =>
117116
collections.get(id) match {
118117
case Some(coll) => {
119-
val success = esqueue.queue("index_collection", new ResourceRef('collection, id), new ElasticsearchParameters(recursive=recursive))
120-
if (success) Ok(toJson(Map("status" -> "reindex successfully queued")))
121-
else BadRequest(toJson(Map("status" -> "reindex queuing failed, Elasticsearch may be disabled")))
118+
collections.index(id)
119+
Ok(toJson(s"Collection $id reindexed"))
122120
}
123121
case None => {
124122
Logger.error("Error getting collection" + id)

app/api/Comments.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import play.api.i18n.Messages
1616
* Comments on datasets.
1717
*
1818
*/
19-
class Comments @Inject()(datasets: DatasetService, comments: CommentService, events: EventService, users: UserService) extends ApiController {
19+
class Comments @Inject()(datasets: DatasetService,
20+
comments: CommentService,
21+
events: EventService,
22+
users: UserService,
23+
searches: SearchService) extends ApiController {
2024

2125
def comment(id: UUID) = PermissionAction(Permission.AddComment, Some(ResourceRef(ResourceRef.comment, id)))(parse.json) { implicit request =>
2226
Logger.trace("Adding comment")
@@ -37,9 +41,7 @@ class Comments @Inject()(datasets: DatasetService, comments: CommentService, eve
3741
if (parent.dataset_id.isDefined) {
3842
datasets.get(parent.dataset_id.get) match {
3943
case Some(dataset) => {
40-
current.plugin[ElasticsearchPlugin].foreach {
41-
_.index(dataset, false)
42-
}
44+
searches.index(dataset, false)
4345
}
4446
case None => Logger.error("Dataset not found: " + id)
4547
}

app/api/Extractions.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ class Extractions @Inject()(
8585
val fid = for {response <- futureResponse} yield {
8686
if (response.status == 200) {
8787
val inputStream: InputStream = response.ahcResponse.getResponseBodyAsStream()
88-
val file = files.save(inputStream, filename, response.header("Content-Type"), user, null)
88+
val contentLengthStr = response.header("Content-Length").getOrElse("-1")
89+
val contentLength = Integer.parseInt(contentLengthStr).toLong
90+
val file = files.save(inputStream, filename, contentLength, response.header("Content-Type"), user, null)
8991
file match {
9092
case Some(f) => {
9193
// Add new file & byte count to appConfig

app/api/Files.scala

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -507,20 +507,6 @@ class Files @Inject()(
507507
}
508508
}
509509

510-
/**
511-
* Upload file using multipart form enconding.
512-
*/
513-
@deprecated
514-
def upload(showPreviews: String = "DatasetLevel", originalZipFile: String = "", flagsFromPrevious: String = "") = PermissionAction(Permission.AddFile)(parse.multipartFormData) { implicit request =>
515-
val uploadedFiles = FileUtils.uploadFilesMultipart(request, showPreviews = showPreviews, originalZipFile = originalZipFile,
516-
flagsFromPrevious = flagsFromPrevious, apiKey = request.apiKey)
517-
uploadedFiles.length match {
518-
case 0 => BadRequest("No files uploaded")
519-
case 1 => Ok(Json.obj("id" -> uploadedFiles.head.id))
520-
case _ => Ok(Json.obj("ids" -> uploadedFiles.toList))
521-
}
522-
}
523-
524510
/**
525511
* Upload a file to a specific dataset
526512
*/

app/api/Geometry.scala

Lines changed: 0 additions & 30 deletions
This file was deleted.

app/api/Logos.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ class Logos @Inject()(logos: LogoService) extends ApiController {
7878
request.body.file("image") match {
7979
case Some(f) => {
8080
val ct = util.FileUtils.getContentType(f.filename, f.contentType)
81-
logos.save(new FileInputStream(f.ref.file), p, name, showText, Some(ct), user) match {
81+
logos.save(new FileInputStream(f.ref.file), p, name, showText,
82+
f.ref.file.length, Some(ct), user) match {
8283
case Some(logo) => {
8384
// delete old images
8485
logos.list(Some(p), Some(name)).foreach { l =>

app/api/Previews.scala

Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class Previews @Inject()(previews: PreviewService, tiles: TileService) extends A
9999
/**
100100
* Upload a preview.
101101
*/
102-
def upload(iipKey: String = "") =
102+
def upload() =
103103
PermissionAction(Permission.AddFile)(parse.multipartFormData) { implicit request =>
104104
request.body.file("File").map { f =>
105105
try {
@@ -110,26 +110,8 @@ class Previews @Inject()(previews: PreviewService, tiles: TileService) extends A
110110
if (f.contentType.getOrElse("application/octet-stream").equals("application/xml"))
111111
realContentType = Some("application/dzi")
112112

113-
val id = UUID(previews.save(new FileInputStream(f.ref.file), f.filename, realContentType))
113+
val id = UUID(previews.save(new FileInputStream(f.ref.file), f.filename, f.ref.file.length, realContentType))
114114
Logger.debug("ctp: " + realContentType)
115-
// for IIP server references, store the IIP URL, key and filename on the IIP server for possible later deletion of the previewed file
116-
if (f.filename.endsWith(".imageurl")) {
117-
val iipRefReader = new BufferedReader(new FileReader(f.ref.file));
118-
119-
val serverLine = iipRefReader.readLine()
120-
var urlEnd = serverLine.indexOf("/", serverLine.indexOf("://") + 3)
121-
if (urlEnd == -1) {
122-
urlEnd = serverLine.length()
123-
}
124-
val iipURL = serverLine.substring(8, urlEnd)
125-
126-
val imageLine = iipRefReader.readLine()
127-
val iipImage = imageLine.substring(imageLine.lastIndexOf("/") + 1)
128-
129-
iipRefReader.close()
130-
131-
previews.setIIPReferences(id, iipURL, iipImage, iipKey)
132-
}
133115

134116
// Check whether a title for the preview was sent
135117
request.body.dataParts.get("title") match {
@@ -259,60 +241,6 @@ class Previews @Inject()(previews: PreviewService, tiles: TileService) extends A
259241
}
260242
}
261243

262-
/**
263-
* Add annotation to 3D model preview.
264-
*/
265-
def attachAnnotation(preview_id: UUID) = PermissionAction(Permission.AddFile, Some(ResourceRef(ResourceRef.preview, preview_id)))(parse.json) { implicit request =>
266-
val x_coord = (request.body \ "x_coord").asOpt[String].getOrElse("0.0")
267-
val y_coord = (request.body \ "y_coord").asOpt[String].getOrElse("0.0")
268-
val z_coord = (request.body \ "z_coord").asOpt[String].getOrElse("0.0")
269-
val description = (request.body \ "description").asOpt[String].getOrElse("")
270-
271-
previews.get(preview_id) match {
272-
case Some(preview) => {
273-
val annotation = ThreeDAnnotation(x_coord, y_coord, z_coord, description)
274-
previews.annotation(preview_id, annotation)
275-
Ok(toJson(Map("status" -> "success")))
276-
}
277-
case None => BadRequest(toJson("Preview not found " + preview_id))
278-
}
279-
}
280-
281-
def editAnnotation(preview_id: UUID) = PermissionAction(Permission.AddFile, Some(ResourceRef(ResourceRef.preview, preview_id)))(parse.json) { implicit request =>
282-
Logger.debug("thereq: " + request.body.toString)
283-
val x_coord = (request.body \ "x_coord").asOpt[String].getOrElse("0.0")
284-
val y_coord = (request.body \ "y_coord").asOpt[String].getOrElse("0.0")
285-
val z_coord = (request.body \ "z_coord").asOpt[String].getOrElse("0.0")
286-
val description = (request.body \ "description").asOpt[String].getOrElse("")
287-
288-
previews.get(preview_id) match {
289-
case Some(preview) => {
290-
previews.findAnnotation(preview_id, x_coord, y_coord, z_coord) match {
291-
case Some(annotation) => {
292-
previews.updateAnnotation(preview_id, annotation.id, description)
293-
Ok(toJson(Map("status" -> "success")))
294-
}
295-
case None => Ok(toJson(Map("status" -> "success"))) //What the user sees locally must not change if an annotation is deleted after the user loads the dataset
296-
//but before attempting to modify the selected annotation's description.
297-
//BadRequest(toJson("Annotation for preview " + preview_id + " not found: " + x_coord + "," + y_coord + "," + z_coord))
298-
}
299-
}
300-
case None => BadRequest(toJson("Preview not found " + preview_id))
301-
}
302-
}
303-
304-
def listAnnotations(preview_id: UUID) = PermissionAction(Permission.ViewFile, Some(ResourceRef(ResourceRef.preview, preview_id))) { implicit request =>
305-
previews.get(preview_id) match {
306-
case Some(preview) => {
307-
val annotationsOfPreview = previews.listAnnotations(preview_id)
308-
val list = for (annotation <- annotationsOfPreview) yield jsonAnnotation(annotation)
309-
Logger.debug("thelist: " + toJson(list))
310-
Ok(toJson(list))
311-
}
312-
case None => BadRequest(toJson("Preview not found " + preview_id))
313-
}
314-
}
315-
316244
/**
317245
* Update the title field of a preview to change what is displayed on preview tab
318246
* @param preview_id UUID of preview to change

app/api/Search.scala

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package api
22

33
import api.Permission._
44
import services.{DatasetService, FileService, CollectionService, PreviewService, SpaceService,
5-
MultimediaQueryService, ElasticsearchPlugin}
5+
MultimediaQueryService, SearchService}
66
import play.Logger
77
import scala.collection.mutable.{ListBuffer, HashMap}
8-
import util.{SearchUtils, SearchResult}
8+
import util.SearchResult
99
import play.api.libs.json.{JsObject, Json, JsValue}
1010
import play.api.libs.json.Json.toJson
1111
import javax.inject.{Inject, Singleton}
@@ -20,45 +20,32 @@ class Search @Inject() (
2020
collections: CollectionService,
2121
previews: PreviewService,
2222
queries: MultimediaQueryService,
23-
spaces: SpaceService) extends ApiController {
23+
spaces: SpaceService,
24+
searches: SearchService) extends ApiController {
2425

2526
/** Search using a simple text string with filters */
2627
def search(query: String, resource_type: Option[String], datasetid: Option[String], collectionid: Option[String],
2728
spaceid: Option[String], folderid: Option[String], field: Option[String], tag: Option[String],
2829
from: Option[Int], size: Option[Int], page: Option[Int]) = PermissionAction(Permission.ViewDataset) { implicit request =>
29-
current.plugin[ElasticsearchPlugin] match {
30-
case Some(plugin) => {
31-
// If from is specified, use it. Otherwise use page * size of page if possible, otherwise use 0.
32-
val from_index = from match {
33-
case Some(f) => from
34-
case None => page match {
35-
case Some(p) => Some(size.getOrElse(0) * p)
36-
case None => None
37-
}
30+
if (searches.isEnabled) {
31+
// If from is specified, use it. Otherwise use page * size of page if possible, otherwise use 0.
32+
val from_index = from match {
33+
case Some(f) => from
34+
case None => page match {
35+
case Some(p) => Some(size.getOrElse(0) * p)
36+
case None => None
3837
}
38+
}
3939

40-
// TODO: Better way to build a URL?
41-
val source_url = s"/api/search?query=$query" +
42-
(resource_type match {case Some(x) => s"&resource_type=$x" case None => ""}) +
43-
(datasetid match {case Some(x) => s"&datasetid=$x" case None => ""}) +
44-
(collectionid match {case Some(x) => s"&collectionid=$x" case None => ""}) +
45-
(spaceid match {case Some(x) => s"&spaceid=$x" case None => ""}) +
46-
(folderid match {case Some(x) => s"&folderid=$x" case None => ""}) +
47-
(field match {case Some(x) => s"&field=$x" case None => ""}) +
48-
(tag match {case Some(x) => s"&tag=$x" case None => ""})
49-
50-
// Add space filter to search here as a simple permissions check
51-
val permitted = spaces.listAccess(0, Set[Permission](Permission.ViewSpace), request.user, true, true, false, false).map(sp => sp.id)
40+
// Add space filter to search here as a simple permissions check
41+
val permitted = spaces.listAccess(0, Set[Permission](Permission.ViewSpace), request.user, true, true, false, false).map(sp => sp.id)
5242

53-
val response = plugin.search(query, resource_type, datasetid, collectionid, spaceid, folderid, field, tag, from_index, size, permitted, request.user)
43+
val result = searches.search(query, resource_type, datasetid, collectionid, spaceid, folderid, field, tag, from_index, size, permitted, request.user)
5444

55-
val result = SearchUtils.prepareSearchResponse(response, source_url, request.user)
56-
Ok(toJson(result))
57-
}
58-
case None => {
59-
Logger.debug("Search plugin not enabled")
60-
Ok(views.html.pluginNotEnabled("Text search"))
61-
}
45+
Ok(toJson(result))
46+
} else {
47+
Logger.debug("Search plugin not enabled")
48+
Ok(views.html.pluginNotEnabled("Text search"))
6249
}
6350
}
6451

@@ -67,21 +54,14 @@ class Search @Inject() (
6754
implicit request =>
6855
implicit val user = request.user
6956

70-
current.plugin[ElasticsearchPlugin] match {
71-
case Some(plugin) => {
72-
val queryList = Json.parse(query).as[List[JsValue]]
73-
val response = plugin.search(queryList, grouping, from, size, user)
74-
75-
// TODO: Better way to build a URL?
76-
val source_url = s"/api/search?query=$query&grouping=$grouping"
77-
78-
val result = SearchUtils.prepareSearchResponse(response, source_url, user)
79-
Ok(toJson(result))
80-
}
81-
case None => {
82-
BadRequest("Elasticsearch plugin could not be reached")
83-
}
57+
if (searches.isEnabled) {
58+
val queryList = Json.parse(query).as[List[JsValue]]
59+
val result = searches.search(queryList, grouping, from, size, user)
60+
Ok(toJson(result))
61+
} else {
62+
BadRequest("Elasticsearch plugin could not be reached")
8463
}
64+
8565
}
8666

8767
/**

0 commit comments

Comments
 (0)