Skip to content

Commit

Permalink
added commit logs to S3 and fixed #1068
Browse files Browse the repository at this point in the history
  • Loading branch information
ppazos committed Mar 3, 2020
1 parent c9745e0 commit f1069ec
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 76 deletions.
26 changes: 23 additions & 3 deletions grails-app/conf/BootStrap.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import com.cabolabs.ehrserver.ResourceService
import com.cabolabs.ehrserver.notification.*
import com.cabolabs.ehrserver.conf.ConfigurationItem
import com.cabolabs.ehrserver.ehr.*
import com.cabolabs.ehrserver.log.CommitLoggerService
import com.cabolabs.ehrserver.versions.VersionFSRepoService
import com.cabolabs.openehr.opt.manager.OptManager
import com.cabolabs.ehrserver.parsers.JsonService
Expand All @@ -57,6 +56,12 @@ import com.cabolabs.openehr.terminology.TerminologyParser
import com.cabolabs.ehrserver.openehr.OptRepositoryS3Impl
import com.cabolabs.openehr.opt.manager.OptRepositoryFSImpl

import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.auth.AWSStaticCredentialsProvider
import com.amazonaws.services.s3.AmazonS3ClientBuilder


//org.grails.orm.hibernate.cfg.GrailsHibernateUtil

