1 | package dbnp.query |
---|
2 | |
---|
3 | import dbnp.studycapturing.* |
---|
4 | import nl.grails.plugins.gdt.* |
---|
5 | |
---|
6 | // TODO: Make use of the searchable-plugin possibilities instead of querying the database directly |
---|
7 | |
---|
8 | /** |
---|
9 | * Basic web interface for searching within studies |
---|
10 | * |
---|
11 | * @author Robert Horlings (robert@isdat.nl) |
---|
12 | */ |
---|
13 | class AdvancedQueryController { |
---|
14 | def entitiesToSearchFor = [ 'Study': 'Studies', 'Sample': 'Samples'] |
---|
15 | def index = { |
---|
16 | [entitiesToSearchFor: entitiesToSearchFor, searchableFields: getSearchableFields()] |
---|
17 | } |
---|
18 | |
---|
19 | /** |
---|
20 | * Searches for studies or samples based on the user parameters. |
---|
21 | * |
---|
22 | * @param entity The entity to search for ( 'Study' or 'Sample' ) |
---|
23 | * @param criteria HashMap with the values being hashmaps with field, operator and value. |
---|
24 | * [ 0: [ field: 'Study.name', operator: 'equals', value: 'term' ], 1: [..], .. ] |
---|
25 | */ |
---|
26 | def search = { |
---|
27 | if( !params.criteria ) { |
---|
28 | flash.error = "No criteria given to search for. Please try again."; |
---|
29 | redirect( action: 'index' ) |
---|
30 | } |
---|
31 | |
---|
32 | if( !params.entity || !entitiesToSearchFor*.key.contains( params.entity ) ) { |
---|
33 | flash.error = "No or incorrect entity given to search for. Please try again."; |
---|
34 | redirect( action: 'index', params: [ criteria: parseCriteria( params.criteria ) ] ) |
---|
35 | } |
---|
36 | |
---|
37 | // Create a search object and let it do the searching |
---|
38 | Search search; |
---|
39 | String view; |
---|
40 | switch( params.entity ) { |
---|
41 | case "Study": search = new StudySearch(); view = "studyresults"; break; |
---|
42 | case "Sample": search = new SampleSearch(); view = "sampleresults"; break; |
---|
43 | |
---|
44 | // This exception will only be thrown if the entitiesToSearchFor contains more entities than |
---|
45 | // mentioned in this switch structure. |
---|
46 | default: throw new Exception( "Can't search for entities of type " + params.entity ); |
---|
47 | } |
---|
48 | |
---|
49 | search.execute( parseCriteria( params.criteria ) ); |
---|
50 | |
---|
51 | render( view: view, model: [search: search] ); |
---|
52 | } |
---|
53 | |
---|
54 | /** |
---|
55 | * Returns a map of entities with the names of the fields the user can search on |
---|
56 | * @return |
---|
57 | */ |
---|
58 | protected def getSearchableFields() { |
---|
59 | def fields = [:]; |
---|
60 | |
---|
61 | getEntities().each { |
---|
62 | def entity = getEntity( 'dbnp.studycapturing.' + it ); |
---|
63 | |
---|
64 | if( entity ) { |
---|
65 | def domainFields = entity.giveDomainFields(); |
---|
66 | def templateFields = TemplateField.findAllByEntity( entity ) |
---|
67 | |
---|
68 | def fieldNames = ( domainFields + templateFields ).collect { it.name }.unique() + 'Template' |
---|
69 | |
---|
70 | fields[ it ] = fieldNames.sort { a, b -> a[0].toUpperCase() + a[1..-1] <=> b[0].toUpperCase() + b[1..-1] }; |
---|
71 | } |
---|
72 | } |
---|
73 | |
---|
74 | return fields; |
---|
75 | } |
---|
76 | |
---|
77 | /** |
---|
78 | * Parses the criteria from the query form given by the user |
---|
79 | * @param c Data from the input form and had a form like |
---|
80 | * |
---|
81 | * [ |
---|
82 | * 0: [entityfield:a.b, operator: b, value: c], |
---|
83 | * 0.entityfield: a.b, |
---|
84 | * 0.operator: b, |
---|
85 | * 0.field: c |
---|
86 | * 1: [entityfield:f.q, operator: e, value: d], |
---|
87 | * 1.entityfield: f.q, |
---|
88 | * 1.operator: e, |
---|
89 | * 1.field: d |
---|
90 | * ] |
---|
91 | * |
---|
92 | * @return List with Criterion objects |
---|
93 | */ |
---|
94 | protected List parseCriteria( def c ) { |
---|
95 | ArrayList list = []; |
---|
96 | |
---|
97 | // Loop through all keys of c and remove the non-numeric ones |
---|
98 | c.each { |
---|
99 | if( it.key ==~ /[0-9]+/ ) { |
---|
100 | def formCriterion = it.value; |
---|
101 | Criterion criterion = new Criterion(); |
---|
102 | |
---|
103 | // Split entity and field |
---|
104 | def field = formCriterion.entityfield?.split( /\./ ); |
---|
105 | |
---|
106 | if( field.size() > 1 ) { |
---|
107 | criterion.entity = field[0].toString(); |
---|
108 | criterion.field = field[1].toString(); |
---|
109 | } else { |
---|
110 | criterion.entity = null; |
---|
111 | criterion.field = field; |
---|
112 | } |
---|
113 | |
---|
114 | // Convert operator string to Operator-enum field |
---|
115 | switch( formCriterion.operator ) { |
---|
116 | case ">=": criterion.operator = Operator.gte; break; |
---|
117 | case ">": criterion.operator = Operator.gt; break; |
---|
118 | case "<": criterion.operator = Operator.lte; break; |
---|
119 | case "<=": criterion.operator = Operator.lt; break; |
---|
120 | case "contains": criterion.operator = Operator.contains; break; |
---|
121 | case "equals": criterion.operator = Operator.equals; break; |
---|
122 | } |
---|
123 | |
---|
124 | // Copy value |
---|
125 | criterion.value = formCriterion.value; |
---|
126 | |
---|
127 | list << criterion; |
---|
128 | } |
---|
129 | } |
---|
130 | |
---|
131 | return list; |
---|
132 | } |
---|
133 | |
---|
134 | /** |
---|
135 | * Returns all entities for which criteria can be entered |
---|
136 | * @return |
---|
137 | */ |
---|
138 | protected def getEntities() { |
---|
139 | return [ 'Study', 'Subject', 'Sample', 'Event', 'SamplingEvent', 'Assay' ] |
---|
140 | } |
---|
141 | |
---|
142 | /** |
---|
143 | * Creates an object of the given entity. |
---|
144 | * |
---|
145 | * @return False if the entity is not a subclass of TemplateEntity |
---|
146 | */ |
---|
147 | protected def getEntity( entityName ) { |
---|
148 | // Find the templates |
---|
149 | def entity |
---|
150 | try { |
---|
151 | entity = Class.forName(entityName, true, this.getClass().getClassLoader()) |
---|
152 | |
---|
153 | // succes, is entity an instance of TemplateEntity? |
---|
154 | if (entity.superclass =~ /TemplateEntity$/ || entity.superclass.superclass =~ /TemplateEntity$/) { |
---|
155 | return entity; |
---|
156 | } else { |
---|
157 | return false; |
---|
158 | } |
---|
159 | } catch( ClassNotFoundException e ) { |
---|
160 | log.error "Class " + entityName + " not found: " + e.getMessage() |
---|
161 | return null; |
---|
162 | } |
---|
163 | |
---|
164 | } |
---|
165 | |
---|
166 | } |
---|