Line data Source code
1 : /**
2 : Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPWebResource.h"
24 : #include "SPWebRoot.h"
25 : #include "SPWebHost.h"
26 :
27 : namespace STAPPLER_VERSIONIZED stappler::web {
28 :
29 2425 : Resource::Resource(ResourceType t, const Transaction &a, QueryList &&list)
30 2425 : : _type(t), _transaction(a), _queries(move(list)) {
31 2425 : if (_queries.isDeltaApplicable()) {
32 1025 : _delta = Time::microseconds(_transaction.getDeltaValue(*_queries.getScheme()));
33 : }
34 2425 : }
35 :
36 : SP_COVERAGE_TRIVIAL
37 : Resource::~Resource() { }
38 :
39 1425 : ResourceType Resource::getType() const {
40 1425 : return _type;
41 : }
42 :
43 2725 : const db::Scheme &Resource::getScheme() const { return *_queries.getScheme(); }
44 :
45 25 : Status Resource::getStatus() const { return _status; }
46 :
47 100 : bool Resource::isDeltaApplicable() const {
48 100 : return _queries.isDeltaApplicable();
49 : }
50 :
51 50 : bool Resource::hasDelta() const {
52 50 : if (isDeltaApplicable()) {
53 50 : if (_queries.isView()) {
54 25 : return static_cast<const db::FieldView *>(_queries.getItems().front().field->getSlot())->delta;
55 : } else {
56 25 : return _queries.getScheme()->hasDelta();
57 : }
58 : }
59 0 : return false;
60 : }
61 :
62 50 : void Resource::setQueryDelta(Time d) {
63 50 : _queries.setDelta(d);
64 50 : }
65 :
66 50 : Time Resource::getQueryDelta() const {
67 50 : return _queries.getDelta();
68 : }
69 :
70 3400 : Time Resource::getSourceDelta() const { return _delta; }
71 :
72 600 : void Resource::addExtraResolveField(StringView str) {
73 600 : if (emplace_ordered(_extraResolves, str.pdup())) {
74 575 : _isResolvesUpdated = true;
75 : }
76 600 : }
77 :
78 2425 : void Resource::setUser(User *u) {
79 2425 : _user = u;
80 2425 : }
81 :
82 2100 : void Resource::setFilterData(const Value &val) {
83 2100 : _filterData = val;
84 2100 : }
85 :
86 725 : void Resource::applyQuery(const Value &query) {
87 725 : _queries.apply(query);
88 725 : }
89 :
90 2350 : void Resource::prepare(QueryList::Flags flags) {
91 2350 : if (_isResolvesUpdated) {
92 2350 : _queries.resolve(_extraResolves);
93 2350 : _isResolvesUpdated = false;
94 : }
95 2350 : _queries.addFlag(flags);
96 2350 : }
97 :
98 1850 : const db::QueryList &Resource::getQueries() const {
99 1850 : return _queries;
100 : }
101 :
102 : SP_COVERAGE_TRIVIAL
103 : bool Resource::prepareUpdate() { return false; }
104 :
105 : SP_COVERAGE_TRIVIAL
106 : bool Resource::prepareCreate() { return false; }
107 :
108 : SP_COVERAGE_TRIVIAL
109 : bool Resource::prepareAppend() { return false; }
110 :
111 : SP_COVERAGE_TRIVIAL
112 : bool Resource::removeObject() { return false; }
113 :
114 : SP_COVERAGE_TRIVIAL
115 : Value Resource::updateObject(Value &, Vector<db::InputFile> &) { return Value(); }
116 :
117 : SP_COVERAGE_TRIVIAL
118 : Value Resource::createObject(Value &, Vector<db::InputFile> &) { return Value(); }
119 :
120 : SP_COVERAGE_TRIVIAL
121 : Value Resource::appendObject(Value &) { return Value(); }
122 :
123 : SP_COVERAGE_TRIVIAL
124 : Value Resource::getResultObject() { return Value(); }
125 :
126 : SP_COVERAGE_TRIVIAL
127 : void Resource::resolve(const Scheme &, Value &) { }
128 :
129 450 : db::InputConfig::Require Resource::getInputFlags() const {
130 450 : return db::InputConfig::Require::Data | db::InputConfig::Require::Files;
131 : }
132 :
133 525 : size_t Resource::getMaxRequestSize() const {
134 525 : return getRequestScheme().getMaxRequestSize();
135 : }
136 525 : size_t Resource::getMaxVarSize() const {
137 525 : return getRequestScheme().getMaxVarSize();
138 : }
139 525 : size_t Resource::getMaxFileSize() const {
140 525 : return getRequestScheme().getMaxFileSize();
141 : }
142 :
143 250 : void Resource::encodeFiles(Value &data, Vector<db::InputFile> &files) {
144 325 : for (auto &it : files) {
145 75 : const db::Field * f = getRequestScheme().getField(it.name);
146 75 : if (f && f->isFile()) {
147 25 : if (!data.hasValue(it.name)) {
148 25 : data.setInteger(it.negativeId(), it.name);
149 : }
150 50 : } else if (f && f->getType() == db::Type::Extra) {
151 50 : auto fobj = f->getSlot<db::FieldExtra>();
152 50 : auto cIt = fobj->fields.find("content");
153 50 : auto tIt = fobj->fields.find("type");
154 50 : auto mIt = fobj->fields.find("mtime");
155 50 : if (cIt != fobj->fields.end() && tIt != fobj->fields.end() && tIt->second.getType() == db::Type::Text) {
156 50 : if (cIt->second.getType() == db::Type::Text && !it.isBinary) {
157 : auto patch = Value({
158 50 : pair("content", Value(it.readText())),
159 50 : pair("type", Value(it.type)),
160 125 : });
161 :
162 25 : if (mIt != fobj->fields.end()) {
163 25 : patch.setInteger(Time::now().toMicros(), "mtime");
164 : }
165 25 : data.setValue(move(patch), f->getName().str<Interface>());
166 50 : } else if (cIt->second.getType() == db::Type::Bytes) {
167 : auto patch = Value({
168 50 : pair("content", Value(it.readBytes())),
169 50 : pair("type", Value(it.type)),
170 125 : });
171 :
172 25 : if (mIt != fobj->fields.end()) {
173 25 : patch.setInteger(Time::now().toMicros(), "mtime");
174 : }
175 25 : data.setValue(move(patch), f->getName().str<Interface>());
176 25 : }
177 : }
178 : }
179 : }
180 250 : }
181 :
182 300 : static bool Resource_isIdRequest(const db::QueryFieldResolver &next, ResolveOptions opts, ResolveOptions target) {
183 300 : if (next.getResolves().empty()) {
184 0 : if (auto vec = next.getIncludeVec()) {
185 0 : if (vec->size() == 1 && vec->front().name == "$id") {
186 0 : return true;
187 : }
188 : }
189 : }
190 :
191 300 : if ((opts & ResolveOptions::Ids) != ResolveOptions::None) {
192 0 : if ((opts & target) == ResolveOptions::None) {
193 0 : return true;
194 : }
195 : }
196 300 : return false;
197 : }
198 :
199 50 : void Resource::resolveSet(const QueryFieldResolver &res, int64_t id, const db::Field &field, Value &fobj) {
200 50 : QueryFieldResolver next(res.next(field.getName()));
201 50 : if (next) {
202 50 : auto &fields = next.getResolves();
203 50 : bool idOnly = Resource_isIdRequest(next, ResolveOptions::None, ResolveOptions::Sets);
204 :
205 : auto objs = idOnly
206 50 : ? Worker(*res.getScheme(), _transaction).getField(fobj, field, Set<const Field *>{(const Field *)nullptr})
207 100 : : Worker(*res.getScheme(), _transaction).getField(fobj, field, fields);
208 50 : if (objs.isArray()) {
209 50 : Value arr;
210 50 : for (auto &sit : objs.asArray()) {
211 0 : if (sit.isDictionary()) {
212 0 : auto id = sit.getInteger("__oid");
213 0 : if (idOnly) {
214 0 : arr.addInteger(id);
215 : } else {
216 0 : if (_resolveObjects.insert(id).second == false) {
217 0 : sit.setInteger(id);
218 : }
219 0 : arr.addValue(std::move(sit));
220 : }
221 : }
222 : }
223 50 : fobj = std::move(arr);
224 50 : return;
225 50 : }
226 50 : }
227 0 : fobj = Value();
228 : }
229 :
230 75 : void Resource::resolveObject(const QueryFieldResolver &res, int64_t id, const db::Field &field, Value &fobj) {
231 75 : QueryFieldResolver next(res.next(field.getName()));
232 75 : if (next && _resolveObjects.find(fobj.asInteger()) == _resolveObjects.end()) {
233 75 : auto &fields = next.getResolves();
234 75 : if (!Resource_isIdRequest(next, _resolve, ResolveOptions::Objects)) {
235 75 : Value obj = Worker(*res.getScheme(), _transaction).getField(fobj, field, fields);
236 75 : if (obj.isDictionary()) {
237 75 : auto id = obj.getInteger("__oid");
238 75 : if (_resolveObjects.insert(id).second == false) {
239 0 : fobj.setInteger(id);
240 : } else {
241 75 : fobj.setValue(move(obj));
242 : }
243 75 : return;
244 : }
245 75 : } else {
246 0 : return;
247 : }
248 0 : fobj.setNull();
249 : }
250 : }
251 :
252 25 : void Resource::resolveArray(const QueryFieldResolver &res, int64_t id, const db::Field &field, Value &fobj) {
253 25 : fobj.setValue(Worker(*res.getScheme(), _transaction).getField(fobj, field));
254 25 : }
255 :
256 175 : void Resource::resolveFile(const QueryFieldResolver &res, int64_t id, const db::Field &field, Value &fobj) {
257 175 : QueryFieldResolver next(res.next(field.getName()));
258 175 : if (next) {
259 175 : auto fields = next.getResolves();
260 175 : if (!Resource_isIdRequest(next, _resolve, ResolveOptions::Files)) {
261 175 : Value obj = Worker(*res.getScheme(), _transaction).getField(fobj, field, fields);
262 175 : if (obj.isDictionary()) {
263 175 : fobj.setValue(move(obj));
264 175 : return;
265 : }
266 175 : } else {
267 0 : return;
268 : }
269 175 : }
270 0 : fobj.setNull();
271 : }
272 :
273 1100 : static void Resource_resolveExtra(const db::QueryFieldResolver &res, Value &obj) {
274 1100 : auto &fields = res.getResolves();
275 1100 : auto &fieldData = res.getResolvesData();
276 1100 : auto &dict = obj.asDict();
277 1100 : auto it = dict.begin();
278 4175 : while (it != dict.end()) {
279 3075 : auto f = res.getField(it->first);
280 3075 : if (fieldData.find(it->first) != fieldData.end()) {
281 25 : db::QueryFieldResolver next(res.next(it->first));
282 25 : if (next) {
283 0 : Resource_resolveExtra(next, it->second);
284 : }
285 25 : it ++;
286 3050 : } else if (!f || f->isProtected() || (fields.find(f) == fields.end())) {
287 25 : it = dict.erase(it);
288 3025 : } else if (f->getType() == db::Type::Extra && it->second.isDictionary()) {
289 0 : db::QueryFieldResolver next(res.next(it->first));
290 0 : if (next) {
291 0 : Resource_resolveExtra(next, it->second);
292 : }
293 0 : it ++;
294 : } else {
295 3025 : it ++;
296 : }
297 : }
298 1100 : }
299 :
300 5900 : int64_t Resource::processResolveResult(const QueryFieldResolver &res, const Set<const Field *> &fields, Value &obj) {
301 5900 : int64_t id = 0;
302 5900 : auto &dict = obj.asDict();
303 5900 : auto it = dict.begin();
304 :
305 46475 : while (it != dict.end()) {
306 40575 : if (it->first == "__oid") {
307 5900 : id = it->second.asInteger();
308 5900 : ++ it;
309 6000 : continue;
310 34675 : } else if (it->first == "__delta") {
311 50 : if (res.getMeta() == QueryFieldResolver::Meta::None) {
312 50 : if (it->second.getString("action") == "delete") {
313 0 : it->second.setString("delete");
314 : } else {
315 50 : it = dict.erase(it);
316 50 : continue;
317 : }
318 : } else {
319 0 : auto meta = res.getMeta();
320 0 : if ((meta & QueryFieldResolver::Meta::Action) == QueryFieldResolver::Meta::None) {
321 0 : it->second.erase("action");
322 0 : } else if ((meta & QueryFieldResolver::Meta::Time) == QueryFieldResolver::Meta::None) {
323 0 : it->second.erase("time");
324 : }
325 : }
326 0 : ++ it;
327 0 : continue;
328 34625 : } else if (it->first == "__views") {
329 50 : auto meta = res.getMeta();
330 50 : if ((meta & QueryFieldResolver::Meta::View) == QueryFieldResolver::Meta::None) {
331 50 : it = dict.erase(it);
332 50 : continue;
333 : } else {
334 0 : ++ it;
335 0 : continue;
336 : }
337 34575 : } else if (it->first == "__ts_rank") {
338 0 : ++ it;
339 0 : continue;
340 : }
341 :
342 34575 : auto f = res.getField(it->first);
343 34575 : if (!f || f->isProtected() || (fields.find(f) == fields.end())) {
344 1800 : it = dict.erase(it);
345 32775 : } else if ((f->getType() == db::Type::Extra || f->getType() == db::Type::Data || f->getType() == db::Type::Virtual) && it->second.isDictionary()) {
346 2225 : QueryFieldResolver next(res.next(it->first));
347 2225 : if (next) {
348 1100 : Resource_resolveExtra(next, it->second);
349 : }
350 2225 : it ++;
351 : } else {
352 30550 : it ++;
353 : }
354 : }
355 5900 : return id;
356 : }
357 :
358 5900 : void Resource::resolveResult(const QueryFieldResolver &res, Value &obj, uint16_t depth, uint16_t max) {
359 5900 : auto &searchField = res.getResolves();
360 :
361 5900 : int64_t id = processResolveResult(res, searchField, obj);
362 :
363 5900 : if (res && depth <= max) {
364 5900 : auto & fields = *res.getFields();
365 80325 : for (auto &it : fields) {
366 74425 : const Field &f = it.second;
367 74425 : auto type = f.getType();
368 :
369 74425 : if (f.isSimpleLayout() || searchField.find(&f) == searchField.end()) {
370 74050 : if (type == db::Type::Bytes && f.getTransform() == db::Transform::Uuid) {
371 0 : auto &fobj = obj.getValue(it.first);
372 0 : if (fobj.isBytes()) {
373 0 : fobj.setString(memory::uuid(fobj.getBytes()).str());
374 : }
375 : }
376 74050 : continue;
377 74050 : }
378 :
379 375 : if (!obj.hasValue(it.first) && (type == db::Type::Set || type == db::Type::Array || type == db::Type::View)) {
380 75 : obj.setInteger(id, it.first);
381 : }
382 :
383 375 : auto &fobj = obj.getValue(it.first);
384 375 : if (type == db::Type::Object && fobj.isInteger()) {
385 75 : resolveObject(res, id, f, fobj);
386 300 : } else if ((type == db::Type::Set || type == db::Type::View) && fobj.isInteger()) {
387 50 : resolveSet(res, id, f, fobj);
388 250 : } else if (type == db::Type::Array && fobj.isInteger()) {
389 25 : resolveArray(res, id, f, fobj);
390 225 : } else if ((type == db::Type::File || type == db::Type::Image) && fobj.isInteger()) {
391 175 : resolveFile(res, id, f, fobj);
392 : }
393 : }
394 :
395 80325 : for (auto &it : fields) {
396 74425 : auto &f = it.second;
397 74425 : auto type = f.getType();
398 :
399 500 : if ((type == db::Type::Object && obj.isDictionary(it.first))
400 74925 : || ((type == db::Type::Set || type == db::Type::View) && obj.isArray(it.first))) {
401 75 : QueryFieldResolver next(res.next(it.first));
402 75 : if (next) {
403 75 : if (type == db::Type::Set || type == db::Type::View) {
404 0 : auto &fobj = obj.getValue(it.first);
405 0 : for (auto &sit : fobj.asArray()) {
406 0 : if (sit.isDictionary()) {
407 0 : resolveResult(next, sit, depth + 1, max);
408 : }
409 : }
410 0 : } else {
411 75 : auto &fobj = obj.getValue(it.first);
412 75 : if (fobj.isDictionary()) {
413 75 : resolveResult(next, fobj, depth + 1, max);
414 : }
415 : }
416 : }
417 74350 : } else if (f.isFile() && obj.isDictionary()) {
418 1500 : auto &dict = obj.asDict();
419 1500 : auto f_it = dict.find(it.first);
420 1500 : if (f_it != dict.end() && f_it->second.isNull()) {
421 0 : dict.erase(f_it);
422 : }
423 : }
424 : }
425 0 : } else if (obj.isDictionary()) {
426 0 : auto &dict = obj.asDict();
427 0 : auto it = dict.begin();
428 0 : while (it != dict.end()) {
429 0 : auto f = res.getField(it->first);
430 0 : if (f && f->isFile()) {
431 0 : it = dict.erase(it);
432 : } else {
433 0 : ++ it;
434 : }
435 : }
436 : }
437 5900 : }
438 :
439 5825 : void Resource::resolveResult(const QueryList &l, Value &obj) {
440 5825 : if (_isResolvesUpdated) {
441 175 : _queries.resolve(_extraResolves);
442 175 : _isResolvesUpdated = false;
443 : }
444 5825 : resolveResult(l.getFields(), obj, 0, l.getResolveDepth());
445 5825 : }
446 :
447 1650 : const db::Scheme &Resource::getRequestScheme() const {
448 1650 : return getScheme();
449 : }
450 :
451 2075 : ResourceObject::ResourceObject(const Transaction &a, QueryList &&q)
452 2075 : : Resource(ResourceType::Object, a, move(q)) { }
453 :
454 50 : bool ResourceObject::prepareUpdate() {
455 50 : return true;
456 : }
457 :
458 : SP_COVERAGE_TRIVIAL
459 : bool ResourceObject::prepareCreate() {
460 : _status = HTTP_NOT_IMPLEMENTED;
461 : return false;
462 : }
463 :
464 : SP_COVERAGE_TRIVIAL
465 : bool ResourceObject::prepareAppend() {
466 : _status = HTTP_NOT_IMPLEMENTED;
467 : return false;
468 : }
469 :
470 75 : bool ResourceObject::removeObject() {
471 75 : auto objs = getDatabaseId(_queries);
472 75 : if (objs.empty()) {
473 0 : return false;
474 : }
475 :
476 75 : bool ret = (objs.size() == 1);
477 150 : for (auto &it : objs) {
478 75 : _transaction.perform([&, this] {
479 75 : if (ret) {
480 75 : ret = Worker(getScheme(), _transaction).remove(it);
481 : } else {
482 0 : Worker(getScheme(), _transaction).remove(it);
483 : }
484 75 : return true;
485 : });
486 : }
487 75 : return (objs.size() == 1)?ret:true;
488 75 : }
489 :
490 75 : Value ResourceObject::performUpdate(const Vector<int64_t> &objs, Value &data, Vector<db::InputFile> &files) {
491 75 : Value ret;
492 75 : encodeFiles(data, files);
493 :
494 175 : for (auto &it : data.asDict()) {
495 100 : addExtraResolveField(it.first);
496 : }
497 :
498 150 : for (auto &it : objs) {
499 75 : _transaction.perform([&, this] {
500 75 : ret.addValue(Worker(getScheme(), _transaction).update(it, data));
501 75 : return true;
502 : });
503 : }
504 :
505 150 : return processResultList(_queries, ret);
506 75 : }
507 :
508 50 : Value ResourceObject::updateObject(Value &data, Vector<db::InputFile> &files) {
509 50 : Value ret;
510 50 : if (files.empty() && (!data.isDictionary() || data.empty())) {
511 0 : return Value();
512 : }
513 :
514 : // single-object or mass update
515 50 : auto objs = getDatabaseId(_queries);
516 50 : if (objs.empty()) {
517 0 : return Value();
518 : }
519 :
520 50 : return performUpdate(objs, data, files);
521 50 : }
522 :
523 1225 : Value ResourceObject::getResultObject() {
524 1225 : auto ret = getDatabaseObject();
525 1225 : if (!ret.isArray()) {
526 0 : return Value();
527 : }
528 :
529 1225 : return processResultList(_queries, ret);
530 1225 : }
531 :
532 25 : int64_t ResourceObject::getObjectMtime() {
533 25 : auto tmpQuery = _queries;
534 25 : tmpQuery.clearFlags();
535 25 : tmpQuery.addFlag(QueryList::SimpleGet);
536 25 : auto str = tmpQuery.setQueryAsMtime();
537 25 : if (!str.empty()) {
538 25 : if (auto ret = _transaction.performQueryList(tmpQuery)) {
539 25 : if (ret.isArray()) {
540 25 : return ret.getValue(0).getInteger(str);
541 : } else {
542 0 : return ret.getInteger(str);
543 : }
544 25 : }
545 : }
546 :
547 0 : return 0;
548 25 : }
549 :
550 1700 : Value ResourceObject::processResultList(const QueryList &s, Value &ret) {
551 1700 : if (ret.isArray()) {
552 1700 : auto &arr = ret.asArray();
553 1700 : auto it = arr.begin();
554 7450 : while (it != arr.end()) {
555 5750 : if (it->isInteger()) {
556 450 : if (auto val = Worker(getScheme(), _transaction).get(it->getInteger())) {
557 225 : *it = move(val);
558 225 : }
559 : }
560 :
561 5750 : if (!processResultObject(s, *it)) {
562 0 : it = arr.erase(it);
563 : } else {
564 5750 : it ++;
565 : }
566 : }
567 1700 : return std::move(ret);
568 : }
569 0 : return Value();
570 : }
571 :
572 5825 : bool ResourceObject::processResultObject(const QueryList &s, Value &obj) {
573 5825 : if (obj.isDictionary()) {
574 5825 : resolveResult(s, obj);
575 5825 : return true;
576 : }
577 0 : return false;
578 : }
579 :
580 1225 : Value ResourceObject::getDatabaseObject() {
581 1225 : return _transaction.performQueryList(_queries);
582 : }
583 :
584 125 : Vector<int64_t> ResourceObject::getDatabaseId(const QueryList &q, size_t count) {
585 125 : const Vector<QueryList::Item> &items = q.getItems();
586 125 : count = min(items.size(), count);
587 :
588 125 : return _transaction.performQueryListForIds(q, count);
589 : }
590 :
591 1325 : ResourceReslist::ResourceReslist(const Transaction &a, QueryList &&q)
592 1325 : : ResourceObject(a, move(q)) {
593 1325 : _type = ResourceType::ResourceList;
594 1325 : }
595 :
596 100 : bool ResourceReslist::prepareCreate() {
597 100 : return true;
598 : }
599 :
600 125 : Value ResourceReslist::doCreateObject(Value &data, Vector<db::InputFile> &files, const Value &extra) {
601 125 : if (extra.isDictionary()) {
602 100 : for (auto & it : extra.asDict()) {
603 50 : data.setValue(it.second, it.first);
604 : }
605 : }
606 :
607 125 : if (!files.empty()) {
608 25 : encodeFiles(data, files);
609 : }
610 :
611 625 : for (auto &it : data.asDict()) {
612 500 : addExtraResolveField(it.first);
613 : }
614 :
615 250 : return Worker(getScheme(), _transaction).create(data);
616 : }
617 :
618 100 : Value ResourceReslist::performCreateObject(Value &data, Vector<db::InputFile> &files, const Value &extra) {
619 : // single object
620 100 : if (data.isDictionary() || data.empty()) {
621 75 : Value ret = doCreateObject(data, files, extra);
622 75 : if (processResultObject(_queries, ret)) {
623 75 : return ret;
624 : }
625 100 : } else if (data.isArray()) {
626 25 : Vector<db::InputFile> empty;
627 25 : Value ret;
628 50 : for (auto &obj : data.asArray()) {
629 25 : Value n = doCreateObject(obj, empty, extra);
630 25 : if (n) {
631 25 : ret.addValue(std::move(n));
632 : }
633 25 : }
634 25 : return processResultList(_queries, ret);
635 25 : }
636 :
637 0 : return Value();
638 : }
639 :
640 75 : Value ResourceReslist::createObject(Value &data, Vector<db::InputFile> &file) {
641 150 : return performCreateObject(data, file, Value());
642 : }
643 :
644 600 : ResourceSet::ResourceSet(const Transaction &a, QueryList &&q)
645 600 : : ResourceReslist(a, move(q)) {
646 600 : _type = ResourceType::Set;
647 600 : }
648 :
649 25 : bool ResourceSet::prepareAppend() {
650 25 : return true;
651 : }
652 :
653 25 : Value ResourceSet::createObject(Value &data, Vector<db::InputFile> &file) {
654 : // write object patch
655 25 : Value extra;
656 25 : auto &items = _queries.getItems();
657 25 : auto &item = items.back();
658 25 : if (items.size() > 1 && item.ref) {
659 : // has subqueries, try to calculate origin
660 25 : if (auto id = items.at(items.size() - 2).query.getSingleSelectId()) {
661 25 : extra.setInteger(id, item.ref->getName().str<Interface>());
662 : } else {
663 0 : auto ids = getDatabaseId(_queries, _queries.size() - 1);
664 0 : if (ids.size() == 1) {
665 0 : extra.setInteger(ids.front(), item.ref->getName().str<Interface>());
666 : }
667 0 : }
668 : }
669 25 : if (!item.query.getSelectList().empty()) {
670 : // has select query, try to extract extra data
671 0 : for (auto &it : item.query.getSelectList()) {
672 0 : if (it.compare == db::Comparation::Equal) {
673 0 : extra.setValue(it.value1, it.field);
674 : }
675 : }
676 : }
677 50 : return performCreateObject(data, file, extra);
678 25 : }
679 :
680 25 : Value ResourceSet::appendObject(Value &data) {
681 : // write object patch
682 25 : Value extra;
683 25 : auto &items = _queries.getItems();
684 25 : auto &item = items.back();
685 25 : if (items.size() > 1 && item.ref) {
686 : // has subqueries, try to calculate origin
687 25 : if (auto id = items.at(items.size() - 2).query.getSingleSelectId()) {
688 25 : extra.setInteger(id, item.ref->getName().str<Interface>());
689 : } else {
690 0 : auto ids = getDatabaseId(_queries);
691 0 : if (ids.size() == 1 && ids.front()) {
692 0 : extra.setInteger(ids.front(), item.ref->getName().str<Interface>());
693 : }
694 0 : }
695 : }
696 :
697 25 : if (extra.empty()) {
698 0 : return Value();
699 : }
700 :
701 : // collect object ids from input data
702 25 : Value val;
703 25 : if (data.isDictionary() && data.hasValue(item.ref->getName())) {
704 0 : val = std::move(data.getValue(item.ref->getName()));
705 : } else {
706 25 : val = std::move(data);
707 : }
708 25 : Vector<int64_t> ids;
709 25 : Vector<db::InputFile> empty;
710 25 : if (val.isArray()) {
711 50 : for (auto &it : val.asArray()) {
712 25 : if (it.isDictionary()) {
713 25 : it = doCreateObject(it, empty, extra);
714 25 : if (it.isInteger("__oid")) {
715 25 : it = Value(it.getInteger("__oid"));
716 : }
717 : }
718 25 : auto i = it.asInteger();
719 25 : if (i) {
720 25 : ids.push_back(i);
721 : }
722 : }
723 : } else {
724 0 : if (val.isDictionary()) {
725 0 : val = doCreateObject(val, empty, extra);
726 0 : if (val.isInteger("__oid")) {
727 0 : val = Value(val.getInteger("__oid"));
728 : }
729 : }
730 0 : auto i = val.asInteger();
731 0 : if (i) {
732 0 : ids.push_back(i);
733 : }
734 : }
735 :
736 25 : Vector<db::InputFile> files;
737 25 : return performUpdate(ids, extra, files);
738 25 : }
739 :
740 :
741 175 : ResourceRefSet::ResourceRefSet(const Transaction &a, QueryList &&q)
742 175 : : ResourceSet(a, move(q)), _sourceScheme(_queries.getSourceScheme()), _field(_queries.getField()) {
743 175 : _type = ResourceType::ReferenceSet;
744 175 : }
745 :
746 25 : bool ResourceRefSet::prepareUpdate() {
747 25 : return true;
748 : }
749 75 : bool ResourceRefSet::prepareCreate() {
750 75 : return true;
751 : }
752 25 : bool ResourceRefSet::prepareAppend() {
753 25 : return true;
754 : }
755 25 : bool ResourceRefSet::removeObject() {
756 25 : auto id = getObjectId();
757 25 : if (id == 0) {
758 0 : return false;
759 : }
760 :
761 50 : return _transaction.perform([&, this] () -> bool {
762 25 : Vector<int64_t> objs;
763 25 : if (!isEmptyRequest()) {
764 0 : objs = getDatabaseId(_queries);
765 0 : if (objs.empty()) {
766 0 : return false;
767 : }
768 : }
769 25 : return doCleanup(id, objs);
770 50 : });
771 : }
772 25 : Value ResourceRefSet::updateObject(Value &value, Vector<db::InputFile> &files) {
773 25 : if (value.isDictionary() && value.hasValue(_field->getName()) && (value.isBasicType(_field->getName()) || value.isArray(_field->getName()))) {
774 0 : value = value.getValue(_field->getName());
775 : }
776 25 : if (value.isBasicType() && !value.isNull()) {
777 0 : return doAppendObject(value, true);
778 25 : } else if (value.isArray()) {
779 25 : return doAppendObjects(value, true);
780 : } else {
781 0 : return ResourceSet::updateObject(value, files);
782 : }
783 : }
784 75 : Value ResourceRefSet::createObject(Value &value, Vector<db::InputFile> &files) {
785 75 : encodeFiles(value, files);
786 75 : return appendObject(value);
787 : }
788 100 : Value ResourceRefSet::appendObject(Value &value) {
789 100 : if (value.isBasicType()) {
790 25 : return doAppendObject(value, false);
791 75 : } else if (value.isArray()) {
792 75 : return doAppendObjects(value, false);
793 0 : } else if (value.isDictionary()) {
794 0 : return doAppendObject(value, false);
795 : }
796 0 : return Value();
797 : }
798 :
799 150 : int64_t ResourceRefSet::getObjectId() {
800 150 : if (!_objectId) {
801 150 : auto ids = _transaction.performQueryListForIds(_queries, _queries.getItems().size() - 1);
802 150 : if (!ids.empty()) {
803 150 : _objectId = ids.front();
804 : }
805 150 : }
806 150 : return _objectId;
807 : }
808 :
809 25 : bool ResourceRefSet::isEmptyRequest() {
810 25 : if (_queries.getItems().back().query.empty()) {
811 25 : return true;
812 : }
813 0 : return false;
814 : }
815 :
816 125 : Vector<int64_t> ResourceRefSet::prepareAppendList(int64_t id, const Value &patch, bool cleanup) {
817 125 : Vector<int64_t> ids;
818 125 : if (patch.isArray() && patch.size() > 0) {
819 350 : for (auto &it : patch.asArray()) {
820 225 : Value obj;
821 225 : if (it.isNull() || (it.isDictionary() && !it.hasValue("__oid"))) {
822 150 : obj = Worker(getScheme(), _transaction).create(it);
823 : } else {
824 75 : obj = Worker(getScheme(), _transaction).get(it);
825 : }
826 225 : if (obj) {
827 225 : if (auto pushId = obj.getInteger("__oid")) {
828 225 : ids.push_back(pushId);
829 : }
830 : }
831 225 : }
832 : }
833 :
834 125 : return ids;
835 0 : }
836 :
837 25 : bool ResourceRefSet::doCleanup(int64_t id, const Vector<int64_t> &objs) {
838 25 : if (objs.empty()) {
839 25 : Worker(*_sourceScheme, _transaction).clearField(id, *_field);
840 : } else {
841 0 : Value objsData;
842 0 : for (auto &it : objs) {
843 0 : objsData.addInteger(it);
844 : }
845 0 : Worker(*_sourceScheme, _transaction).clearField(id, *_field, move(objsData));
846 0 : }
847 25 : return true;
848 : }
849 :
850 25 : Value ResourceRefSet::doAppendObject(const Value &val, bool cleanup) {
851 25 : Value arr;
852 25 : arr.addValue(val);
853 50 : return doAppendObjects(arr, cleanup);
854 25 : }
855 :
856 125 : Value ResourceRefSet::doAppendObjects(const Value &val, bool cleanup) {
857 125 : Value ret;
858 125 : _transaction.perform([&, this] { // all or nothing
859 125 : return doAppendObjectsTransaction(ret, val, cleanup);
860 : });
861 :
862 125 : if (!_queries.getFields().getFields()->empty()) {
863 125 : return processResultList(_queries, ret);
864 : }
865 :
866 0 : return ret;
867 125 : }
868 :
869 125 : bool ResourceRefSet::doAppendObjectsTransaction(Value &ret, const Value &val, bool cleanup) {
870 125 : auto id = getObjectId();
871 125 : if (id == 0) {
872 0 : return false;
873 : }
874 :
875 125 : Vector<int64_t> ids = prepareAppendList(id, val, cleanup);
876 125 : if (ids.empty()) {
877 0 : Root::getCurrent()->error("ResourceRefSet", "Empty changeset id list in update/append action", Value({
878 0 : pair("sourceScheme", Value(_sourceScheme->getName())),
879 0 : pair("targetScheme", Value(getScheme().getName())),
880 0 : }));
881 0 : return false;
882 : }
883 :
884 125 : Value patch;
885 350 : for (auto &it : ids) {
886 225 : patch.addInteger(it);
887 : }
888 :
889 125 : if (cleanup) {
890 25 : ret = Worker(*_sourceScheme, _transaction).setField(id, *_field, move(patch));
891 : } else {
892 100 : ret = Worker(*_sourceScheme, _transaction).appendField(id, *_field, move(patch));
893 : }
894 :
895 125 : return !ret.empty();
896 125 : }
897 :
898 350 : ResourceProperty::ResourceProperty(const Transaction &a, QueryList &&q, const Field *prop)
899 350 : : Resource(ResourceType::File, a, move(q)), _field(prop) {
900 350 : _queries.setProperty(prop);
901 350 : }
902 :
903 50 : bool ResourceProperty::removeObject() {
904 : // perform one-line remove
905 100 : return _transaction.perform([&, this] () -> bool {
906 50 : if (auto id = getObjectId()) {
907 100 : if (Worker(getScheme(), _transaction).update(id, Value({ pair(_field->getName().str<Interface>(), Value()) }))) {
908 50 : return true;
909 : }
910 : }
911 0 : _status = HTTP_CONFLICT;
912 0 : return false;
913 100 : });
914 : }
915 :
916 275 : uint64_t ResourceProperty::getObjectId() {
917 275 : auto ids = _transaction.performQueryListForIds(_queries);
918 550 : return ids.empty() ? 0 : ids.front();
919 275 : }
920 :
921 150 : ResourceFile::ResourceFile(const Transaction &a, QueryList &&q, const Field *prop)
922 150 : : ResourceProperty(a, move(q), prop) {
923 150 : _type = ResourceType::File;
924 150 : }
925 :
926 50 : db::InputConfig::Require ResourceFile::getInputFlags() const {
927 50 : return db::InputConfig::Require::Files;
928 : }
929 :
930 25 : bool ResourceFile::prepareUpdate() {
931 25 : return true;
932 : }
933 75 : bool ResourceFile::prepareCreate() {
934 75 : return true;
935 : }
936 75 : Value ResourceFile::updateObject(Value &, Vector<db::InputFile> &f) {
937 75 : if (f.empty()) {
938 0 : _status = HTTP_BAD_REQUEST;
939 0 : return Value();
940 : }
941 :
942 75 : db::InputFile *file = nullptr;
943 75 : for (auto &it : f) {
944 75 : if (it.name == _field->getName() || it.name == "content") {
945 0 : file = ⁢
946 0 : break;
947 75 : } else if (it.name.empty()) {
948 75 : it.name = _field->getName().str<Interface>();
949 75 : file = ⁢
950 75 : break;
951 : }
952 : }
953 :
954 150 : for (auto &it : f) {
955 75 : if (it.name != _field->getName() && &it != file) {
956 0 : it.close();
957 : }
958 : }
959 :
960 75 : if (!file) {
961 0 : _status = HTTP_BAD_REQUEST;
962 0 : return Value();
963 : }
964 :
965 75 : if (file->name != _field->getName()) {
966 0 : file->name = _field->getName().str<Interface>();
967 : }
968 :
969 75 : Value patch;
970 75 : patch.setInteger(file->negativeId(), _field->getName().str<Interface>());
971 :
972 : // perform one-line update
973 75 : if (auto id = getObjectId()) {
974 75 : auto ret = Worker(getScheme(), _transaction).update(id, patch);
975 75 : ret = getFileForObject(ret);
976 75 : return ret;
977 75 : }
978 0 : return Value();
979 75 : }
980 50 : Value ResourceFile::createObject(Value &val, Vector<db::InputFile> &f) {
981 : // same as update
982 50 : return updateObject(val, f);
983 : }
984 :
985 25 : Value ResourceFile::getResultObject() {
986 25 : if (_field->hasFlag(db::Flags::Protected)) {
987 0 : _status = HTTP_NOT_FOUND;
988 0 : return Value();
989 : }
990 25 : return getDatabaseObject();
991 : }
992 :
993 75 : Value ResourceFile::getFileForObject(Value &object) {
994 75 : if (object.isDictionary()) {
995 75 : auto id = object.getInteger(_field->getName());
996 75 : if (id) {
997 75 : auto fileScheme = Host::getCurrent().getFileScheme();
998 75 : Value ret(Worker(*fileScheme, _transaction).get(id));
999 75 : return ret;
1000 75 : }
1001 : }
1002 0 : return Value();
1003 : }
1004 :
1005 25 : Value ResourceFile::getDatabaseObject() {
1006 25 : return _transaction.performQueryListField(_queries, *_field);
1007 : }
1008 :
1009 200 : ResourceArray::ResourceArray(const Transaction &a, QueryList &&q, const Field *prop)
1010 200 : : ResourceProperty(a, move(q), prop) {
1011 200 : _type = ResourceType::Array;
1012 200 : }
1013 :
1014 50 : bool ResourceArray::prepareUpdate() {
1015 50 : return true;
1016 : }
1017 75 : bool ResourceArray::prepareCreate() {
1018 75 : return true;
1019 : }
1020 25 : bool ResourceArray::prepareAppend() {
1021 25 : return true;
1022 : }
1023 :
1024 150 : static Value ResourceArray_extract(Value &data, StringView fieldName) {
1025 150 : Value arr;
1026 150 : if (data.isArray()) {
1027 50 : arr = std::move(data);
1028 : } else {
1029 100 : arr.addValue(move(data));
1030 : }
1031 150 : return arr;
1032 0 : }
1033 :
1034 50 : Value ResourceArray::updateObject(Value &data, Vector<db::InputFile> &) {
1035 50 : Value arr = ResourceArray_extract(data, _field->getName());
1036 :
1037 50 : if (!arr.isArray()) {
1038 0 : _status = HTTP_BAD_REQUEST;
1039 0 : return Value();
1040 : }
1041 :
1042 : // perform one-line update
1043 50 : if (auto id = getObjectId()) {
1044 100 : return Worker(getScheme(), _transaction).setField(id, *_field, std::move(arr));
1045 : }
1046 0 : return Value();
1047 50 : }
1048 100 : Value ResourceArray::createObject(Value &data, Vector<db::InputFile> &) {
1049 100 : Value arr = ResourceArray_extract(data, _field->getName());
1050 :
1051 100 : if (!arr.isArray()) {
1052 0 : _status = HTTP_BAD_REQUEST;
1053 0 : return Value(false);
1054 : }
1055 :
1056 : // perform one-line update
1057 100 : if (auto id = getObjectId()) {
1058 200 : return Worker(getScheme(), _transaction).appendField(id, *_field, move(arr));
1059 : }
1060 0 : return Value();
1061 100 : }
1062 :
1063 25 : Value ResourceArray::appendObject(Value &data) {
1064 25 : Vector<db::InputFile> empty;
1065 50 : return createObject(data, empty);
1066 25 : }
1067 :
1068 25 : Value ResourceArray::getResultObject() {
1069 25 : if (_field->hasFlag(db::Flags::Protected)) {
1070 0 : _status = HTTP_NOT_FOUND;
1071 0 : return Value();
1072 : }
1073 25 : return getDatabaseObject();
1074 : }
1075 :
1076 25 : Value ResourceArray::getDatabaseObject() {
1077 25 : return _transaction.performQueryListField(_queries, *_field);
1078 : }
1079 :
1080 150 : ResourceFieldObject::ResourceFieldObject(const Transaction &a, QueryList &&q)
1081 150 : : ResourceObject(a, move(q)), _sourceScheme(_queries.getSourceScheme()), _field(_queries.getField()) {
1082 150 : _type = ResourceType::ObjectField;
1083 150 : }
1084 :
1085 25 : bool ResourceFieldObject::prepareUpdate() {
1086 25 : return true;
1087 : }
1088 :
1089 50 : bool ResourceFieldObject::prepareCreate() {
1090 50 : return true;
1091 : }
1092 :
1093 : SP_COVERAGE_TRIVIAL
1094 : bool ResourceFieldObject::prepareAppend() {
1095 : return false;
1096 : }
1097 :
1098 25 : bool ResourceFieldObject::removeObject() {
1099 25 : auto id = getObjectId();
1100 25 : if (id == 0) {
1101 0 : return false;
1102 : }
1103 :
1104 50 : return _transaction.perform([&, this] () -> bool {
1105 25 : return doRemoveObject();
1106 25 : });
1107 : }
1108 :
1109 25 : Value ResourceFieldObject::updateObject(Value &val, Vector<db::InputFile> &files) {
1110 : // create or update object
1111 25 : Value ret;
1112 25 : _transaction.perform([&, this] () -> bool {
1113 25 : if (getObjectId()) {
1114 25 : ret = doUpdateObject(val, files);
1115 : } else {
1116 0 : ret = doCreateObject(val, files);
1117 : }
1118 25 : if (ret) {
1119 25 : return true;
1120 : }
1121 0 : return false;
1122 : });
1123 25 : return ret;
1124 0 : }
1125 :
1126 50 : Value ResourceFieldObject::createObject(Value &val, Vector<db::InputFile> &files) {
1127 : // remove then recreate object
1128 50 : Value ret;
1129 50 : _transaction.perform([&, this] () -> bool {
1130 50 : if (getObjectId()) {
1131 25 : if (!doRemoveObject()) {
1132 0 : return false;
1133 : }
1134 : }
1135 50 : ret = doCreateObject(val, files);
1136 50 : if (ret) {
1137 50 : return true;
1138 : }
1139 0 : return false;
1140 : });
1141 50 : return ret;
1142 0 : }
1143 :
1144 : SP_COVERAGE_TRIVIAL
1145 : Value ResourceFieldObject::appendObject(Value &) {
1146 : return Value();
1147 : }
1148 :
1149 200 : int64_t ResourceFieldObject::getRootId() {
1150 200 : if (!_rootId) {
1151 100 : auto ids = _transaction.performQueryListForIds(_queries, _queries.getItems().size() - 1);
1152 100 : if (!ids.empty()) {
1153 100 : _rootId = ids.front();
1154 : }
1155 100 : }
1156 200 : return _rootId;
1157 : }
1158 :
1159 125 : int64_t ResourceFieldObject::getObjectId() {
1160 125 : if (!_objectId) {
1161 100 : if (auto id = getRootId()) {
1162 100 : if (auto obj = Worker(*_field->getSlot()->owner, _transaction).get(id, {_field->getName()})) {
1163 100 : _objectId = obj.getInteger(_field->getName());
1164 100 : }
1165 : }
1166 : }
1167 125 : return _objectId;
1168 : }
1169 :
1170 50 : bool ResourceFieldObject::doRemoveObject() {
1171 50 : return Worker(*_sourceScheme, _transaction).clearField(getRootId(), *_field);
1172 : }
1173 :
1174 25 : Value ResourceFieldObject::doUpdateObject(Value &val, Vector<db::InputFile> &files) {
1175 25 : encodeFiles(val, files);
1176 50 : return Worker(getScheme(), _transaction).update(getObjectId(), val);
1177 : }
1178 :
1179 50 : Value ResourceFieldObject::doCreateObject(Value &val, Vector<db::InputFile> &files) {
1180 50 : encodeFiles(val, files);
1181 100 : if (auto ret = Worker(getScheme(), _transaction).create(val)) {
1182 50 : if (auto id = ret.getInteger("__oid")) {
1183 150 : auto updObj = Worker(*_sourceScheme, _transaction).update(getRootId(), Value({
1184 100 : pair(_field->getName().str<Interface>(), Value(id))
1185 150 : }));
1186 50 : if (updObj) {
1187 50 : return ret;
1188 : }
1189 50 : }
1190 50 : }
1191 0 : return Value();
1192 : }
1193 :
1194 :
1195 75 : ResourceView::ResourceView(const Transaction &h, QueryList &&q)
1196 75 : : ResourceSet(h, move(q)), _field(_queries.getField()) {
1197 75 : if (_queries.isDeltaApplicable()) {
1198 50 : auto tag = _queries.getItems().front().query.getSingleSelectId();
1199 50 : _delta = Time::microseconds(_transaction.getDeltaValue(*_queries.getSourceScheme(), *static_cast<const db::FieldView *>(_field->getSlot()), tag));
1200 : }
1201 75 : }
1202 :
1203 : SP_COVERAGE_TRIVIAL
1204 : bool ResourceView::prepareUpdate() { return false; }
1205 :
1206 : SP_COVERAGE_TRIVIAL
1207 : bool ResourceView::prepareCreate() { return false; }
1208 :
1209 : SP_COVERAGE_TRIVIAL
1210 : bool ResourceView::prepareAppend() { return false; }
1211 :
1212 : SP_COVERAGE_TRIVIAL
1213 : bool ResourceView::removeObject() { return false; }
1214 :
1215 : SP_COVERAGE_TRIVIAL
1216 : Value ResourceView::updateObject(Value &data, Vector<db::InputFile> &) { return Value(); }
1217 :
1218 : SP_COVERAGE_TRIVIAL
1219 : Value ResourceView::createObject(Value &data, Vector<db::InputFile> &) { return Value(); }
1220 :
1221 75 : Value ResourceView::getResultObject() {
1222 75 : auto ret = _transaction.performQueryListField(_queries, *_field);
1223 75 : if (!ret.isArray()) {
1224 50 : return Value();
1225 : }
1226 :
1227 25 : return processResultList(_queries, ret);
1228 75 : }
1229 :
1230 225 : ResourceSearch::ResourceSearch(const Transaction &a, QueryList &&q, const Field *prop)
1231 225 : : ResourceObject(a, move(q)), _field(prop) {
1232 225 : _type = ResourceType::Search;
1233 225 : }
1234 :
1235 225 : Value ResourceSearch::getResultObject() {
1236 225 : auto slot = _field->getSlot<db::FieldFullTextView>();
1237 225 : if (auto &searchData = _queries.getExtraData().getValue("search")) {
1238 225 : auto q = slot->parseQuery(searchData);
1239 :
1240 225 : if (!q.empty()) {
1241 225 : _queries.setFullTextQuery(_field, db::FullTextQuery(q));
1242 225 : auto ret = _transaction.performQueryList(_queries);
1243 225 : if (!ret.isArray()) {
1244 0 : return Value();
1245 : }
1246 :
1247 225 : auto res = processResultList(_queries, ret);
1248 225 : if (!res.empty()) {
1249 225 : if (auto &headlines = _queries.getExtraData().getValue("headlines")) {
1250 175 : auto ql = slot->searchConfiguration->stemQuery(q);
1251 350 : for (auto &it : res.asArray()) {
1252 175 : makeHeadlines(it, headlines, ql);
1253 : }
1254 175 : }
1255 : }
1256 225 : return res;
1257 225 : }
1258 225 : }
1259 0 : return Value();
1260 : }
1261 :
1262 175 : void ResourceSearch::makeHeadlines(Value &obj, const Value &headlineInfo, const Vector<String> &ql) {
1263 175 : auto &h = obj.emplace("__headlines");
1264 350 : for (auto &it : headlineInfo.asDict()) {
1265 175 : auto d = getObjectLine(obj, it.first);
1266 175 : if (d && d->isString()) {
1267 175 : auto headStr = makeHeadline(d->getString(), it.second, ql);
1268 175 : if (!headStr.empty()) {
1269 175 : h.setString(headStr, it.first);
1270 : }
1271 175 : }
1272 : }
1273 175 : }
1274 :
1275 175 : String ResourceSearch::makeHeadline(const StringView &value, const Value &headlineInfo, const Vector<String> &ql) {
1276 175 : auto slot = _field->getSlot<db::FieldFullTextView>();
1277 175 : stappler::search::Configuration::HeadlineConfig cfg;
1278 175 : if (headlineInfo.isString()) {
1279 75 : if (headlineInfo.getString() == "plain") {
1280 50 : cfg.startToken = StringView("<b>"); cfg.stopToken = StringView("</b>");
1281 50 : return slot->searchConfiguration->makeHeadline(cfg, value, ql);
1282 25 : } else if (headlineInfo.getString() == "html") {
1283 25 : cfg.startToken = StringView("<b>"); cfg.stopToken = StringView("</b>");
1284 25 : cfg.startFragment = StringView("<p>"); cfg.stopFragment = StringView("</p>");
1285 25 : return slot->searchConfiguration->makeHtmlHeadlines(cfg, value, ql);
1286 : }
1287 100 : } else if (headlineInfo.isDictionary()) {
1288 100 : auto type = headlineInfo.getString("type");
1289 100 : auto start = headlineInfo.getString("start");
1290 100 : auto end = headlineInfo.getString("end");
1291 :
1292 100 : if (type == "html") {
1293 50 : if (!start.empty() && start.size() < 24 && !end.empty() && end.size() < 24) {
1294 25 : cfg.startToken = start; cfg.stopToken = end;
1295 : } else {
1296 25 : cfg.startToken = StringView("<b>"); cfg.stopToken = StringView("</b>");
1297 : }
1298 :
1299 50 : auto fragStart = headlineInfo.getString("fragStart");
1300 50 : auto fragEnd = headlineInfo.getString("fragStop");
1301 50 : if (!fragStart.empty() && fragStart.size() < 24 && !fragEnd.empty() && fragEnd.size() < 24) {
1302 25 : cfg.startFragment = fragStart; cfg.stopFragment = fragEnd;
1303 : } else {
1304 25 : cfg.startFragment = StringView("<p>"); cfg.stopFragment = StringView("</p>");
1305 : }
1306 :
1307 50 : cfg.maxWords = headlineInfo.getInteger("maxWords", search::Configuration::HeadlineConfig::DefaultMaxWords);
1308 50 : cfg.minWords = headlineInfo.getInteger("minWords", search::Configuration::HeadlineConfig::DefaultMinWords);
1309 50 : cfg.shortWord = headlineInfo.getInteger("shortWord", search::Configuration::HeadlineConfig::DefaultShortWord);
1310 :
1311 50 : size_t frags = 1;
1312 50 : auto fragments = headlineInfo.getValue("fragments");
1313 50 : if (fragments.isString() && fragments.getString() == "max") {
1314 0 : frags = maxOf<size_t>();
1315 50 : } else if (auto f = fragments.asInteger()) {
1316 25 : frags = f;
1317 : }
1318 :
1319 50 : return slot->searchConfiguration->makeHtmlHeadlines(cfg, value, ql, frags);
1320 50 : } else {
1321 50 : if (!start.empty() && start.size() < 24 && !end.empty() && end.size() < 24) {
1322 25 : cfg.startToken = start; cfg.stopToken = end;
1323 : } else {
1324 25 : cfg.startToken = StringView("<b>"); cfg.stopToken = StringView("</b>");
1325 : }
1326 :
1327 50 : return slot->searchConfiguration->makeHeadline(cfg, value, ql);
1328 : }
1329 100 : }
1330 0 : return String();
1331 175 : }
1332 :
1333 175 : const Value *ResourceSearch::getObjectLine(const Value &obj, const StringView &key) {
1334 175 : const Value *ptr = &obj;
1335 175 : key.split<StringView::Chars<'.'>>([&] (const StringView &k) {
1336 350 : if (ptr && ptr->isDictionary()) {
1337 350 : if (auto &v = ptr->getValue(k)) {
1338 350 : ptr = &v;
1339 : }
1340 : } else {
1341 0 : ptr = nullptr;
1342 : }
1343 350 : });
1344 175 : return ptr;
1345 : }
1346 :
1347 : }
|