root/trunk/grails-app/controllers/importer/ImporterController.groovy @ 206

Revision 206, 33.7 KB (checked in by taco@…, 3 years ago)

#21 Small changes in ImporterController?.groovy and _showSampleLayout.gsp

Line 
1package importer
2
3import data.SimpleAssay
4import data.SimpleAssayMeasurement
5import data.SimpleAssayMeasurementType
6import data.SimpleAssaySample
7
8/**
9 * @author Adem and Jahn
10 * @since 20100602
11 *
12 *
13 * This Controller provides controler functionality for three views.
14 *
15 * (1) A workflow for creating new Assays from a string of text given as XLS sheet or as pasted, tab-delimited plain text.
16 *
17 * (2) A simple preview for all Assay data within a Study.
18 *
19 * (3) A simple editor for editing Assay measurements.
20 *
21 */
22
23
24
25import org.codehaus.groovy.grails.web.json.*
26import org.apache.poi.hssf.usermodel.*
27import org.apache.poi.poifs.filesystem.POIFSFileSystem
28import org.apache.poi.ss.usermodel.DataFormatter
29import grails.converters.*
30import org.apache.poi.hssf.usermodel.HSSFWorkbook
31import dbnp.rest.common.CommunicationManager
32import org.codehaus.groovy.grails.commons.GrailsApplication
33import org.apache.commons.codec.*
34
35
36class ImporterController {
37
38    def ImporterService
39    def searchableService
40    def fileService
41
42    def index = {
43        String.metaClass.mostSimilarTo = { mostSimilar(delegate, it) }
44        redirect(action: 'pages')
45    }
46   
47    /**
48     *   This flow is provides the user with a means for importing Assay data into the Simple Assay Module (SAM).
49     *
50     *   @param string externalStudyID          The code member of a Study domain object in GSCF.
51     *   @param string externalAssayID          The externalAssayID of member of a Assay domain object in GSCF.
52     *
53     *
54     *
55     *   The flow performs the following view actions.
56     *
57     *   (1) The user pastes a tab-delimted string into a text area containing a table to import.
58     *
59     *   (2) The user selects the columns of the table that he/she wants to import.
60     *
61     *   (3) The user matches the subjects in the table to those in the system.
62     *
63     *   (4) The data is validated at the GSCF and saved in SAM.
64     *
65     *   (5) An overvie of the imported data is shown to the user.
66     *
67     *
68     *
69     *   Variables stored in flow scope:
70     *
71     *   flow.selectedAssayID   The externalAssayID of a GSCF assay.
72     *   flow.studies                   list of all studies. each element e is a map with two entires. e['name'] gives
73     *                                                  the name of the study, e['id'] gives the corresponding id.
74     *
75     *   flow.selectedStudy             name of the selected study as string
76     *
77     *   flow.selectedStudyID   external ID of the selected study
78     *
79     *   flow.selectedAsasyID   external ID of the selected assay
80     *
81     *   flow.data                              2-dimensional map containing information to be stored as read in from the user
82     *                          The format of this map is as follows.
83     *                          The first level map has GSCF-subject names as keys. The values are level-2 maps.
84     *                          Level-2 maps have GSCF start time as keys which are mapped to string values.
85     *                          These string values are at some point stored as SimpleAssayMeasurements.
86     *
87     *   flow.samples                   list of SimpleAssaySamples that belong to this import's assay
88     *   
89     *   flow.tabDelimitedString  tab-delimited string representing a table. given by user.
90     */
91
92    def startError = {
93        render (view:'/importer/pages/_startError')
94    }
95
96    def pagesFlow = {
97
98        String.metaClass.mostSimilarTo = { mostSimilar(delegate, it) }
99
100        onStart {
101            flow.layouts = [ 'Sample Layout','Subject Layout' ]
102
103            try {
104                def list = CommunicationManager.getStudies( session.sessionToken )
105                def studies = list.collect{ [token: it['studyToken'], name: it['title']] }
106                flow.studies = studies
107            }
108            catch (Exception e){
109                redirect(action: 'startError')
110            }
111            flow.warnings = []
112
113            flow.page = 0
114            flow.pages = [
115                [title: 'Select Study'],
116                [title: 'Select Assay'],
117                [title: 'Select Layout'],
118                [title: 'Input Assay Data'],
119                [title: 'Select Data'],
120                [title: 'Show  Data'],
121                [title: 'Done']
122            ]
123        }
124
125        mainPage {
126            render(view: '/importer/importMain')
127            onRender {
128                flow.page = 0
129                next()
130            }
131            on("next") {
132                success()
133            }.to "selectStudy"
134        }
135
136        selectStudy {
137            render(view: '_selectStudy')
138            onRender {
139                flow.page = 1
140            }
141            on("next") {
142                flow.selectedStudyID = params['studyId']
143                    println flow.selectedStudyID
144                def assays = []
145                def assayNames = []
146                CommunicationManager.getAssays( session.sessionToken, flow.selectedStudyID, grailsApplication.config.grails.serverURL ).each{
147                    assays.push( ['name':it['name'],'externalAssayId':it['assayToken']] )
148                    assayNames.push( it['name'] )
149                }
150                    println assays
151                flow.assays     = assays
152                flow.assayNames = assayNames
153                success()
154            }.to "selectAssay"
155        }
156
157        selectAssay {
158            render( view: '_selectAssay' )
159            onRender {
160                flow.page = 2
161            }
162            on("next") {
163                def selectedName = params['assayName']
164                def data = flow.assays.find{ it['name'] == selectedName }
165                flow.selectedAssayID = data['externalAssayId']
166                flow.selectedAssayName = selectedName
167                // Save the local cache of the selected assay
168                flow.assay = saveAssay(flow.selectedAssayID, flow.selectedStudyID, selectedName )
169                success()
170            }.to "selectLayout"
171            on('previous').to('selectStudy')
172        }
173
174
175        // get table data from user
176        getTable {                                                      // user gives text input as file or by copy-and-paste
177            render(view: '_getTable')
178            onRender {
179                flow.page = 4
180            }
181            on("next"){
182                def tabbedTable = params['assayData']
183                if( (tabbedTable.replaceAll("^\\s+", "").size() > 0) && (params['file'] == "null" ) ) {
184                    flow.tabDelimitedString = tabbedTable
185                    flow.file = params['file']
186                    success()
187                }
188                // there is a file
189                else if ((tabbedTable.replaceAll("^\\s+", "").size() == 0) && (params['file'] != "null" )) {
190                    try {
191                        flow.tabDelimitedString = fileService.get(params['file'])
192                        flow.file = params['file']
193                        success()
194                    }
195                    catch (Exception e){
196                        fileService.delete(params['file'])
197                        flash.errors = "File Error : Please check the file integrity. It should be an Excel sheet."
198                        error()
199                    }
200                }
201                // catch error if there is nothing to do
202                else if ((tabbedTable.replaceAll("^\\s+", "").size() == 0) && (params['file'] == "null" ))
203                {
204                    flash.errors = 'No user input : Please paste your data or select a file'
205                    error()
206                }
207                //catch error if there is too much information
208                else if ((tabbedTable.replaceAll("^\\s+", "").size() > 0) && (params['file'] != "null" ))
209                {
210                    flash.errors = 'Too much information : Please paste your data OR select a file'
211                    error()
212                }
213            }.to "buildTableModel"
214            on("previous").to "selectAssay"
215        }
216
217
218        selectLayout{
219            render( view: '_selectLayout' )
220            onRender {
221                flow.page = 3
222            }
223            on("next") {
224                def layout = params['layout']
225                flow.hasSubjectLayout =  (layout==flow.layouts[1]) ? true : false ;
226            }.to "getTable"
227            on('previous').to('selectAssay')
228        }
229
230
231        // methods for parsing the file
232        // only for Subject Layout
233        buildTableModel{
234            action {
235                // define the list of samples into the study
236                flow.samples     = getSamples( flow.selectedAssayID )
237                flow.sampleTimes = flow.samples.collect{ it.getStartTime() }.unique()
238                buildModel( flow )   // add all table data to the flow
239                if( flow.hasSubjectLayout ) { return subjectLayout() }
240                else { return sampleLayout() }
241            }
242            on("subjectLayout"){}.to "selectTypesForSubjectLayout"
243            on( "sampleLayout"){}.to "selectTypesForSampleLayout"
244        }
245
246
247        // only for sample layout
248        selectTypesForSampleLayout{                                     // match columns to assays and samples (i.e., date-subject pair)
249            render(view: '_selectTypesForSampleLayout')
250            onRender {
251                flow.page = 5
252            }
253            on('refresh'){
254                def measurementTypes = []
255                for(i in data.SimpleAssayMeasurementType.list()){
256                    measurementTypes.add(i.name)
257                    i.discard()
258                }
259                flow.measurementTypes = measurementTypes
260            }.to('selectTypesForSampleLayout')
261            on("next"){
262                // return the list of subjects and assays
263                try{
264                    addMeasurementsToFlow(flow, params)  // set up-to-date measurements in flow.measurements
265                }
266                catch (Exception e) {
267                    flash.erors = "Error"
268                    error()
269                }
270            }.to "showDataForSampleLayout"
271            on("previous").to('selectLayout')
272        }
273
274        // only for subject layout
275        selectTypesForSubjectLayout {           // match columns to assays and samples (i.e., date-subject pair)
276            render(view: '_selectTypesForSubjectLayout' )
277            onRender {
278                flow.page = 5
279            }
280            on("next"){
281                // return the list of subjects and assays
282                getAttributes(flow, params)
283            }.to "fetchSubjectsForSubjectLayout"
284            on("previous").to('selectLayout')
285        }
286
287
288        // return the list of the subjects into the study from GSCF
289        // only for subject layout
290        fetchSubjectsForSubjectLayout {
291            action {
292                def studySubjects = []
293                for (subject in CommunicationManager
294                    .getSubjects( session.sessionToken, flow.selectedStudyID.toString() )){
295                    studySubjects.add(subject)
296                }
297
298                if (studySubjects.size() < flow.importedSubjects.size() ) {
299                    flash.errors = 'Error : wrong number of subjects'
300                    error()
301                }
302
303                flow.studySubjects = studySubjects
304            }
305            on("success").to("validateSubjects")
306        }
307
308
309        // only for subject layout
310        validateSubjects {
311            render(view: '_validateSubjects')                           // probably this requires more than one state
312            onRender {
313                flow.page = 5
314            }
315            on("next"){
316                // get the list of matching subjects and the data
317                getSubjects(flow, params)
318            }.to "showDataForSubjectLayout"
319            on("previous").to "selectTypesForSubjectLayout"
320        }
321
322
323        // for sample layout
324        showDataForSampleLayout {                                                       // show summary of assay to user
325            render(view:'_showSampleLayout')
326            onRender {
327                flow.page = 6
328            }
329            on("next").to "saveData"
330            on('previous').to('selectTypesForSampleLayout')
331        }
332       
333
334       
335
336        // for subject layout and sample layout
337        showDataForSubjectLayout {                                                      // show summary of assay to user
338            render(view:'_showSubjectLayout')
339            onRender {
340                flow.page = 6
341            }
342            on("next").to "saveData"
343            on('previous').to('validateSubject')
344        }
345       
346
347
348        // try to save data
349        // for subject layout and sample layout
350        saveData {                                                     
351            action{
352                if( flow.hasSubjectLayout ) {                                   // save subject layout
353                    flow.warnings = saveSAMforSubjectLayout( flow.selectedStudyID, flow.selectedAssayID,
354                        flow.selectedAssayName, flow.subjects, flow.times, flow.data )
355                }
356                else {                                                                                  // save sample
357                    flow.measurements.each {
358                        it.save(flush:true)
359                    }
360                }
361                fileService.delete(flow.file)
362                return success()
363            }
364            on("success").to "done"
365        }
366
367
368        done {                                                          // display success or error message,
369            render(view: '_done')                       // go back to first step or exit
370            onRender {
371                flow.page = 7
372            }
373            on("next").to "done"
374        }
375
376    }
377
378    /* what is this function */
379    private getAttributes(flow, params) {
380        // matchingAssays contains the ID for each assays into the file and into the study
381        def matchingAssays = []
382        // importedSubjects contains the list of subjects into the file
383        def importedSubjects = []
384        // assayValuesMap contains the list of data for each assay
385        def assayValuesMap = []
386        def typeList = []
387
388        for (entity in 0..params['entity'].size()-1){
389            if( params['entity'].getAt(entity).equals("Subjects")){
390                for (line in flow.datamatrix){
391                    importedSubjects.add(line.getAt(entity))
392                }
393            }
394            else if (params['entity'].getAt(entity).equals("Not Used")){}
395
396            else {
397                def assayID = []
398
399                assayID.add(flow.headersList.getAt(entity))
400                assayID.add(params['entity'].getAt(entity))
401                matchingAssays.add(assayID)
402
403                def assayValues = []
404                for (line in flow.datamatrix){
405                    assayValues.add(line.getAt(entity))
406                }
407                def assayMap = [:]
408                assayMap[params['entity'].getAt(entity)]= assayValues
409                assayValuesMap.add assayMap
410                typeList.add(params['type'].getAt(entity))
411            }
412        }
413        flow.importedSubjects = importedSubjects
414        flow.assayValuesMap   = assayValuesMap
415        flow.typeList = typeList
416    }
417
418
419    private getSubjects(flow, params){
420
421        def rankedImportedSubjects = []
422        def rankedStudySubjects = []
423        for (importedSubjects in params['importedSubject']){
424            rankedImportedSubjects.add(importedSubjects)
425        }
426        for (studySubjects in params['studySubject']){
427            rankedStudySubjects.add(studySubjects)
428        }
429        flow.rankedImportedSubjects = rankedImportedSubjects
430        flow.rankedStudySubjects = rankedStudySubjects  //simpleAssayMeasurementAssayType/create
431
432
433        // the list of the subjects [importedID, studyID]
434        def matchingSubjects = []
435        def mapToSave = [:]
436
437        // creation of a map to save into the SAM database
438        // exemple data = [subject1 : [time1 : (type1 , 1.2), time2 : 2.3], subject2 : [time1 : 3.4, time2 : 7.4]]
439        for (i in 0..flow.rankedStudySubjects.size()-1){
440            def matchingSubjectstmp = []
441            matchingSubjectstmp.add(flow.rankedStudySubjects.getAt(i))
442            matchingSubjectstmp.add(flow.rankedImportedSubjects.getAt(i))
443            matchingSubjects.add(matchingSubjectstmp)
444
445            def subjectAssaysList = []
446            int pos = 0
447            for (j in flow.assayValuesMap) {
448                def studyAssayMap = [:]
449                j.each { key,value ->
450                    def valuesList = []
451                    valuesList.add(flow.typeList.getAt(pos))
452                    valuesList.add(value.getAt(i))
453                    studyAssayMap[key]= valuesList
454                    pos = pos + 1
455                }
456                subjectAssaysList.add(studyAssayMap)
457            }
458            mapToSave[flow.rankedStudySubjects.getAt(i)]= subjectAssaysList
459        }
460        flow.subjects = matchingSubjects
461        flow.times    = flow.assayValuesMap
462        flow.data     = mapToSave
463    }
464
465
466    /**
467     * This method handles a file being uploaded and storing it in a temporary directory
468     * and returning a workbook
469     *
470     * @param formfilename name used for the file field in the form
471     * @return workbook object reference
472     */
473    private HSSFWorkbook handleUpload() {
474        def tempfile = null // new File("/home/lupin/projects/eclipse/sam/testTjeerdCode.xls")
475        //tempfile.leftShift(pastedData)
476        //def tempfile = new File("/home/ademcan/Desktop/testSAM.xls")
477        POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(tempfile))
478        HSSFWorkbook    wb = new HSSFWorkbook(fs);
479        return wb
480        //return ImporterService.getWorkbook(new FileInputStream(tempfile))
481    }
482
483
484
485
486
487
488    /* Create a Horrible Style Sheet Framework Workbook from tab-delimited string.
489     *
490     * @param  tabbed:  A tab-delimited string representing a table as by copy and paste from 
491     *                  an Excell sheet.
492     * @return HSSFWorkbook containing the same table.
493     *       
494     */
495    private HSSFWorkbook getWorkBook( tabbed ) {
496
497        //tabbed=tabbed.trim()
498        //def wb = new HSSFWorkbook()
499
500        def wb
501        if (tabbed instanceof File){
502            def fs = new POIFSFileSystem(new FileInputStream(tabbed))
503            wb = new HSSFWorkbook(fs)
504        }
505        else {
506            wb = new HSSFWorkbook()
507        }
508
509        def createHelper = wb.getCreationHelper()
510        def sheet = wb.createSheet("sheet")
511
512        def rowIndex = 0
513        tabbed.splitEachLine("\t") { line ->
514            def row = sheet.createRow((short)rowIndex++)
515            def colIndex = 0
516            line.each{ entry ->
517                def cell = row.createCell(colIndex++).setCellValue(entry)
518            }
519        }
520
521        return wb
522    }
523
524
525
526    /* Convenience method for isConsistentWithGSCF()
527     * see there for documentation.
528     *
529     * @param  subject: a GSCF subject.name string
530     * @param  time:    a GSCF time string for the event that belongs to a sample in GSCF
531     * @param  samples: a list of sample related strings as given by the CommunicationManager
532     * @return true, if the pair (subject, time) exists as a sample in GSCF (for the current
533     *         assay)
534     */
535    private boolean sampleExists( subject, time, samples )
536    {
537        for( sample in samples ) {
538            def sampleSubject = sample['subject'].toString()
539            def sampleTime    = sample['startTime'].toString()
540            if( subject.toString()==sampleSubject && time.toString()==sampleTime ) {
541                return true
542            }
543        }
544
545        return false
546    }
547
548
549
550    /**
551     * This method checks whether the data imported and matched by the user is
552     * still consistent with the subject names and sample times found in GSCF.
553     *
554     * We veryfy that:
555     * (1) All subject names (i.e. names in GSCF) with non-null measurement occur in GSCF.
556     * (2) All pairs (time, subject name) selected by the user correspond to a sample existing in GSCF.
557     *
558     * @param  externalAssayID Id of an assay to be tested
559     * @param  subjects:  Map of subjects, mapping GSCF subject names to local names given by the user as column names
560     * @param  ttimes:    Map of times, mapping GSCF times names to local times given by the user as column names
561     * @param  data:      Big data map containing all meassurements. GSCF subject names are mapped to a second-level
562     *                    map M2. M2 maps GSCF times to list-pairs of measurement type and value. Example:
563     *
564     *                    [ subject1 : [ time1 : [type1, 1.2], time2 : [type2, 2.3] ],
565     *                      subject2 : [ time1 : [type1, 3.4], time2 : [type2, 7.4] ]  ]
566     *
567     *                    Here, we ignore the actual values pairs and only look at data like this:
568     *
569     *                    [ subject1 : [ time1 : XXX, time2 : XXX ],
570     *                      subject2 : [ time1 : XXX, time2 : XXX ]  ]
571     *
572     *                    where XXX is ignored.
573     *
574     * @return true if data is still in sync with GSCF
575     */
576    public boolean isConsistentWithGSCF( externalAssayID, subjects, ttimes, data, samples ) {
577 
578        // Loop through all pairs ( GSCF subject names,  Time) selected by the user.
579        // Check whether the corresponding sample exists in GSCF.
580
581        def returnValue = true
582        data.each{ subject, list ->       
583            if( returnValue )  list.each{ pair ->
584                if( returnValue )  pair.keySet().each { time ->
585                    if( !sampleExists( subject, time, samples) ) {
586                        returnValue = false
587                    }
588                }
589            }
590        }                                                      // remark: calling break in a closure does not work
591
592        return returnValue
593    }
594
595
596
597    /** Save user-imported data on SAM. Before saving, check if subjects and times
598     *  given by the user are still consistent with the GSCF. Then save the data.
599     *  Then check for inconsistencies again. If inconsistencies arise at any point
600     *  don't save changes, or even roll back changes made, and return false.
601     *
602     *
603     * @param  externalAssayID Id of an assay to be tested
604     * @param  subjects:  Map of subjects, mapping GSCF subject names to local names given by the user as column names
605     * @param  ttimes:    Map of times, mapping GSCF times names to local times given by the user as column names
606     * @param  data:      Big data map containing all meassurements. GSCF subject names are mapped to a second-level
607     *                    map M2. M2 maps GSCF times to list-pairs of measurement type and value. Example:
608     *
609     *                    [ subject1 : [ time1 : [type1, 1.2], time2 : [type2, 2.3] ],
610     *                      subject2 : [ time1 : [type1, 3.4], time2 : [type2, 7.4] ]  ]
611     *
612     * @return true if data is still in sync with GSCF
613     */
614    private Object saveSAMforSubjectLayout( selectedStudyID, externalAssayID, assayName, subjects, ttimes, data ) {
615        def warnings = saveMeasurements( selectedStudyID, externalAssayID, subjects, ttimes, data )
616        saveAssay( externalAssayID, selectedStudyID, assayName )
617        return warnings
618    }
619
620
621
622    /* convenience method for saveMeasurements()
623     *
624     * There should be error handling added here.
625     *
626     * This method saves measurement data to the database by
627     * creating new instances of domain objects classes.
628     */
629    private String saveMeasurement( assaySamples, subject, time, list, type, value, assay ) {
630
631        def measurementType =
632        SimpleAssayMeasurementType.find( "from SimpleAssayMeasurementType as s where s.name = '${type}'" )
633
634        // TODO: check here whether a measurementType was actually found (and handle '-' )
635        if (!measurementType) {
636            return "Could not find measurement type ${type}"
637        }
638
639        def sample =  assaySamples.find{ it.startTime==time && it.subject==subject && it.assay==assay }
640        if( !sample ) {                 // this is only a work-around; if a sample cannot be found, this is actually an error; gscf should supply it.
641            return    "Could not save Measurement. Sample not found in GSCF for time ${time}, subject ${subject}"
642        }
643
644        // possible error: type mismatch
645        // possible error: sample not found
646
647        def measurementQuery = "from SimpleAssayMeasurement as m where m.assay= ${assay.id} " +
648                                "AND m.type= ${measurementType.id} AND m.sample = ${sample.id} "
649        def measurement = SimpleAssayMeasurementType.find( measurementQuery )
650
651        if(measurement) {
652            measurement.value  = value.toDouble()
653            measurement.sample = sample
654            measurement.type   = measurementType
655            measurement.value  = value.toDouble()
656        }
657        else {
658            measurement = new SimpleAssayMeasurement(
659                startTime:time, value:value, type:measurementType, assay:assay, sample:sample )
660        }
661
662        // save(flush:true) and discard() are neccessary here.
663        // Without, grails tries to serialise the object (measurement) to
664        // store it in the 'persistenceContext' of the flow object.
665        // This in turn leads to an error, as persistent domain objects are
666        // usually not serializable. Grails strange behaviour is documented.
667        // discard() tells hibernate to 'forget' a domain object.
668        measurement.type.discard()
669        measurement.save(flush:true)
670        sample.discard()           // needed at all?
671        sample.save(flush:true)    // needed at all?
672        return null
673    }
674
675
676
677
678    /* convenience method for saveSAM()
679     *
680     * @return  list of SimpleAssayMeasurements that have been saved in SAM. The list is mixed:
681     *          it contains measurements that are new and also measurements that have been modified.
682     *          The modified measurements contain their old values in the transient member oldValue.
683     */
684    private List saveMeasurements( externalStudyID, externalAssayID, subjects, ttimes, data ) {
685
686        def warnings = []
687        def assaySamples = getSamples( externalAssayID )
688        def assay = SimpleAssay.find("from SimpleAssay s where s.externalAssayID = '${externalAssayID}'")
689
690        data.each{ subject, list ->
691            list.each{ pair ->
692                pair.each { time, typeValuePair ->
693                    def type  = typeValuePair[0]
694                    def value = typeValuePair[1]
695                    def warning = saveMeasurement( assaySamples, subject, time, list, type, value, assay )
696                    if(warning) { warnings.add warning }
697                }
698            }
699        }
700
701        return warnings
702    }
703
704
705
706
707    /* convenience method for saveSAM() */
708    private SimpleAssay saveAssay( externalAssayID, externalStudyID, assayName ) {
709        def assay = SimpleAssayMeasurement.find( "from SimpleAssay as s where s.externalAssayID='${externalAssayID}'" )
710        if(!assay) { assay = new SimpleAssay(
711                externalAssayID:externalAssayID,
712                externalStudyID:externalStudyID,
713                isNew:true,
714                name:assayName ) }
715        else       { assay.isNew = false }
716        assay.save(flush:true)
717        return assay
718    }
719
720
721
722    /* convenience method for importer flow)
723    in future, use samples from cache!    */
724    private getSamples( externalAssayID )
725    {
726        def assay = SimpleAssay.find( "from SimpleAssay s where s.externalAssayID = '${externalAssayID}'" )
727        def samples = []
728        for ( i in CommunicationManager.getSamples( session.sessionToken, externalAssayID ) ) {
729            samples.add( getSample(assay,i) )
730        }
731        assay.save(flush:true)
732        return samples.unique()
733    }
734
735
736
737    /** convenience method for get samples
738     *  Get all Samples belonging to map.
739     *  If Sample does no occure in SAM yet, then create a new Sample object in SAM
740     *  based on Sample domain object from GSCF. */
741    private getSample(assay,map)
742    {
743        def subject  = map[  'subject']
744        def time     = map['startTime']
745        def material = map[ 'material']
746        def name     = map[    'event']
747        def externalSampleId = map['name']
748        def query  = "from SimpleAssaySample as s where s.assay= '${assay.id}' and s.subject = '${subject}' AND s.startTime = '${time}' "
749        def sample = SimpleAssaySample.find(query)
750        if(!sample)
751        {
752            sample = new SimpleAssaySample( assay:assay, startTime:time, name:name, material:material,
753                subject:subject, externalSampleId:externalSampleId )
754            sample.save(flush:true)
755        }
756        assay.addToSamples(sample)
757        return sample
758    }
759
760
761
762    /* add to flow scope all data need for building the table view */
763    private buildModel(flow)
764    {
765   
766        def datamatrix = []         // the values containes into the lines
767        def headersList =  []       // the list of all headers in the file to import
768        //def wb = (flow.tabDelimitedString==null) ? handleUpload() : getWorkBook(flow.tabDelimitedString)
769
770        def wb = getWorkBook(flow.tabDelimitedString)
771
772        // getHeader contains information about headers
773        for (header in ImporterService.getHeader(wb, 0)){
774            headersList.add(header.name)
775        }
776        // getDatamatrix contains the data for each line
777        for (line in importerService.getDatamatrix(wb,0,5,1)){
778            def lines = []
779            for (box in line ){
780                lines.add(box.toString())
781            }
782            datamatrix.add(lines)
783        }
784
785        def typeList = data.SimpleAssayMeasurementType.list()
786        def measurementTypes = []
787
788        for(i in typeList){
789            measurementTypes.add(i.name)
790            i.discard()
791        }
792
793        flow.headersList      = headersList
794        flow.datamatrix       = datamatrix
795        flow.measurementTypes = measurementTypes
796    }
797
798
799
800    /**   Make a list of updated and new SimpleAssayMeasurements containing
801     *   the values provided by the user. Store this list in flow.measurements
802     *
803     *   This method fills three flow variables:
804     *
805     *   (1) flow.measurements     list of measurements changed
806     *   (2) flow.usedSamples     samples effected
807     *   (3) flow.usedTypes       types effected
808     *
809     */
810    private addMeasurementsToFlow(flow, params) {
811        def samples = params['samples'] // get data from form
812        def types   = params['types']
813        def cells   = params['cells']
814        def typeObjects = SimpleAssayMeasurementType.findAll()
815        def sampleObjects = flow.samples
816        def measurements = []
817        def assay = SimpleAssay.find("from SimpleAssay as a where a.externalAssayID = '${flow.selectedAssayID}'")
818        flow.usedSamples = []
819        flow.usedTypes   = []
820
821        samples.each{ row,sampleName ->
822            types.each{ col,typeName ->
823                if( !sampleName.equals('') && !typeName.equals('-') ) {
824                    def ck = col+'.' + row
825                    def value = cells[ck].replace(",",".").toDouble()
826                    measurements.add setMeasurement(flow,sampleObjects,typeObjects,sampleName,typeName,value,assay)
827                }
828            }
829        }
830
831        flow.measurements = measurements
832    }
833
834    /**   Convenience method for fillSamples()
835     *   Make a list of updated and new SimpleAssayMeasurements containing
836     *   the values provided by the user.
837     *
838     *   @return updated or new SimpleAssayMeasurements which is transient.
839     *           The values of the new measurment are set according to the arguments given.
840     */
841    private setMeasurement( flow,samples,types,sampleName,typeName,value,assay ) {
842        def sample = samples.find{ it.externalSampleId==sampleName }
843        def type   = types.find{ it.name==typeName}
844
845        if( !flow.usedSamples.contains(sample) ) { flow.usedSamples.add(sample) }
846        if( !flow.usedTypes.contains(type)     ) { flow.usedTypes.add(type)     }
847
848        def query = "from SimpleAssayMeasurement as m where m.type= '${type.id}' AND m.sample= '${sample.id}'"
849        def measurement =
850        SimpleAssayMeasurement.find(query) ?: new SimpleAssayMeasurement()
851        measurement.assay  = assay
852        measurement.sample = sample
853        measurement.type   = type
854        measurement.value  = value
855        return measurement
856    }
857
858    // Quickly add subAssay method
859    // Used for adding subAssays form the assay importer webflow
860    private addSubAssays(params,flow){
861        for (i in 0..params['name'].size()-1){
862            def subAssay = new SimpleAssayMeasurementType(
863                name: params['name'].getAt(i),
864                unit: params['unit'].getAt(i),
865                detectableLimit: params['limit'].getAt(i).toDouble()
866            )
867            subAssay.save(flush:true)
868            flow.measurementTypes.add(subAssay.name)
869        }
870    }
871
872
873    // classes for fuzzy string matching
874    // <FUZZY MATCHING>
875    static def similarity(l_seq, r_seq, degree=2) {
876        def l_histo = countNgramFrequency(l_seq, degree)
877        def r_histo = countNgramFrequency(r_seq, degree)
878
879        dotProduct(l_histo, r_histo) /
880        Math.sqrt(dotProduct(l_histo, l_histo) *
881            dotProduct(r_histo, r_histo))
882    }
883
884    static def countNgramFrequency(sequence, degree) {
885        def histo = [:]
886        def items = sequence.size()
887
888        for (int i = 0; i + degree <= items; i++)
889        {
890            def gram = sequence[i..<(i + degree)]
891            histo[gram] = 1 + histo.get(gram, 0)
892        }
893        histo
894    }
895
896    static def dotProduct(l_histo, r_histo) {
897        def sum = 0
898        l_histo.each { key, value ->
899            sum = sum + l_histo[key] * r_histo.get(key, 0)
900        }
901        sum
902    }
903
904    static def stringSimilarity (l_str, r_str, degree=2) {
905        similarity(l_str.toLowerCase().toCharArray(),
906            r_str.toLowerCase().toCharArray(),
907            degree)
908    }
909
910    static def mostSimilar(pattern, candidates, threshold=0) {
911        def topScore = 0
912        def bestFit = null
913
914        candidates.each { candidate ->
915            def score = stringSimilarity(pattern, candidate)
916            if (score > topScore) {
917                topScore = score
918                bestFit = candidate
919            }
920        }
921
922        if (topScore < threshold)
923        bestFit = null
924
925        bestFit
926    }
927    // </FUZZY MATCHING>
928
929
930
931    def test = {
932        render params
933        render session.sessionToken
934        render '<br>'
935        CommunicationManager.metaClass.getMethods().each {render it.toString() + '<br>'}
936        render '<br>'
937        render '<br>'
938        render '<br>'
939        try {
940            render CommunicationManager.getStudies( session.sessionToken )
941        } catch (Exception e) { println e }
942    }
943
944}
Note: See TracBrowser for help on using the browser.