1 /**
2  * Orthanc - A Lightweight, RESTful DICOM Store
3  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4  * Department, University Hospital of Liege, Belgium
5  * Copyright (C) 2017-2020 Osimis S.A., Belgium
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * In addition, as a special exception, the copyright holders of this
13  * program give permission to link the code of its release with the
14  * OpenSSL project's "OpenSSL" library (or with modified versions of it
15  * that use the same license as the "OpenSSL" library), and distribute
16  * the linked executables. You must obey the GNU General Public License
17  * in all respects for all of the code used other than "OpenSSL". If you
18  * modify file(s) with this exception, you may extend this exception to
19  * your version of the file(s), but you are not obligated to do so. If
20  * you do not wish to do so, delete this exception statement from your
21  * version. If you delete this exception statement from all source files
22  * in the program, then also delete it here.
23  *
24  * This program is distributed in the hope that it will be useful, but
25  * WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27  * General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program. If not, see <http://www.gnu.org/licenses/>.
31  **/
32 
33 
34 #include "../../PrecompiledHeadersServer.h"
35 #include "DatabaseLookup.h"
36 
37 #include "../../../../OrthancFramework/Sources/OrthancException.h"
38 #include "../../Search/DicomTagConstraint.h"
39 #include "../../ServerToolbox.h"
40 #include "SetOfResources.h"
41 
42 namespace Orthanc
43 {
44   namespace Compatibility
45   {
46     namespace
47     {
48       // Anonymous namespace to avoid clashes between compiler modules
49       class MainTagsConstraints : boost::noncopyable
50       {
51       private:
52         std::vector<DicomTagConstraint*>  constraints_;
53 
54       public:
~MainTagsConstraints()55         ~MainTagsConstraints()
56         {
57           for (size_t i = 0; i < constraints_.size(); i++)
58           {
59             assert(constraints_[i] != NULL);
60             delete constraints_[i];
61           }
62         }
63 
Reserve(size_t n)64         void Reserve(size_t n)
65         {
66           constraints_.reserve(n);
67         }
68 
GetSize() const69         size_t GetSize() const
70         {
71           return constraints_.size();
72         }
73 
GetConstraint(size_t i) const74         DicomTagConstraint& GetConstraint(size_t i) const
75         {
76           if (i >= constraints_.size())
77           {
78             throw OrthancException(ErrorCode_ParameterOutOfRange);
79           }
80           else
81           {
82             assert(constraints_[i] != NULL);
83             return *constraints_[i];
84           }
85         }
86 
Add(const DatabaseConstraint & constraint)87         void Add(const DatabaseConstraint& constraint)
88         {
89           constraints_.push_back(new DicomTagConstraint(constraint));
90         }
91       };
92     }
93 
94 
ApplyIdentifierConstraint(SetOfResources & candidates,ILookupResources & compatibility,const DatabaseConstraint & constraint,ResourceType level)95     static void ApplyIdentifierConstraint(SetOfResources& candidates,
96                                           ILookupResources& compatibility,
97                                           const DatabaseConstraint& constraint,
98                                           ResourceType level)
99     {
100       std::list<int64_t> matches;
101 
102       switch (constraint.GetConstraintType())
103       {
104         case ConstraintType_Equal:
105           compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
106                                     IdentifierConstraintType_Equal, constraint.GetSingleValue());
107           break;
108 
109         case ConstraintType_SmallerOrEqual:
110           compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
111                                     IdentifierConstraintType_SmallerOrEqual, constraint.GetSingleValue());
112           break;
113 
114         case ConstraintType_GreaterOrEqual:
115           compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
116                                     IdentifierConstraintType_GreaterOrEqual, constraint.GetSingleValue());
117 
118           break;
119 
120         case ConstraintType_Wildcard:
121           compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
122                                     IdentifierConstraintType_Wildcard, constraint.GetSingleValue());
123 
124           break;
125 
126         case ConstraintType_List:
127           for (size_t i = 0; i < constraint.GetValuesCount(); i++)
128           {
129             std::list<int64_t> tmp;
130             compatibility.LookupIdentifier(tmp, level, constraint.GetTag(),
131                                       IdentifierConstraintType_Wildcard, constraint.GetValue(i));
132             matches.splice(matches.end(), tmp);
133           }
134 
135           break;
136 
137         default:
138           throw OrthancException(ErrorCode_InternalError);
139       }
140 
141       candidates.Intersect(matches);
142     }
143 
144 
ApplyIdentifierRange(SetOfResources & candidates,ILookupResources & compatibility,const DatabaseConstraint & smaller,const DatabaseConstraint & greater,ResourceType level)145     static void ApplyIdentifierRange(SetOfResources& candidates,
146                                      ILookupResources& compatibility,
147                                      const DatabaseConstraint& smaller,
148                                      const DatabaseConstraint& greater,
149                                      ResourceType level)
150     {
151       assert(smaller.GetConstraintType() == ConstraintType_SmallerOrEqual &&
152              greater.GetConstraintType() == ConstraintType_GreaterOrEqual &&
153              smaller.GetTag() == greater.GetTag() &&
154              ServerToolbox::IsIdentifier(smaller.GetTag(), level));
155 
156       std::list<int64_t> matches;
157       compatibility.LookupIdentifierRange(matches, level, smaller.GetTag(),
158                                      greater.GetSingleValue(), smaller.GetSingleValue());
159       candidates.Intersect(matches);
160     }
161 
162 
ApplyLevel(SetOfResources & candidates,IDatabaseWrapper & database,ILookupResources & compatibility,const std::vector<DatabaseConstraint> & lookup,ResourceType level)163     static void ApplyLevel(SetOfResources& candidates,
164                            IDatabaseWrapper& database,
165                            ILookupResources& compatibility,
166                            const std::vector<DatabaseConstraint>& lookup,
167                            ResourceType level)
168     {
169       typedef std::set<const DatabaseConstraint*>  SetOfConstraints;
170       typedef std::map<DicomTag, SetOfConstraints> Identifiers;
171 
172       // (1) Select which constraints apply to this level, and split
173       // them between "identifier tags" constraints and "main DICOM
174       // tags" constraints
175 
176       Identifiers       identifiers;
177       SetOfConstraints  mainTags;
178 
179       for (size_t i = 0; i < lookup.size(); i++)
180       {
181         if (lookup[i].GetLevel() == level)
182         {
183           if (lookup[i].IsIdentifier())
184           {
185             identifiers[lookup[i].GetTag()].insert(&lookup[i]);
186           }
187           else
188           {
189             mainTags.insert(&lookup[i]);
190           }
191         }
192       }
193 
194 
195       // (2) Apply the constraints over the identifiers
196 
197       for (Identifiers::const_iterator it = identifiers.begin();
198            it != identifiers.end(); ++it)
199       {
200         // Check whether some range constraint over identifiers is
201         // present at this level
202         const DatabaseConstraint* smaller = NULL;
203         const DatabaseConstraint* greater = NULL;
204 
205         for (SetOfConstraints::const_iterator it2 = it->second.begin();
206              it2 != it->second.end(); ++it2)
207         {
208           assert(*it2 != NULL);
209 
210           if ((*it2)->GetConstraintType() == ConstraintType_SmallerOrEqual)
211           {
212             smaller = *it2;
213           }
214 
215           if ((*it2)->GetConstraintType() == ConstraintType_GreaterOrEqual)
216           {
217             greater = *it2;
218           }
219         }
220 
221         if (smaller != NULL &&
222             greater != NULL)
223         {
224           // There is a range constraint: Apply it, as it is more efficient
225           ApplyIdentifierRange(candidates, compatibility, *smaller, *greater, level);
226         }
227         else
228         {
229           smaller = NULL;
230           greater = NULL;
231         }
232 
233         for (SetOfConstraints::const_iterator it2 = it->second.begin();
234              it2 != it->second.end(); ++it2)
235         {
236           // Check to avoid applying twice the range constraint
237           if (*it2 != smaller &&
238               *it2 != greater)
239           {
240             ApplyIdentifierConstraint(candidates, compatibility, **it2, level);
241           }
242         }
243       }
244 
245 
246       // (3) Apply the constraints over the main DICOM tags (no index
247       // here, so this is less efficient than filtering over the
248       // identifiers)
249       if (!mainTags.empty())
250       {
251         MainTagsConstraints c;
252         c.Reserve(mainTags.size());
253 
254         for (SetOfConstraints::const_iterator it = mainTags.begin();
255              it != mainTags.end(); ++it)
256         {
257           assert(*it != NULL);
258           c.Add(**it);
259         }
260 
261         std::list<int64_t>  source;
262         candidates.Flatten(compatibility, source);
263         candidates.Clear();
264 
265         std::list<int64_t>  filtered;
266         for (std::list<int64_t>::const_iterator candidate = source.begin();
267              candidate != source.end(); ++candidate)
268         {
269           DicomMap tags;
270           database.GetMainDicomTags(tags, *candidate);
271 
272           bool match = true;
273 
274           for (size_t i = 0; i < c.GetSize(); i++)
275           {
276             if (!c.GetConstraint(i).IsMatch(tags))
277             {
278               match = false;
279               break;
280             }
281           }
282 
283           if (match)
284           {
285             filtered.push_back(*candidate);
286           }
287         }
288 
289         candidates.Intersect(filtered);
290       }
291     }
292 
293 
GetOneInstance(IDatabaseWrapper & compatibility,int64_t resource,ResourceType level)294     static std::string GetOneInstance(IDatabaseWrapper& compatibility,
295                                       int64_t resource,
296                                       ResourceType level)
297     {
298       for (int i = level; i < ResourceType_Instance; i++)
299       {
300         assert(compatibility.GetResourceType(resource) == static_cast<ResourceType>(i));
301 
302         std::list<int64_t> children;
303         compatibility.GetChildrenInternalId(children, resource);
304 
305         if (children.empty())
306         {
307           throw OrthancException(ErrorCode_Database);
308         }
309 
310         resource = children.front();
311       }
312 
313       return compatibility.GetPublicId(resource);
314     }
315 
316 
ApplyLookupResources(std::list<std::string> & resourcesId,std::list<std::string> * instancesId,const std::vector<DatabaseConstraint> & lookup,ResourceType queryLevel,size_t limit)317     void DatabaseLookup::ApplyLookupResources(std::list<std::string>& resourcesId,
318                                               std::list<std::string>* instancesId,
319                                               const std::vector<DatabaseConstraint>& lookup,
320                                               ResourceType queryLevel,
321                                               size_t limit)
322     {
323       // This is a re-implementation of
324       // "../../../Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp"
325 
326       assert(ResourceType_Patient < ResourceType_Study &&
327              ResourceType_Study < ResourceType_Series &&
328              ResourceType_Series < ResourceType_Instance);
329 
330       ResourceType upperLevel = queryLevel;
331       ResourceType lowerLevel = queryLevel;
332 
333       for (size_t i = 0; i < lookup.size(); i++)
334       {
335         ResourceType level = lookup[i].GetLevel();
336 
337         if (level < upperLevel)
338         {
339           upperLevel = level;
340         }
341 
342         if (level > lowerLevel)
343         {
344           lowerLevel = level;
345         }
346       }
347 
348       assert(upperLevel <= queryLevel &&
349              queryLevel <= lowerLevel);
350 
351       SetOfResources candidates(database_, upperLevel);
352 
353       for (int level = upperLevel; level <= lowerLevel; level++)
354       {
355         ApplyLevel(candidates, database_, compatibility_, lookup, static_cast<ResourceType>(level));
356 
357         if (level != lowerLevel)
358         {
359           candidates.GoDown();
360         }
361       }
362 
363       std::list<int64_t> resources;
364       candidates.Flatten(compatibility_, resources);
365 
366       // Climb up, up to queryLevel
367 
368       for (int level = lowerLevel; level > queryLevel; level--)
369       {
370         std::list<int64_t> parents;
371         for (std::list<int64_t>::const_iterator
372                it = resources.begin(); it != resources.end(); ++it)
373         {
374           int64_t parent;
375           if (database_.LookupParent(parent, *it))
376           {
377             parents.push_back(parent);
378           }
379         }
380 
381         resources.swap(parents);
382       }
383 
384       // Apply the limit, if given
385 
386       if (limit != 0 &&
387           resources.size() > limit)
388       {
389         resources.resize(limit);
390       }
391 
392       // Get the public ID of all the selected resources
393 
394       size_t pos = 0;
395 
396       for (std::list<int64_t>::const_iterator
397              it = resources.begin(); it != resources.end(); ++it, pos++)
398       {
399         assert(database_.GetResourceType(*it) == queryLevel);
400 
401         const std::string resource = database_.GetPublicId(*it);
402         resourcesId.push_back(resource);
403 
404         if (instancesId != NULL)
405         {
406           if (queryLevel == ResourceType_Instance)
407           {
408             // The resource is itself the instance
409             instancesId->push_back(resource);
410           }
411           else
412           {
413             // Collect one child instance for each of the selected resources
414             instancesId->push_back(GetOneInstance(database_, *it, queryLevel));
415           }
416         }
417       }
418     }
419   }
420 }
421