1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package eu.ehri.project.graphql;
21
22 import com.google.common.base.Preconditions;
23 import com.google.common.collect.ImmutableList;
24 import com.google.common.collect.Iterables;
25 import com.google.common.collect.Lists;
26 import com.google.common.collect.Maps;
27 import com.google.common.collect.Sets;
28 import eu.ehri.project.acl.AclManager;
29 import eu.ehri.project.api.Api;
30 import eu.ehri.project.api.EventsApi;
31 import eu.ehri.project.api.QueryApi;
32 import eu.ehri.project.definitions.ContactInfo;
33 import eu.ehri.project.definitions.CountryInfo;
34 import eu.ehri.project.definitions.DefinitionList;
35 import eu.ehri.project.definitions.Entities;
36 import eu.ehri.project.definitions.EventTypes;
37 import eu.ehri.project.definitions.Geo;
38 import eu.ehri.project.definitions.Isaar;
39 import eu.ehri.project.definitions.IsadG;
40 import eu.ehri.project.definitions.Isdiah;
41 import eu.ehri.project.definitions.Ontology;
42 import eu.ehri.project.definitions.Skos;
43 import eu.ehri.project.definitions.SkosMultilingual;
44 import eu.ehri.project.exceptions.ItemNotFound;
45 import eu.ehri.project.models.AccessPointType;
46 import eu.ehri.project.models.Annotation;
47 import eu.ehri.project.models.Country;
48 import eu.ehri.project.models.DocumentaryUnit;
49 import eu.ehri.project.models.EntityClass;
50 import eu.ehri.project.models.Link;
51 import eu.ehri.project.models.Repository;
52 import eu.ehri.project.models.RepositoryDescription;
53 import eu.ehri.project.models.annotations.EntityType;
54 import eu.ehri.project.models.base.Accessible;
55 import eu.ehri.project.models.base.Annotatable;
56 import eu.ehri.project.models.base.Described;
57 import eu.ehri.project.models.base.Description;
58 import eu.ehri.project.models.base.Entity;
59 import eu.ehri.project.models.base.Linkable;
60 import eu.ehri.project.models.base.Named;
61 import eu.ehri.project.models.base.Temporal;
62 import eu.ehri.project.models.cvoc.AuthoritativeSet;
63 import eu.ehri.project.models.cvoc.Concept;
64 import eu.ehri.project.models.cvoc.Vocabulary;
65 import eu.ehri.project.models.events.SystemEvent;
66 import eu.ehri.project.persistence.Bundle;
67 import eu.ehri.project.utils.LanguageHelpers;
68 import graphql.TypeResolutionEnvironment;
69 import graphql.schema.DataFetcher;
70 import graphql.schema.GraphQLArgument;
71 import graphql.schema.GraphQLEnumType;
72 import graphql.schema.GraphQLFieldDefinition;
73 import graphql.schema.GraphQLInterfaceType;
74 import graphql.schema.GraphQLList;
75 import graphql.schema.GraphQLNonNull;
76 import graphql.schema.GraphQLObjectType;
77 import graphql.schema.GraphQLOutputType;
78 import graphql.schema.GraphQLScalarType;
79 import graphql.schema.GraphQLSchema;
80 import graphql.schema.GraphQLTypeReference;
81 import graphql.schema.TypeResolver;
82 import org.apache.commons.codec.binary.Base64;
83 import org.slf4j.Logger;
84 import org.slf4j.LoggerFactory;
85
86 import java.text.MessageFormat;
87 import java.util.Collections;
88 import java.util.List;
89 import java.util.Map;
90 import java.util.MissingResourceException;
91 import java.util.Objects;
92 import java.util.Optional;
93 import java.util.ResourceBundle;
94 import java.util.concurrent.atomic.AtomicInteger;
95 import java.util.function.Function;
96 import java.util.function.Supplier;
97 import java.util.stream.Collectors;
98 import java.util.stream.StreamSupport;
99
100 import static graphql.Scalars.*;
101 import static graphql.schema.GraphQLArgument.newArgument;
102 import static graphql.schema.GraphQLEnumType.newEnum;
103 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
104 import static graphql.schema.GraphQLInterfaceType.newInterface;
105 import static graphql.schema.GraphQLObjectType.newObject;
106
107
108
109
110 public class GraphQLImpl {
111
112 private static final ResourceBundle bundle = ResourceBundle.getBundle("eu.ehri.project.graphql.messages");
113 private static final Logger logger = LoggerFactory.getLogger(GraphQLImpl.class);
114
115 private static final String SLICE_PARAM = "at";
116 private static final String FIRST_PARAM = "first";
117 private static final String FROM_PARAM = "from";
118 private static final String AFTER_PARAM = "after";
119 private static final String ALL_PARAM = "all";
120
121 private static final String HAS_PREVIOUS_PAGE = "hasPreviousPage";
122 private static final String HAS_NEXT_PAGE = "hasNextPage";
123 private static final String PAGE_INFO = "pageInfo";
124 private static final String ITEMS = "items";
125 private static final String EDGES = "edges";
126 private static final String NODE = "node";
127 private static final String CURSOR = "cursor";
128 private static final String NEXT_PAGE = "nextPage";
129 private static final String PREVIOUS_PAGE = "previousPage";
130
131 private static final int DEFAULT_LIST_LIMIT = 40;
132 private static final int MAX_LIST_LIMIT = 100;
133
134 private static final List<EntityClass> supportedTypes = ImmutableList.of(
135 EntityClass.DOCUMENTARY_UNIT,
136 EntityClass.REPOSITORY,
137 EntityClass.COUNTRY,
138 EntityClass.HISTORICAL_AGENT,
139 EntityClass.CVOC_CONCEPT,
140 EntityClass.CVOC_VOCABULARY,
141 EntityClass.AUTHORITATIVE_SET,
142 EntityClass.ANNOTATION,
143 EntityClass.LINK
144 );
145
146 private static final List<EventTypes> supportedEvents = ImmutableList.of(
147 EventTypes.creation,
148 EventTypes.modification,
149 EventTypes.deletion,
150 EventTypes.ingest,
151 EventTypes.annotation
152 );
153
154 private final Api _api;
155 private final boolean stream;
156
157 public GraphQLImpl(Api api, boolean stream) {
158 this._api = api;
159 this.stream = stream;
160 }
161
162 public GraphQLImpl(Api api) {
163 this(api, false);
164 }
165
166 public GraphQLSchema getSchema() {
167 return GraphQLSchema.newSchema()
168 .query(queryType())
169
170
171
172 .additionalTypes(Sets.newHashSet(datePeriodType, systemEventType))
173 .build();
174 }
175
176 private Api api() {
177 return _api;
178 }
179
180 private EventsApi events() {
181 return api().events()
182 .withEntityClasses(supportedTypes.toArray(new EntityClass[supportedTypes.size()]))
183 .withEventTypes(supportedEvents.toArray(new EventTypes[supportedEvents.size()]));
184 }
185
186 private static String __(String key) {
187 try {
188 return bundle.getString(key);
189 } catch (MissingResourceException e) {
190 logger.error("Missing resource for i18n key: {}", key);
191 return '!' + key + '!';
192 }
193
194 }
195
196 private static String toBase64(String s) {
197 return Base64.encodeBase64String(s.getBytes());
198 }
199
200 private static String fromBase64(String s) {
201 return new String(Base64.decodeBase64(s));
202 }
203
204
205 private static Map<String, Object> mapOf(Object... items) {
206 Preconditions.checkArgument(items.length % 2 == 0, "Items must be pairs of key/value");
207 Map<String, Object> map = Maps.newHashMap();
208 for (int i = 0; i < items.length; i += 2) {
209 map.put(((String) items[i]), items[i + 1]);
210 }
211 return map;
212 }
213
214 private static int decodeCursor(String cursor, int defaultVal) {
215 try {
216 return cursor != null
217 ? Math.max(-1, Integer.parseInt(fromBase64(cursor)))
218 : defaultVal;
219 } catch (NumberFormatException e) {
220 return defaultVal;
221 }
222 }
223
224 private static int getLimit(Integer limitArg, boolean stream) {
225 if (limitArg == null) {
226 return stream ? -1 : DEFAULT_LIST_LIMIT;
227 } else if (limitArg < 0) {
228 return stream ? limitArg : MAX_LIST_LIMIT;
229 } else {
230 return stream ? limitArg : Math.min(MAX_LIST_LIMIT, limitArg);
231 }
232 }
233
234 private static int getOffset(String afterCursor, String fromCursor) {
235 if (afterCursor != null) {
236 return decodeCursor(afterCursor, -1) + 1;
237 } else if (fromCursor != null) {
238 return decodeCursor(fromCursor, 0);
239 } else {
240 return 0;
241 }
242 }
243
244
245
246 private static final GraphQLList GraphQLStringList = new GraphQLList(GraphQLString);
247 private static final GraphQLNonNull GraphQLNonNullString = new GraphQLNonNull(GraphQLString);
248
249 private static final GraphQLScalarType CursorType =
250 new GraphQLScalarType("Cursor", __("cursor.description"), GraphQLString.getCoercing());
251
252 private static final GraphQLArgument idArgument = newArgument()
253 .name(Bundle.ID_KEY)
254 .description(__("graphql.argument.id.description"))
255 .type(new GraphQLNonNull(GraphQLID))
256 .build();
257
258
259
260 private DataFetcher<Iterable<SystemEvent>> itemEventsDataFetcher() {
261 return env -> events().listForItem(env.<Entity>getSource().as(SystemEvent.class));
262 }
263
264 private DataFetcher<Map<String, Object>> topLevelDocDataFetcher() {
265 return connectionDataFetcher(() -> {
266 Iterable<Country> countries = api().query()
267 .setStream(true).setLimit(-1).page(EntityClass.COUNTRY, Country.class);
268 Iterable<Iterable<Entity>> docs = Iterables.transform(countries, c ->
269 Iterables.transform(c.getTopLevelDocumentaryUnits(), d -> d.as(Entity.class)));
270 return Iterables.concat(docs);
271 });
272 }
273
274 private DataFetcher<Map<String, Object>> entityTypeConnectionDataFetcher(EntityClass type) {
275
276
277
278
279
280
281 return connectionDataFetcher(() -> api().query()
282 .setStream(true).setLimit(-1).page(type, Entity.class));
283 }
284
285 private DataFetcher<Map<String, Object>> hierarchicalOneToManyRelationshipConnectionFetcher(
286 Function<Entity, Iterable<? extends Entity>> top, Function<Entity, Iterable<? extends Entity>> all) {
287
288
289 return env -> {
290 boolean allOrTop = (Boolean) Optional.ofNullable(env.getArgument(ALL_PARAM)).orElse(false);
291 Function<Entity, Iterable<? extends Entity>> func = allOrTop ? all : top;
292 return connectionDataFetcher(() -> func.apply((env.getSource()))).get(env);
293 };
294 }
295
296 private DataFetcher<Map<String, Object>> oneToManyRelationshipConnectionFetcher(Function<Entity, Iterable<? extends Entity>> f) {
297 return env -> connectionDataFetcher(() -> f.apply((env.getSource()))).get(env);
298 }
299
300 private DataFetcher<Map<String, Object>> connectionDataFetcher(Supplier<Iterable<? extends Entity>> iter) {
301
302
303
304
305 return env -> {
306 int limit = getLimit(env.getArgument(FIRST_PARAM), stream);
307 int offset = getOffset(env.getArgument(AFTER_PARAM), env.getArgument(FROM_PARAM));
308 return stream && limit < 0
309 ? lazyConnectionData(iter, limit, offset)
310 : strictConnectionData(iter, limit, offset);
311 };
312 }
313
314 private Map<String, Object> connectionData(Iterable<?> items,
315 Iterable<Map<String, Object>> edges, String nextCursor, String prevCursor) {
316 return mapOf(
317 ITEMS, items,
318 EDGES, edges,
319 PAGE_INFO, mapOf(
320 HAS_NEXT_PAGE, nextCursor != null,
321 NEXT_PAGE, nextCursor,
322 HAS_PREVIOUS_PAGE, prevCursor != null,
323 PREVIOUS_PAGE, prevCursor
324 )
325 );
326 }
327
328 private Map<String, Object> strictConnectionData(Supplier<Iterable<? extends Entity>> iter, int limit, int offset) {
329
330
331
332 long total = Iterables.size(iter.get());
333 QueryApi query = api().query().setStream(true).setLimit(limit).setOffset(offset);
334 QueryApi.Page<Entity> page = query.page(iter.get(), Entity.class);
335 List<Entity> items = Lists.newArrayList(page);
336
337
338
339 List<Map<String, Object>> edges = Lists.newArrayListWithExpectedSize(items.size());
340 for (int i = 0; i < items.size(); i++) {
341 edges.add(mapOf(
342 CURSOR, toBase64(String.valueOf(offset + i)),
343 NODE, items.get(i)
344 ));
345 }
346
347 boolean hasNext = page.getOffset() + items.size() < total;
348 boolean hasPrev = page.getOffset() > 0;
349 String nextCursor = toBase64(String.valueOf(offset + limit));
350 String prevCursor = toBase64(String.valueOf(offset - limit));
351 return connectionData(items, edges, hasNext ? nextCursor : null, hasPrev ? prevCursor : null);
352 }
353
354 private Map<String, Object> lazyConnectionData(Supplier<Iterable<? extends Entity>> iter, int limit, int offset) {
355 QueryApi query = api().query().setLimit(limit).setOffset(offset);
356 QueryApi.Page<Entity> items = query.page(iter.get(), Entity.class);
357 boolean hasPrev = items.getOffset() > 0;
358
359
360
361 final AtomicInteger index = new AtomicInteger();
362 Iterable<Map<String, Object>> edges = Iterables.transform(
363 query.page(iter.get(), Entity.class), item -> mapOf(
364 CURSOR, toBase64(String.valueOf(offset + index.getAndIncrement())),
365 NODE, item
366 ));
367
368 String prevCursor = toBase64(String.valueOf(offset - limit));
369 return connectionData(items, edges, null, hasPrev ? prevCursor : null);
370 }
371
372 private DataFetcher<Entity> entityIdDataFetcher(String type) {
373 return env -> {
374 try {
375 Accessible detail = api().detail(env.getArgument(Bundle.ID_KEY), Accessible.class);
376 return Objects.equals(detail.getType(), type) ? detail : null;
377 } catch (ItemNotFound e) {
378 return null;
379 }
380 };
381 };
382
383 private static final DataFetcher<String> idDataFetcher =
384 env -> (env.<Entity>getSource()).getProperty(EntityType.ID_KEY);
385
386 private static final DataFetcher<String> typeDataFetcher =
387 env -> (env.<Entity>getSource()).getProperty(EntityType.TYPE_KEY);
388
389 private static final DataFetcher<Object> attributeDataFetcher = env -> {
390 Entity source = env.<Entity>getSource();
391 String name = env.getFields().get(0).getName();
392 return source.getProperty(name);
393 };
394
395 private static DataFetcher<List> listDataFetcher(DataFetcher<Object> fetcher) {
396 return env -> {
397 Object obj = fetcher.get(env);
398 if (obj == null) {
399 return Collections.emptyList();
400 } else if (obj instanceof List) {
401 return (List)obj;
402 } else {
403 return Lists.newArrayList(obj);
404 }
405 };
406 }
407
408 private static final DataFetcher<Description> descriptionDataFetcher = env -> {
409 String lang = env.getArgument(Ontology.LANGUAGE_OF_DESCRIPTION);
410 String code = env.getArgument(Ontology.IDENTIFIER_KEY);
411
412 Entity source = env.getSource();
413 Iterable<Description> descriptions = source.as(Described.class).getDescriptions();
414
415 if (lang == null && code == null) {
416 int at = env.getArgument(SLICE_PARAM);
417 List<Description> descList = Lists.newArrayList(descriptions);
418 return at >= 1 && descList.size() >= at ? descList.get(at - 1) : null;
419 } else {
420 for (Description next : descriptions) {
421 String langCode = next.getLanguageOfDescription();
422 if (langCode.equalsIgnoreCase(lang)) {
423 if (code != null && !code.isEmpty()) {
424 String ident = next.getDescriptionCode();
425 if (ident.equals(code)) {
426 return next;
427 }
428 } else {
429 return next;
430 }
431 }
432 }
433 return null;
434 }
435 };
436
437 private static final DataFetcher<String> annotationNameDataFetcher =
438 env -> Optional.ofNullable(env.<Entity>getSource().as(Annotation.class)
439 .getAnnotator())
440 .map(Named::getName).orElse(null);
441
442 private static final DataFetcher<List<Map<String, Object>>> relatedItemsDataFetcher = env -> {
443 Entity source = env.getSource();
444 Iterable<Link> links = source.as(Linkable.class).getLinks();
445 return StreamSupport.stream(links.spliterator(), false).map(link -> {
446 Linkable target = Iterables.tryFind(link.getLinkTargets(),
447 t -> t != null && !t.equals(source)).orNull();
448 return target == null ? null : mapOf("context", link, "item", target);
449 }).filter(Objects::nonNull).collect(Collectors.toList());
450 };
451
452 private static DataFetcher<Object> transformingDataFetcher(DataFetcher fetcher, Function<Object, Object> transformer) {
453 return env -> transformer.apply(fetcher.get(env));
454 }
455
456 private DataFetcher<Iterable<Entity>> oneToManyRelationshipFetcher(Function<Entity, Iterable<? extends Entity>> f) {
457 return env -> {
458 Iterable<? extends Entity> elements = f.apply((env.getSource()));
459 return api().query().setStream(true).setLimit(-1).page(elements, Entity.class);
460 };
461 }
462
463 private DataFetcher<Entity> manyToOneRelationshipFetcher(Function<Entity, Entity> f) {
464 return env -> {
465 Entity elem = f.apply(env.getSource());
466 if (elem != null &&
467 AclManager.getAclFilterFunction(api().accessor())
468 .compute(elem.asVertex())) {
469 return elem;
470 }
471
472 return null;
473 };
474 }
475
476
477
478 private static GraphQLFieldDefinition.Builder nullAttr(String name, String description, GraphQLOutputType type) {
479 return newFieldDefinition()
480 .type(type)
481 .name(name)
482 .description(description)
483 .dataFetcher(attributeDataFetcher);
484 }
485
486 private static GraphQLFieldDefinition.Builder nullAttr(String name, String description) {
487 return nullAttr(name, description, GraphQLString);
488 }
489
490 private static GraphQLFieldDefinition.Builder nonNullAttr(String name, String description, GraphQLOutputType type) {
491 return newFieldDefinition()
492 .type(type)
493 .name(name)
494 .description(description)
495 .dataFetcher(attributeDataFetcher);
496 }
497
498 private static GraphQLFieldDefinition.Builder nonNullAttr(String name, String description) {
499 return nonNullAttr(name, description, GraphQLString);
500 }
501
502 private static List<GraphQLFieldDefinition> nullStringAttrs(DefinitionList[] items) {
503 return Lists.newArrayList(items)
504 .stream().filter(i -> !i.isMultiValued())
505 .map(f ->
506 newFieldDefinition()
507 .type(GraphQLString)
508 .name(f.name())
509 .description(f.getDescription())
510 .dataFetcher(attributeDataFetcher)
511 .build()
512 ).collect(Collectors.toList());
513 }
514
515 private static List<GraphQLFieldDefinition> listStringAttrs(DefinitionList[] items) {
516 return Lists.newArrayList(items)
517 .stream().filter(DefinitionList::isMultiValued)
518 .map(f ->
519 newFieldDefinition()
520 .type(GraphQLStringList)
521 .name(f.name())
522 .description(f.getDescription())
523 .dataFetcher(listDataFetcher(attributeDataFetcher))
524 .build()
525 ).collect(Collectors.toList());
526 }
527
528 private static final GraphQLFieldDefinition.Builder idField = newFieldDefinition()
529 .type(GraphQLNonNullString)
530 .name(Bundle.ID_KEY)
531 .description(__("graphql.field.id.description"))
532 .dataFetcher(idDataFetcher);
533
534 private static final GraphQLFieldDefinition.Builder typeField = newFieldDefinition()
535 .type(new GraphQLNonNull(GraphQLString))
536 .name(Bundle.TYPE_KEY)
537 .description(__("graphql.field.type.description"))
538 .dataFetcher(typeDataFetcher);
539
540 private static GraphQLFieldDefinition.Builder singleDescriptionFieldDefinition(GraphQLOutputType descriptionType) {
541 return newFieldDefinition()
542 .type(descriptionType)
543 .name("description")
544 .argument(newArgument()
545 .name(Ontology.LANGUAGE_OF_DESCRIPTION)
546 .description(__("graphql.argument.languageCode.description"))
547 .type(GraphQLString)
548 .build()
549 )
550 .argument(newArgument()
551 .name(Ontology.IDENTIFIER_KEY)
552 .description(__("graphql.argument.identifier.description"))
553 .type(GraphQLString)
554 .build()
555 )
556 .argument(newArgument()
557 .name(SLICE_PARAM)
558 .description(__("graphql.argument.at.description"))
559 .type(GraphQLInt)
560 .defaultValue(1)
561 .build()
562 )
563 .description(__("graphl.field.description.description"))
564 .dataFetcher(descriptionDataFetcher);
565 }
566
567 private static GraphQLFieldDefinition.Builder listFieldDefinition(String name, String description,
568 GraphQLOutputType type, DataFetcher dataFetcher) {
569 return newFieldDefinition()
570 .name(name)
571 .type(new GraphQLList(type))
572 .description(description)
573 .dataFetcher(dataFetcher);
574 }
575
576 private GraphQLFieldDefinition.Builder itemEventsFieldDefinition() {
577 return newFieldDefinition()
578 .name("systemEvents")
579 .description(__("graphql.field.systemEvents.description"))
580 .type(new GraphQLList(new GraphQLTypeReference(Entities.SYSTEM_EVENT)))
581 .dataFetcher(itemEventsDataFetcher());
582 }
583
584 private static GraphQLFieldDefinition.Builder connectionFieldDefinition(String name, String description,
585 GraphQLOutputType type, DataFetcher dataFetcher, GraphQLArgument... arguments) {
586 return newFieldDefinition()
587 .name(name)
588 .description(description)
589 .type(type)
590 .dataFetcher(dataFetcher)
591 .argument(newArgument()
592 .name(FIRST_PARAM)
593 .type(GraphQLInt)
594 .description(__("graphql.argument.first.description"))
595 .build()
596 )
597 .argument(newArgument()
598 .name(AFTER_PARAM)
599 .description(__("graphql.argument.after.description"))
600 .type(CursorType)
601 .build()
602 )
603 .argument(newArgument()
604 .name(FROM_PARAM)
605 .description(__("graphql.argument.from.description"))
606 .type(CursorType)
607 .build()
608 )
609 .argument(Lists.newArrayList(arguments));
610 }
611
612 private static final List<GraphQLFieldDefinition> entityFields =
613 ImmutableList.of(idField.build(), typeField.build());
614
615 private static final List<GraphQLFieldDefinition> geoFields = ImmutableList.of(
616 newFieldDefinition()
617 .name(Geo.latitude.name())
618 .description(Geo.latitude.getDescription())
619 .type(GraphQLBigDecimal)
620 .dataFetcher(attributeDataFetcher)
621 .build(),
622 newFieldDefinition()
623 .name(Geo.longitude.name())
624 .description(Geo.longitude.getDescription())
625 .type(GraphQLBigDecimal)
626 .dataFetcher(attributeDataFetcher)
627 .build()
628 );
629
630 private static List<GraphQLFieldDefinition> descriptionFields() {
631 return Lists.newArrayList(
632 nonNullAttr(Ontology.LANGUAGE_OF_DESCRIPTION, __("graphql.field.languageCode.description")).build(),
633 nonNullAttr(Ontology.NAME_KEY, __("graphql.field.name.description")).build(),
634 nullAttr(Ontology.IDENTIFIER_KEY, __("graphql.field.description.identifier.description")).build()
635 );
636 }
637
638 private GraphQLFieldDefinition.Builder descriptionsFieldDefinition(GraphQLOutputType descriptionType) {
639 return newFieldDefinition()
640 .type(new GraphQLList(descriptionType))
641 .name("descriptions")
642 .description(__("graphql.field.descriptions.description"))
643 .dataFetcher(oneToManyRelationshipFetcher(r -> r.as(Described.class).getDescriptions()));
644 }
645
646 private GraphQLFieldDefinition.Builder itemCountFieldDefinition(Function<Entity, Integer> f) {
647 return newFieldDefinition()
648 .type(new GraphQLNonNull(GraphQLInt))
649 .name("itemCount")
650 .description(__("graphql.field.itemCount.description"))
651 .dataFetcher(env -> Math.toIntExact(f.apply(env.<Entity>getSource())));
652 }
653
654 private final GraphQLFieldDefinition.Builder linkFieldDefinition =
655 listFieldDefinition("links", __("graphql.field.links.description"),
656 new GraphQLTypeReference(Entities.LINK),
657 oneToManyRelationshipFetcher(r -> r.as(Linkable.class).getLinks()));
658
659 private final GraphQLFieldDefinition.Builder annotationsFieldDefinition =
660 listFieldDefinition("annotations", __("graphql.field.annotations.description"),
661 new GraphQLTypeReference(Entities.ANNOTATION),
662 oneToManyRelationshipFetcher(r -> r.as(Annotatable.class).getAnnotations()));
663
664 private List<GraphQLFieldDefinition> linksAndAnnotationsFields() {
665 return Lists.newArrayList(linkFieldDefinition.build(), annotationsFieldDefinition.build());
666 }
667
668 private final GraphQLFieldDefinition.Builder accessPointFieldDefinition =
669 listFieldDefinition("accessPoints", __("graphql.field.accessPoints.description"),
670 new GraphQLTypeReference(Entities.ACCESS_POINT),
671 oneToManyRelationshipFetcher(d -> d.as(Description.class).getAccessPoints()));
672
673 private final GraphQLFieldDefinition.Builder datePeriodFieldDefinition =
674 listFieldDefinition("dates", __("graphql.field.dates.description"),
675 new GraphQLTypeReference(Entities.DATE_PERIOD),
676 oneToManyRelationshipFetcher(d -> d.as(Temporal.class).getDatePeriods()));
677
678 private GraphQLFieldDefinition.Builder itemFieldDefinition(String name, String description,
679 GraphQLOutputType type, DataFetcher dataFetcher, GraphQLArgument... arguments) {
680 return newFieldDefinition()
681 .name(name)
682 .type(type)
683 .description(description)
684 .dataFetcher(dataFetcher)
685 .argument(Lists.newArrayList(arguments));
686 }
687
688 private GraphQLFieldDefinition relatedTypeFieldDefinition() {
689 return newFieldDefinition()
690 .name("related")
691 .description(__("graphql.field.related.description"))
692 .type(new GraphQLList(relatedType))
693 .dataFetcher(relatedItemsDataFetcher)
694 .build();
695 }
696
697 private GraphQLFieldDefinition relatedItemsItemFieldDefinition() {
698 return newFieldDefinition()
699 .type(linkableInterface)
700 .name("item")
701 .description(__("graphql.field.related.item.description"))
702 .build();
703 }
704
705
706
707 private static GraphQLOutputType edgeType(GraphQLOutputType wrapped) {
708 return newObject()
709 .name(wrapped.getName() + "Edge")
710 .description(MessageFormat.format(__("graphql.edge.description"), wrapped.getName()))
711 .field(newFieldDefinition()
712 .name(NODE)
713 .type(wrapped)
714 .build()
715 )
716 .field(newFieldDefinition()
717 .name(CURSOR)
718 .type(CursorType)
719 .build()
720 )
721 .build();
722 }
723
724 private static List<GraphQLFieldDefinition> connectionFields(GraphQLOutputType wrappedType) {
725 return ImmutableList.of(
726 newFieldDefinition()
727 .name(ITEMS)
728 .description(MessageFormat.format(__("graphql.connection.field.items.description"), wrappedType.getName()))
729 .type(new GraphQLList(wrappedType))
730 .build(),
731 newFieldDefinition()
732 .name(EDGES)
733 .description(MessageFormat.format(__("graphql.connection.field.edges.description"), wrappedType.getName()))
734 .type(new GraphQLList(edgeType(wrappedType)))
735 .build(),
736 newFieldDefinition()
737 .name(PAGE_INFO)
738 .description(__("graphql.field.pageInfo.description"))
739 .type(newObject()
740 .name(PAGE_INFO + wrappedType.getName())
741 .field(newFieldDefinition()
742 .name(HAS_PREVIOUS_PAGE)
743 .description(__("graphql.field.pageInfo.hasPreviousPage.description"))
744 .type(GraphQLBoolean)
745 .build()
746 )
747 .field(newFieldDefinition()
748 .name(PREVIOUS_PAGE)
749 .description(__("graphql.field.pageInfo.previousPage.description"))
750 .type(CursorType)
751 .build()
752 )
753 .field(newFieldDefinition()
754 .name(HAS_NEXT_PAGE)
755 .description(__("graphql.field.pageInfo.hasNextPage.description"))
756 .type(GraphQLBoolean)
757 .build()
758 )
759 .field(newFieldDefinition()
760 .name(NEXT_PAGE)
761 .description(__("graphql.field.pageInfo.nextPage.description"))
762 .type(CursorType)
763 .build()
764 )
765 .build())
766 .build()
767 );
768 }
769
770 private static GraphQLOutputType connectionType(GraphQLOutputType wrappedType, String name) {
771 return newObject()
772 .name(name)
773 .description(MessageFormat.format(__("graphql.connection.description"), wrappedType.getName()))
774 .fields(connectionFields(wrappedType))
775 .build();
776 }
777
778
779
780 private static final List<GraphQLFieldDefinition> documentaryUnitDescriptionNullFields = nullStringAttrs(IsadG.values());
781 private static final List<GraphQLFieldDefinition> documentaryUnitDescriptionListFields = listStringAttrs(IsadG.values());
782 private static final List<GraphQLFieldDefinition> repositoryDescriptionNullFields = nullStringAttrs(Isdiah.values());
783 private static final List<GraphQLFieldDefinition> repositoryDescriptionListFields = listStringAttrs(Isdiah.values());
784 private static final List<GraphQLFieldDefinition> historicalAgentDescriptionNullFields = nullStringAttrs(Isaar.values());
785 private static final List<GraphQLFieldDefinition> historicalAgentDescriptionListFields = listStringAttrs(Isaar.values());
786 private static final List<GraphQLFieldDefinition> countryDescriptionNullFields = nullStringAttrs(CountryInfo.values());
787 private static final List<GraphQLFieldDefinition> countryDescriptionListFields = listStringAttrs(CountryInfo.values());
788 private static final List<GraphQLFieldDefinition> conceptNullFields = nullStringAttrs(Skos.values());
789 private static final List<GraphQLFieldDefinition> conceptListFields = listStringAttrs(Skos.values());
790 private static final List<GraphQLFieldDefinition> conceptDescriptionNullFields = nullStringAttrs(SkosMultilingual.values());
791 private static final List<GraphQLFieldDefinition> conceptDescriptionListFields = listStringAttrs(SkosMultilingual.values());
792
793
794
795
796 private final TypeResolver entityTypeResolver = new TypeResolver() {
797 @Override
798 public GraphQLObjectType getType(TypeResolutionEnvironment env) {
799 Entity entity = env.getObject();
800 switch (entity.getType()) {
801 case Entities.DOCUMENTARY_UNIT:
802 return documentaryUnitType;
803 case Entities.REPOSITORY:
804 return repositoryType;
805 case Entities.COUNTRY:
806 return countryType;
807 case Entities.HISTORICAL_AGENT:
808 return historicalAgentType;
809 case Entities.CVOC_CONCEPT:
810 return conceptType;
811 case Entities.CVOC_VOCABULARY:
812 return vocabularyType;
813 case Entities.AUTHORITATIVE_SET:
814 return authoritativeSetType;
815 case Entities.ANNOTATION:
816 return annotationType;
817 case Entities.LINK:
818 return linkType;
819 case Entities.ACCESS_POINT:
820 return accessPointType;
821 case Entities.DATE_PERIOD:
822 return datePeriodType;
823 default:
824 return null;
825 }
826 }
827 };
828
829 private final TypeResolver descriptionTypeResolver = new TypeResolver() {
830 @Override
831 public GraphQLObjectType getType(TypeResolutionEnvironment env) {
832 Entity entity = env.getObject();
833 switch (entity.getType()) {
834 case Entities.DOCUMENTARY_UNIT_DESCRIPTION:
835 return documentaryUnitDescriptionType;
836 case Entities.REPOSITORY_DESCRIPTION:
837 return repositoryDescriptionType;
838 case Entities.HISTORICAL_AGENT_DESCRIPTION:
839 return historicalAgentDescriptionType;
840 case Entities.CVOC_CONCEPT_DESCRIPTION:
841 return conceptDescriptionType;
842 default:
843 return null;
844 }
845 }
846 };
847
848 private final GraphQLInterfaceType entityInterface = newInterface()
849 .name(Entity.class.getSimpleName())
850 .description(__("graphql.interface.entity.description"))
851 .fields(entityFields)
852 .typeResolver(entityTypeResolver)
853 .build();
854
855 private final GraphQLInterfaceType descriptionInterface = newInterface()
856 .name(Description.class.getSimpleName())
857 .description(__("graphql.interface.description.description"))
858 .fields(descriptionFields())
859 .typeResolver(descriptionTypeResolver)
860 .build();
861
862 private final GraphQLInterfaceType temporalDescriptionInterface = newInterface()
863 .name(Temporal.class.getSimpleName() + Description.class.getSimpleName())
864 .description(__("graphql.interface.temporalDescription.description"))
865 .fields(descriptionFields())
866 .field((f) -> datePeriodFieldDefinition)
867 .typeResolver(descriptionTypeResolver)
868 .build();
869
870 private final GraphQLInterfaceType describedInterface = newInterface()
871 .name(Described.class.getSimpleName())
872 .description(__("graphql.interface.described.description"))
873 .fields(entityFields)
874 .field(singleDescriptionFieldDefinition(descriptionInterface))
875 .field(descriptionsFieldDefinition(descriptionInterface))
876 .field(nonNullAttr(Ontology.IDENTIFIER_KEY, __("graphql.field.identifier.description")))
877 .fields(linksAndAnnotationsFields())
878 .typeResolver(entityTypeResolver)
879 .build();
880
881 private final GraphQLInterfaceType temporalInterface = newInterface()
882 .name(Temporal.class.getSimpleName())
883 .description(__("graphql.interface.temporal.description"))
884 .fields(entityFields)
885 .field(nonNullAttr(Ontology.IDENTIFIER_KEY, __("graphql.field.identifier.description")))
886 .field(singleDescriptionFieldDefinition(temporalDescriptionInterface))
887 .field(descriptionsFieldDefinition(temporalDescriptionInterface))
888 .typeResolver(entityTypeResolver)
889 .build();
890
891 private final GraphQLInterfaceType annotatableInterface = newInterface()
892 .fields(entityFields)
893 .field(annotationsFieldDefinition)
894 .typeResolver(entityTypeResolver)
895 .name(Annotatable.class.getSimpleName())
896 .description(__("graphql.interface.annotatable.description"))
897 .build();
898
899 private final GraphQLInterfaceType linkableInterface = newInterface()
900 .fields(entityFields)
901 .field(linkFieldDefinition)
902 .typeResolver(entityTypeResolver)
903 .name(Linkable.class.getSimpleName())
904 .description(__("graphql.interface.linkable.description"))
905 .build();
906
907 private final GraphQLObjectType systemEventType = newObject()
908 .name(Entities.SYSTEM_EVENT)
909 .description(__("systemEvent.description"))
910 .field(nonNullAttr(Ontology.EVENT_TIMESTAMP, bundle.getString("systemEvent.field.timestamp.description")))
911 .field(nullAttr(Ontology.EVENT_LOG_MESSAGE, bundle.getString("systemEvent.field.logMessage.description")))
912 .field(nonNullAttr(Ontology.EVENT_TYPE, bundle.getString("systemEvent.field.eventType.description")))
913 .build();
914
915 private final GraphQLEnumType accessPointTypeEnum = newEnum()
916 .name(AccessPointType.class.getSimpleName())
917 .description(__("graphql.enum.accessPointType.description"))
918 .value(AccessPointType.person.name())
919 .value(AccessPointType.family.name())
920 .value(AccessPointType.corporateBody.name())
921 .value(AccessPointType.subject.name())
922 .value(AccessPointType.creator.name())
923 .value(AccessPointType.place.name())
924 .value(AccessPointType.genre.name())
925 .build();
926
927 private final GraphQLObjectType accessPointType = newObject()
928 .name(Entities.ACCESS_POINT)
929 .description(__("accessPoint.description"))
930 .field(nonNullAttr(Ontology.NAME_KEY, __("accessPoint.field.name.description")))
931 .field(newFieldDefinition()
932 .name(Ontology.ACCESS_POINT_TYPE)
933 .description(__("accessPoint.field.type.description"))
934 .type(new GraphQLNonNull(accessPointTypeEnum))
935 .dataFetcher(attributeDataFetcher)
936 .build())
937 .build();
938
939 private final GraphQLObjectType datePeriodType = newObject()
940 .name(Entities.DATE_PERIOD)
941 .description(__("datePeriod.description"))
942 .field(nullAttr(Ontology.DATE_PERIOD_START_DATE, __("datePeriod.field.startDate.description")))
943 .field(nullAttr(Ontology.DATE_PERIOD_END_DATE, __("datePeriod.field.endDate.description")))
944 .build();
945
946 private final GraphQLObjectType addressType = newObject()
947 .name(Entities.ADDRESS)
948 .description(__("address.description"))
949 .fields(nullStringAttrs(ContactInfo.values()))
950 .fields(listStringAttrs(ContactInfo.values()))
951 .build();
952
953 private final GraphQLObjectType relatedType = newObject()
954 .name("Relationship")
955 .description(__("relationship.description"))
956 .field(newFieldDefinition()
957 .name("context")
958 .description(__("relationship.field.context.description"))
959 .type(new GraphQLTypeReference(Entities.LINK))
960 .build())
961 .field(relatedItemsItemFieldDefinition())
962 .build();
963
964
965 private final GraphQLObjectType documentaryUnitDescriptionType = newObject()
966 .name(Entities.DOCUMENTARY_UNIT_DESCRIPTION)
967 .description(__("documentaryUnitDescription.description"))
968 .fields(descriptionFields())
969 .field(accessPointFieldDefinition)
970 .field(datePeriodFieldDefinition)
971 .fields(documentaryUnitDescriptionNullFields)
972 .fields(documentaryUnitDescriptionListFields)
973 .withInterfaces(descriptionInterface, temporalDescriptionInterface)
974 .build();
975
976 private final GraphQLObjectType repositoryDescriptionType = newObject()
977 .name(Entities.REPOSITORY_DESCRIPTION)
978 .description(__("repositoryDescription.description"))
979 .fields(descriptionFields())
980 .field(accessPointFieldDefinition)
981 .field(listFieldDefinition("addresses", __("repositoryDescription.field.addresses.description"),
982 addressType, oneToManyRelationshipFetcher(d ->
983 d.as(RepositoryDescription.class).getAddresses())))
984 .fields(repositoryDescriptionNullFields)
985 .fields(repositoryDescriptionListFields)
986 .withInterfaces(descriptionInterface)
987 .build();
988
989 private final GraphQLObjectType historicalAgentDescriptionType = newObject()
990 .name(Entities.HISTORICAL_AGENT_DESCRIPTION)
991 .description(__("historicalAgentDescription.description"))
992 .fields(descriptionFields())
993 .field(accessPointFieldDefinition)
994 .field(datePeriodFieldDefinition)
995 .fields(historicalAgentDescriptionNullFields)
996 .fields(historicalAgentDescriptionListFields)
997 .withInterfaces(descriptionInterface, temporalDescriptionInterface)
998 .build();
999
1000 private final GraphQLObjectType conceptDescriptionType = newObject()
1001 .name(Entities.CVOC_CONCEPT_DESCRIPTION)
1002 .description(__("conceptDescription.description"))
1003 .fields(descriptionFields())
1004 .field(accessPointFieldDefinition)
1005 .fields(conceptDescriptionNullFields)
1006 .fields(conceptDescriptionListFields)
1007 .withInterfaces(descriptionInterface)
1008 .build();
1009
1010 private static final GraphQLArgument allArgument = newArgument()
1011 .name(ALL_PARAM)
1012 .description(__("graphql.argument.all"))
1013 .type(GraphQLBoolean)
1014 .defaultValue(false)
1015 .build();
1016
1017 private final GraphQLObjectType repositoryType = newObject()
1018 .name(Entities.REPOSITORY)
1019 .description(__("repository.description"))
1020 .fields(entityFields)
1021 .field(nonNullAttr(Ontology.IDENTIFIER_KEY, __("repository.field.identifier.description")))
1022 .field(itemCountFieldDefinition(r -> r.as(Repository.class).getChildCount()))
1023 .field(connectionFieldDefinition("documentaryUnits", __("repository.field.documentaryUnits.description"),
1024 new GraphQLTypeReference("documentaryUnits"),
1025 hierarchicalOneToManyRelationshipConnectionFetcher(
1026 r -> r.as(Repository.class).getTopLevelDocumentaryUnits(),
1027 r -> r.as(Repository.class).getAllDocumentaryUnits()),
1028 allArgument))
1029 .field(singleDescriptionFieldDefinition(repositoryDescriptionType))
1030 .field(descriptionsFieldDefinition(repositoryDescriptionType))
1031 .field(itemFieldDefinition("country", __("repository.field.country.description"),
1032 new GraphQLTypeReference(Entities.COUNTRY),
1033 manyToOneRelationshipFetcher(r -> r.as(Repository.class).getCountry())))
1034 .fields(linksAndAnnotationsFields())
1035 .field(itemEventsFieldDefinition())
1036 .withInterfaces(entityInterface, describedInterface, linkableInterface, annotatableInterface)
1037 .build();
1038
1039 private final GraphQLObjectType documentaryUnitType = newObject()
1040 .name(Entities.DOCUMENTARY_UNIT)
1041 .description(__("documentaryUnit.description"))
1042 .fields(entityFields)
1043 .field(nonNullAttr(Ontology.IDENTIFIER_KEY, __("documentaryUnit.field.identifier.description")))
1044 .field(descriptionsFieldDefinition(documentaryUnitDescriptionType))
1045 .field(singleDescriptionFieldDefinition(documentaryUnitDescriptionType))
1046 .field(itemFieldDefinition("repository", __("documentaryUnit.field.repository.description"), repositoryType,
1047 manyToOneRelationshipFetcher(d -> d.as(DocumentaryUnit.class).getRepository())))
1048 .field(itemCountFieldDefinition(d -> d.as(DocumentaryUnit.class).getChildCount()))
1049 .field(connectionFieldDefinition("children", __("documentaryUnit.field.children.description"),
1050 new GraphQLTypeReference("documentaryUnits"),
1051 hierarchicalOneToManyRelationshipConnectionFetcher(
1052 d -> d.as(DocumentaryUnit.class).getChildren(),
1053 d -> d.as(DocumentaryUnit.class).getAllChildren()),
1054 allArgument))
1055 .field(itemFieldDefinition("parent", __("documentaryUnit.field.parent.description"),
1056 new GraphQLTypeReference(Entities.DOCUMENTARY_UNIT),
1057 manyToOneRelationshipFetcher(d -> d.as(DocumentaryUnit.class).getParent())))
1058 .field(listFieldDefinition("ancestors", __("documentaryUnit.field.ancestors.description"),
1059 new GraphQLTypeReference(Entities.DOCUMENTARY_UNIT),
1060 oneToManyRelationshipFetcher(d -> d.as(DocumentaryUnit.class).getAncestors())))
1061 .fields(linksAndAnnotationsFields())
1062 .field(relatedTypeFieldDefinition())
1063 .field(itemEventsFieldDefinition())
1064 .withInterfaces(entityInterface, describedInterface, linkableInterface, annotatableInterface, temporalInterface)
1065 .build();
1066
1067 private final GraphQLObjectType historicalAgentType = newObject()
1068 .name(Entities.HISTORICAL_AGENT)
1069 .description(__("historicalAgent.description"))
1070 .fields(entityFields)
1071 .field(nonNullAttr(Ontology.IDENTIFIER_KEY, __("historicalAgent.field.identifier.description")))
1072 .field(singleDescriptionFieldDefinition(historicalAgentDescriptionType))
1073 .field(descriptionsFieldDefinition(historicalAgentDescriptionType))
1074 .fields(linksAndAnnotationsFields())
1075 .field(relatedTypeFieldDefinition())
1076 .field(itemEventsFieldDefinition())
1077 .withInterfaces(entityInterface, describedInterface, linkableInterface, annotatableInterface, temporalInterface)
1078 .build();
1079
1080 private final GraphQLObjectType authoritativeSetType = newObject()
1081 .name(Entities.AUTHORITATIVE_SET)
1082 .description(__("authoritativeSet.description"))
1083 .fields(entityFields)
1084 .field(nonNullAttr(Ontology.IDENTIFIER_KEY, __("authoritativeSet.field.identifier.description")))
1085 .field(nonNullAttr(Ontology.NAME_KEY, __("authoritativeSet.field.name.description")))
1086 .field(nullAttr("description", __("authoritativeSet.field.description.description")))
1087 .field(itemCountFieldDefinition(a -> a.as(AuthoritativeSet.class).getChildCount()))
1088 .field(connectionFieldDefinition("authorities", __("authoritativeSet.field.authorities.description"),
1089 new GraphQLTypeReference("historicalAgents"),
1090 oneToManyRelationshipConnectionFetcher(
1091 c -> c.as(AuthoritativeSet.class).getAuthoritativeItems())))
1092 .fields(linksAndAnnotationsFields())
1093 .field(itemEventsFieldDefinition())
1094 .withInterfaces(entityInterface, annotatableInterface)
1095 .build();
1096
1097 private final GraphQLObjectType countryType = newObject()
1098 .name(Entities.COUNTRY)
1099 .description(__("country.description"))
1100 .fields(entityFields)
1101 .field(nonNullAttr(Ontology.IDENTIFIER_KEY, __("country.field.identifier.description")))
1102 .field(newFieldDefinition()
1103 .name(Ontology.NAME_KEY)
1104 .description(__("country.field.name.description"))
1105 .type(new GraphQLNonNull(GraphQLString))
1106 .dataFetcher(transformingDataFetcher(idDataFetcher,
1107 obj -> LanguageHelpers.countryCodeToName(obj.toString())))
1108 .build()
1109 )
1110 .fields(countryDescriptionNullFields)
1111 .fields(countryDescriptionListFields)
1112 .field(itemCountFieldDefinition(c -> c.as(Country.class).getChildCount()))
1113 .field(connectionFieldDefinition("repositories", __("country.field.repositories.description"),
1114 new GraphQLTypeReference("repositories"),
1115 oneToManyRelationshipConnectionFetcher(
1116 c -> c.as(Country.class).getRepositories())))
1117 .fields(linksAndAnnotationsFields())
1118 .field(itemEventsFieldDefinition())
1119 .withInterfaces(entityInterface, annotatableInterface)
1120 .build();
1121
1122 private final GraphQLObjectType conceptType = newObject()
1123 .name(Entities.CVOC_CONCEPT)
1124 .description(__("cvocConcept.description"))
1125 .fields(entityFields)
1126 .field(nonNullAttr(Ontology.IDENTIFIER_KEY, __("cvocConcept.field.identifier.description")))
1127 .fields(conceptNullFields)
1128 .fields(conceptListFields)
1129 .fields(geoFields)
1130 .field(descriptionsFieldDefinition(conceptDescriptionType))
1131 .field(singleDescriptionFieldDefinition(conceptDescriptionType))
1132 .field(listFieldDefinition("related", __("cvocConcept.field.related.description"),
1133 new GraphQLTypeReference(Entities.CVOC_CONCEPT),
1134 oneToManyRelationshipFetcher(
1135 c -> c.as(Concept.class).getRelatedConcepts())))
1136 .field(itemCountFieldDefinition(c -> c.as(Concept.class).getChildCount()))
1137 .field(listFieldDefinition("broader", __("cvocConcept.field.broader.description"),
1138 new GraphQLTypeReference(Entities.CVOC_CONCEPT),
1139 oneToManyRelationshipFetcher(c -> c.as(Concept.class).getBroaderConcepts())))
1140 .field(listFieldDefinition("narrower", __("cvocConcept.field.narrower.description"),
1141 new GraphQLTypeReference(Entities.CVOC_CONCEPT),
1142 oneToManyRelationshipFetcher(
1143 c -> c.as(Concept.class).getNarrowerConcepts())))
1144 .field(itemFieldDefinition("vocabulary", __("cvocConcept.field.vocabulary.description"),
1145 new GraphQLTypeReference(Entities.CVOC_VOCABULARY),
1146 manyToOneRelationshipFetcher(c -> c.as(Concept.class).getVocabulary())))
1147 .fields(linksAndAnnotationsFields())
1148 .field(itemEventsFieldDefinition())
1149 .withInterfaces(entityInterface, describedInterface, linkableInterface, annotatableInterface)
1150 .build();
1151
1152 private final GraphQLObjectType vocabularyType = newObject()
1153 .name(Entities.CVOC_VOCABULARY)
1154 .description(__("cvocVocabulary.description"))
1155 .fields(entityFields)
1156 .field(nonNullAttr(Ontology.IDENTIFIER_KEY, __("cvocVocabulary.field.identifier.description")))
1157 .field(nonNullAttr(Ontology.NAME_KEY, __("cvocVocabulary.field.name.description")))
1158 .field(nullAttr("description", __("cvocVocabulary.field.description.description")))
1159 .field(itemCountFieldDefinition(r -> r.as(Vocabulary.class).getChildCount()))
1160 .field(connectionFieldDefinition("concepts", __("cvocVocabulary.field.concepts.description"),
1161 new GraphQLTypeReference("concepts"),
1162 oneToManyRelationshipConnectionFetcher(
1163 c -> c.as(Vocabulary.class).getConcepts())))
1164 .fields(linksAndAnnotationsFields())
1165 .field(itemEventsFieldDefinition())
1166 .withInterfaces(entityInterface, annotatableInterface)
1167 .build();
1168
1169 private final GraphQLObjectType annotationType = newObject()
1170 .name(Entities.ANNOTATION)
1171 .description(__("annotation.description"))
1172 .fields(entityFields)
1173 .field(nonNullAttr(Ontology.ANNOTATION_NOTES_BODY, __("annotation.field.body.description")))
1174 .field(nullAttr(Ontology.ANNOTATION_FIELD, __("annotation.field.field.description")))
1175 .field(nullAttr(Ontology.ANNOTATION_TYPE, __("annotation.field.annotationType.description")))
1176 .field(newFieldDefinition()
1177 .type(GraphQLString)
1178 .name("by")
1179 .description(__("annotation.field.by.description"))
1180 .dataFetcher(annotationNameDataFetcher)
1181 .build()
1182 )
1183 .field(listFieldDefinition("targets", __("annotation.field.targets.description"),
1184 annotatableInterface,
1185 oneToManyRelationshipFetcher(a -> a.as(Annotation.class).getTargets())))
1186 .field(listFieldDefinition("annotations", __("annotation.field.annotations.description"),
1187 new GraphQLTypeReference(Entities.ANNOTATION),
1188 oneToManyRelationshipFetcher(r -> r.as(Annotatable.class).getAnnotations())))
1189 .field(itemEventsFieldDefinition())
1190 .withInterfaces(entityInterface, annotatableInterface)
1191 .build();
1192
1193 private final GraphQLObjectType linkType = newObject()
1194 .name(Entities.LINK)
1195 .description(__("link.description"))
1196 .fields(entityFields)
1197 .field(nullAttr(Ontology.LINK_HAS_DESCRIPTION, __("link.field.description.description")))
1198 .field(nullAttr(Ontology.LINK_HAS_FIELD, __("link.field.field.description")))
1199 .field(listFieldDefinition("targets", __("link.field.targets.description"), linkableInterface,
1200 oneToManyRelationshipFetcher(a -> a.as(Link.class).getLinkTargets())))
1201 .field(listFieldDefinition("body", __("link.field.body.description"), accessPointType,
1202 oneToManyRelationshipFetcher(a -> a.as(Link.class).getLinkBodies())))
1203 .field(annotationsFieldDefinition)
1204 .field(datePeriodFieldDefinition)
1205 .field(itemEventsFieldDefinition())
1206 .withInterfaces(entityInterface, annotatableInterface)
1207 .build();
1208
1209 private final GraphQLOutputType documentaryUnitsConnection = connectionType(
1210 new GraphQLTypeReference(Entities.DOCUMENTARY_UNIT),"documentaryUnits");
1211
1212 private final GraphQLOutputType repositoriesConnection = connectionType(
1213 new GraphQLTypeReference(Entities.REPOSITORY),"repositories");
1214
1215 private final GraphQLOutputType countriesConnection = connectionType(
1216 new GraphQLTypeReference(Entities.COUNTRY),"countries");
1217
1218 private final GraphQLOutputType historicalAgentsConnection = connectionType(
1219 new GraphQLTypeReference(Entities.HISTORICAL_AGENT),"historicalAgents");
1220
1221 private final GraphQLOutputType authoritativeSetsConnection = connectionType(
1222 new GraphQLTypeReference(Entities.AUTHORITATIVE_SET),"authoritativeSets");
1223
1224 private final GraphQLOutputType conceptsConnection = connectionType(
1225 new GraphQLTypeReference(Entities.CVOC_CONCEPT),"concepts");
1226
1227 private final GraphQLOutputType vocabulariesConnection = connectionType(
1228 new GraphQLTypeReference(Entities.CVOC_VOCABULARY),"vocabularies");
1229
1230 private final GraphQLOutputType annotationsConnection = connectionType(
1231 new GraphQLTypeReference(Entities.ANNOTATION),"annotations");
1232
1233 private final GraphQLOutputType linksConnection = connectionType(
1234 new GraphQLTypeReference(Entities.LINK),"links");
1235
1236 private GraphQLObjectType queryType() {
1237 return newObject()
1238 .name("Root")
1239
1240
1241 .field(itemFieldDefinition(Entities.DOCUMENTARY_UNIT, __("root.single.documentaryUnit.description"),
1242 documentaryUnitType, entityIdDataFetcher(Entities.DOCUMENTARY_UNIT), idArgument))
1243 .field(itemFieldDefinition(Entities.REPOSITORY, __("root.single.repository.description"),
1244 repositoryType, entityIdDataFetcher(Entities.REPOSITORY), idArgument))
1245 .field(itemFieldDefinition(Entities.COUNTRY, __("root.single.country.description"),
1246 countryType, entityIdDataFetcher(Entities.COUNTRY), idArgument))
1247 .field(itemFieldDefinition(Entities.HISTORICAL_AGENT, __("root.single.historicalAgent.description"),
1248 historicalAgentType, entityIdDataFetcher(Entities.HISTORICAL_AGENT), idArgument))
1249 .field(itemFieldDefinition(Entities.AUTHORITATIVE_SET, __("root.single.authoritativeSet.description"),
1250 authoritativeSetType, entityIdDataFetcher(Entities.AUTHORITATIVE_SET), idArgument))
1251 .field(itemFieldDefinition(Entities.CVOC_CONCEPT, __("root.single.cvocConcept.description"),
1252 conceptType, entityIdDataFetcher(Entities.CVOC_CONCEPT), idArgument))
1253 .field(itemFieldDefinition(Entities.CVOC_VOCABULARY, __("root.single.cvocVocabulary.description"),
1254 vocabularyType, entityIdDataFetcher(Entities.CVOC_VOCABULARY), idArgument))
1255 .field(itemFieldDefinition(Entities.ANNOTATION, __("root.single.annotation.description"),
1256 annotationType, entityIdDataFetcher(Entities.ANNOTATION), idArgument))
1257 .field(itemFieldDefinition(Entities.LINK, __("root.single.link.description"),
1258 linkType, entityIdDataFetcher(Entities.LINK), idArgument))
1259
1260
1261 .field(connectionFieldDefinition("documentaryUnits", __("root.connection.documentaryUnit.description"),
1262 documentaryUnitsConnection,
1263 entityTypeConnectionDataFetcher(EntityClass.DOCUMENTARY_UNIT)))
1264 .field(connectionFieldDefinition("topLevelDocumentaryUnits", __("root.connection.documentaryUnit.topLevel.description"),
1265 documentaryUnitsConnection,
1266 topLevelDocDataFetcher()))
1267 .field(connectionFieldDefinition("repositories", __("root.connection.repository.description"),
1268 repositoriesConnection,
1269 entityTypeConnectionDataFetcher(EntityClass.REPOSITORY)))
1270 .field(connectionFieldDefinition("historicalAgents", __("root.connection.historicalAgent.description"),
1271 historicalAgentsConnection,
1272 entityTypeConnectionDataFetcher(EntityClass.HISTORICAL_AGENT)))
1273 .field(connectionFieldDefinition("countries", __("root.connection.country.description"),
1274 countriesConnection,
1275 entityTypeConnectionDataFetcher(EntityClass.COUNTRY)))
1276 .field(connectionFieldDefinition("authoritativeSets", __("root.connection.authoritativeSet.description"),
1277 authoritativeSetsConnection,
1278 entityTypeConnectionDataFetcher(EntityClass.AUTHORITATIVE_SET)))
1279 .field(connectionFieldDefinition("concepts", __("root.connection.cvocConcept.description"),
1280 conceptsConnection,
1281 entityTypeConnectionDataFetcher(EntityClass.CVOC_CONCEPT)))
1282 .field(connectionFieldDefinition("vocabularies", __("root.connection.cvocVocabulary.description"),
1283 vocabulariesConnection,
1284 entityTypeConnectionDataFetcher(EntityClass.CVOC_VOCABULARY)))
1285 .field(connectionFieldDefinition("annotations", __("root.connection.annotation.description"),
1286 annotationsConnection,
1287 entityTypeConnectionDataFetcher(EntityClass.ANNOTATION)))
1288 .field(connectionFieldDefinition("links", __("root.connection.link.description"),
1289 linksConnection,
1290 entityTypeConnectionDataFetcher(EntityClass.LINK)))
1291 .build();
1292 }
1293 }