class BootStrap {
Expand Down Expand Up @@ -222,6 +227,8 @@ class BootStrap {
it.registerObjectMarshaller(Contribution) { contribution ->

def commit = CommitLog.findClazzAndByObjectUid('Contribution', contribution.uid)

// FIXME: for S3 this shouldn't depend on the FS, should get the commit through a service
def file = new File(Holders.config.app.commit_logs.withTrailSeparator() +
contribution.organizationUid.withTrailSeparator() +
commit.fileUid + '.xml') // TODO: read json
Expand Down Expand Up @@ -1325,8 +1332,22 @@ class BootStrap {
// 2. initialize OptMAnager with it
// 3. default opts should be loaded from there if any (LATER)

def repo = new OptRepositoryS3Impl(Holders.config.aws.folders.opt_repo.withTrailSeparator())
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(
"${Holders.config.aws.accessKey}",
"${Holders.config.aws.secretKey}")

AmazonS3 s3 = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.withRegion("${Holders.config.aws.region}")
.build()

def repo = new OptRepositoryS3Impl(s3)
optMan = OptManager.getInstance(repo)

def orgs = Organization.list()
orgs.each { org ->
optMan.loadAll(org.uid, true)
}
}
else
{
Expand All @@ -1336,7 +1357,6 @@ class BootStrap {




// ============================================================
// migration for latest changes
/*
Expand Down
2 changes: 1 addition & 1 deletion grails-app/conf/Config.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ aws {
bucket = "ehr-server-test"
region = "us-east-1"
folders { /* keys of folders in S3 */
version_repo = "versions/" // important should end with /
version_repo = "versions/"
commit_logs = "commits/"
opt_repo = "opts/"
}
Expand Down
15 changes: 10 additions & 5 deletions grails-app/conf/spring/resources.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,20 @@ import com.cabolabs.ehrserver.versions.VersionS3RepoService
import com.cabolabs.ehrserver.openehr.OptFSService
import com.cabolabs.ehrserver.openehr.OptS3Service
import com.cabolabs.archetype.OperationalTemplateIndexer
import com.cabolabs.ehrserver.log.CommitLoggerFSService
import com.cabolabs.ehrserver.log.CommitLoggerS3Service

beans = {

// Configuration for using File System or S3 file access
// versionRepoService(VersionS3RepoService)
// optService(OptS3Service)
// Configuration for using S3 file access
versionRepoService(VersionS3RepoService)
optService(OptS3Service)
commitLoggerService(CommitLoggerS3Service)

versionRepoService(VersionFSRepoService)
optService(OptFSService)
// Configuration for using File System file access
// versionRepoService(VersionFSRepoService)
// optService(OptFSService)
// commitLoggerService(CommitLoggerFSService)

authProvider(AuthProvider) {
passwordEncoder = ref("passwordEncoder") // from plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import grails.util.Holders
import javax.servlet.http.HttpServletRequest

@Transactional
class CommitLoggerService {
class CommitLoggerFSService {

def config = Holders.config.app

Expand All @@ -53,12 +53,8 @@ class CommitLoggerService {
// return new File(config.commit_logs.withTrailSeparator() + orguid).canWrite()
// }

// private boolean repoExistsOrg(String orguid)
// {
// return new File(config.commit_logs.withTrailSeparator() + orguid).exists()
// }

static String getCommitContents(CommitLog commit)
String getCommitContents(CommitLog commit)
{
return new File(commit.fileLocation).text
}
Expand Down Expand Up @@ -165,14 +161,16 @@ class CommitLoggerService {
String orguid = request.securityStatelessMap.extradata.org_uid

// saves the reference to the log file in the DB
commit.fileLocation = newCommitFileLocation(orguid, ext)
commit.fileLocation = newCommitFileLocation(orguid, contributionUid, ext)

// ==================================================
// creates parent subfolders if dont exist
def containerFolder = new File(new File(commit.fileLocation).getParent())
containerFolder.mkdirs()

def commitLog = new File(commit.fileLocation)
commitLog << logContent
// ==================================================
}


Expand All @@ -181,9 +179,11 @@ class CommitLoggerService {
commit.save(failOnError: true)
}

static String newCommitFileLocation(String orguid, String ext)
static String newCommitFileLocation(String orguid, String contributionUid, String ext)
{
if (!contributionUid) contributionUid = java.util.UUID.randomUUID()

// TODO: this is XML only, for JSON versions we need to consider the content type of the commit adding a new parameter
return Holders.config.app.commit_logs.withTrailSeparator() + orguid.withTrailSeparator() + java.util.UUID.randomUUID() + ext
return Holders.config.app.commit_logs.withTrailSeparator() + orguid.withTrailSeparator() + contributionUid + ext
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Copyright 2011-2017 CaboLabs Health Informatics
*
* The EHRServer was designed and developed by Pablo Pazos Gutierrez <pablo.pazos@cabolabs.com> at CaboLabs Health Informatics (www.cabolabs.com).
*
* You can't remove this notice from the source code, you can't remove the "Powered by CaboLabs" from the UI, you can't remove this notice from the window that appears then the "Powered by CaboLabs" link is clicked.
*
* Any modifications to the provided source code can be stated below this notice.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.cabolabs.ehrserver.log

import com.cabolabs.ehrserver.openehr.common.change_control.CommitLog
import grails.transaction.Transactional
import grails.util.Holders
import javax.servlet.http.HttpServletRequest

import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.auth.AWSStaticCredentialsProvider
import com.amazonaws.services.s3.AmazonS3ClientBuilder

@Transactional
class CommitLoggerS3Service {

def config = Holders.config.app

String getCommitContents(CommitLog commit)
{
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(
"${Holders.config.aws.accessKey}",
"${Holders.config.aws.secretKey}")

AmazonS3 s3 = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.withRegion("${Holders.config.aws.region}")
.build()

// https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3.html#getObjectAsString-java.lang.String-java.lang.String-
return s3.getObjectAsString(
Holders.config.aws.bucket,
commit.fileLocation // key
)
}

/**
* If the content (xml or json) was read from the request, we won't be able to read it again,
* reading twice from the request will result on a java.io.IOException "stream closed"
*/
def log(HttpServletRequest request, String contributionUid, boolean success, String content, session, params)
{
// http://docs.oracle.com/javaee/1.4/api/javax/servlet/http/HttpServletRequest.html
def clientLocale = request.locale
def isSecure = request.isSecure() // it uses https?
//def params = request.parameterMap.collectEntries{ [(it.key): it.value[0]] } // params is a map of lists, we need a map of strings
def contentType = request.contentType
def contentLength = request.contentLength
def encoding = request.characterEncoding
def cookies = request.cookies

// Stateless username
def authUser = request.securityStatelessMap.username


// FIXME: file can be read once from the request ...
// I think this can be changed to an if (content) .. else try to read.
def logContent

// contentType can include charset, and here we need to check only the MIME type
// text/xml; charset=UTF-8
def requestContentTypeOnly = contentType.split(';')[0]

try
{
// this is used when there is no content read from the request.reader yet
// if the content (xml or json commit) was read, it should come in the content param
switch (requestContentTypeOnly)
{
// urlencoded not supported
//case 'application/x-www-form-urlencoded':
//break
case 'multipart/form-data': // TODO: check if it us JSON or XML (we dont really know)
def f = request.getFile('versions')
logContent = f?.text
break
case ['application/xml', 'text/xml']:
logContent = request.reader?.text
break
// TODO: add logger for json commits
case 'application/json':
logContent = request.reader?.text
break
default:
println 'commit logger contentType '+ contentType +' not supported'
}
}
catch(java.io.IOException e) // file already read by the controller
{
logContent = content // can be null or empty
}

// TODO: log specific errors thrown by the controller
/*
println "commmit log"
println params.ehrUid
println contributionUid
println clientIP
println clientLocale
println params
println contentType
println contentLength
println url
println authUser
*/

// save the json or xml to the commit log
def ext = '.xml'
if (contentType == 'application/json') ext = '.json'

// empty XML is a possible error so the commit should be saved to the
// database but no xml file will be created

def commit = new CommitLog(
ehrUid: params.ehrUid,
locale: clientLocale,
params: params,
contentType: contentType,
contentLength: contentLength,
success: success,
username: authUser,
action: params.controller +':'+params.action,
objectClazz: 'Contribution',
objectUid: contributionUid, // can be null if !success
remoteAddr: request.remoteAddr,
clientIp: request.getHeader("Client-IP"), // can be null
xForwardedFor: request.getHeader("X-Forwarded-For"), // can be null
referer: request.getHeader('referer'), // can be null
requestURL: request.requestURL,
matchedURI: request.forwardURI,
sessionId: session.id.toString()
)

if (logContent)
{
String orguid = request.securityStatelessMap.extradata.org_uid

// saves the reference to the log file in the DB
commit.fileLocation = newCommitFileLocation(orguid, contributionUid, ext)

// ==================================================
// puts commit content in S3
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(
"${Holders.config.aws.accessKey}",
"${Holders.config.aws.secretKey}")

AmazonS3 s3 = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.withRegion("${Holders.config.aws.region}")
.build()

// https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3.html#putObject-java.lang.String-java.lang.String-java.lang.String-
def putObjectResult = s3.putObject(
Holders.config.aws.bucket,
commit.fileLocation,
logContent
)
// ==================================================
}


// save log to DB
if (!commit.validate()) println commit.errors
commit.save(failOnError: true)
}

static String newCommitFileLocation(String orguid, String contributionUid, String ext)
{
if (!contributionUid) contributionUid = java.util.UUID.randomUUID()

// TODO: this is XML only, for JSON versions we need to consider the content type of the commit adding a new parameter
return Holders.config.aws.folders.commit_logs.withTrailSeparator() + orguid.withTrailSeparator() + contributionUid + ext
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ class OptS3Service {
def total = 0
list_objectSummary.each { s3ObjectSummary ->

// FIXME: filter is not defined
// I think this should check the key ends with OPT, this comes from VersionS3RepoService
//if (filter.call(s3ObjectSummary))
//{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class SyncMarshallersService {
String ext = '.xml'
if (_commit.contentType.contains('json')) ext = '.json'

// FIXME: for S3 this shouldn't depend on the FS, should get the commit through a service
def file = new File(config.commit_logs.withTrailSeparator() +
c.organizationUid.withTrailSeparator() +
_commit.fileUid + ext)
Expand Down
Loading

0 comments on commit f1069ec

Please sign in to comment.