View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.graphql.command;
23  
24  import org.opencastproject.authorization.xacml.manager.api.AclService;
25  import org.opencastproject.authorization.xacml.manager.api.AclServiceFactory;
26  import org.opencastproject.elasticsearch.api.SearchIndexException;
27  import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
28  import org.opencastproject.graphql.event.GqlEvent;
29  import org.opencastproject.graphql.exception.GraphQLNotFoundException;
30  import org.opencastproject.graphql.exception.GraphQLRuntimeException;
31  import org.opencastproject.graphql.exception.GraphQLUnauthorizedException;
32  import org.opencastproject.graphql.execution.context.OpencastContext;
33  import org.opencastproject.graphql.execution.context.OpencastContextManager;
34  import org.opencastproject.graphql.type.input.AccessControlListInput;
35  import org.opencastproject.graphql.type.input.GqlCommonEventMetadataInput;
36  import org.opencastproject.graphql.util.GraphQLObjectMapper;
37  import org.opencastproject.graphql.util.MetadataFieldToGraphQLConverter;
38  import org.opencastproject.index.service.api.IndexService;
39  import org.opencastproject.index.service.catalog.adapter.events.CommonEventCatalogUIAdapter;
40  import org.opencastproject.index.service.exception.IndexServiceException;
41  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
42  import org.opencastproject.metadata.dublincore.DublinCoreMetadataCollection;
43  import org.opencastproject.metadata.dublincore.MetadataField;
44  import org.opencastproject.metadata.dublincore.MetadataList;
45  import org.opencastproject.security.api.AccessControlEntry;
46  import org.opencastproject.security.api.AccessControlList;
47  import org.opencastproject.security.api.SecurityService;
48  import org.opencastproject.security.api.UnauthorizedException;
49  import org.opencastproject.util.NotFoundException;
50  
51  
52  import java.time.Duration;
53  import java.time.OffsetDateTime;
54  import java.time.format.DateTimeFormatter;
55  import java.util.Map;
56  
57  import graphql.schema.GraphQLScalarType;
58  import graphql.schema.GraphQLTypeUtil;
59  
60  public class UpdateEventCommand extends AbstractCommand<GqlEvent> {
61  
62    private final String eventId;
63  
64    public UpdateEventCommand(final Builder builder) {
65      super(builder);
66      this.eventId = builder.eventId;
67    }
68  
69    @Override
70    public GqlEvent execute() {
71      OpencastContext context = OpencastContextManager.getCurrentContext();
72      final ElasticsearchIndex index = context.getService(ElasticsearchIndex.class);
73      final IndexService indexService = context.getService(IndexService.class);
74  
75      final Map<String, Object> eventMetadata = environment.getArgument("metadata");
76      try {
77        indexService.updateEventMetadata(eventId, createMetadataList(eventMetadata, indexService), index);
78      } catch (IndexServiceException | SearchIndexException e) {
79        throw new GraphQLRuntimeException(e);
80      } catch (UnauthorizedException e) {
81        throw new GraphQLUnauthorizedException(e.getMessage());
82      } catch (NotFoundException e) {
83        throw new GraphQLNotFoundException(e.getMessage());
84      }
85  
86      final AccessControlListInput aclInput = GraphQLObjectMapper.newInstance()
87          .convertValue(environment.getArgument("acl"), AccessControlListInput.class);
88      if (aclInput != null) {
89        try {
90          AccessControlList acl = new AccessControlList();
91          for (var entry : aclInput.getEntries()) {
92            for (var action : entry.getAction()) {
93              acl.getEntries().add(new AccessControlEntry(entry.getRole(), action, true));
94            }
95          }
96  
97          if (aclInput.getManagedAclId() != null) {
98            AclService aclService = context.getService(AclServiceFactory.class)
99                                           .serviceFor(context.getService(SecurityService.class).getOrganization());
100           aclService.getAcl(aclInput.getManagedAclId())
101                     .ifPresent(value -> acl.merge(value.getAcl()));
102         }
103         indexService.updateEventAcl(eventId, acl, index);
104       } catch (IndexServiceException | SearchIndexException e) {
105         throw new GraphQLRuntimeException(e);
106       } catch (UnauthorizedException e) {
107         throw new GraphQLUnauthorizedException(e.getMessage());
108       } catch (NotFoundException e) {
109         throw new GraphQLNotFoundException(e.getMessage());
110       }
111     }
112 
113     try {
114       return new GqlEvent(indexService.getEvent(eventId, index).get());
115     } catch (SearchIndexException e) {
116       throw new GraphQLRuntimeException(e);
117     }
118   }
119 
120   private MetadataList createMetadataList(
121       Map<String, Object> eventMetadata,
122       IndexService indexService
123   ) {
124     CommonEventCatalogUIAdapter adapter = (CommonEventCatalogUIAdapter) indexService
125         .getCommonEventCatalogUIAdapter();
126 
127     MetadataList list = new MetadataList();
128     list.add(adapter, adapter.getRawFields());
129 
130     final MediaPackageElementFlavor flavor = MediaPackageElementFlavor
131         .parseFlavor("dublincore/episode");
132 
133     final DublinCoreMetadataCollection collection = list.getMetadataByFlavor(flavor.toString());
134 
135     for (Map.Entry<String, Object> entry: eventMetadata.entrySet()) {
136       String key = entry.getKey();
137       final MetadataField target = collection.getOutputFields().get(key);
138       var type = GraphQLTypeUtil.unwrapNonNull(MetadataFieldToGraphQLConverter.convertType(target));
139 
140       Object value;
141       if (type instanceof GraphQLScalarType) {
142         value = ((GraphQLScalarType)type).getCoercing()
143             .parseValue(eventMetadata.get(key), environment.getGraphQlContext(), environment.getLocale());
144       } else {
145         value = eventMetadata.get(key);
146       }
147 
148       if (value == null) {
149         continue;
150       }
151 
152       switch (target.getType()) {
153         case DATE:
154         case START_DATE:
155           target.setValue(DateTimeFormatter.ofPattern(target.getPattern()).format((OffsetDateTime)value));
156           break;
157         case LONG:
158           target.setValue(value);
159           break;
160         case TEXT:
161           target.setValue(value);
162           break;
163         case BOOLEAN:
164           target.setValue(value);
165           break;
166         case DURATION:
167           target.setValue(((Duration) value).toMillis());
168           break;
169         case TEXT_LONG:
170           target.setValue(value);
171           break;
172         case MIXED_TEXT:
173           target.setValue(value);
174           break;
175         case START_TIME:
176           target.setValue(value);
177           break;
178         case ORDERED_TEXT:
179           target.setValue(value);
180           break;
181         case ITERABLE_TEXT:
182           target.setValue(value);
183           break;
184         default:
185           target.setValue(value);
186           break;
187       }
188     }
189 
190     return list;
191   }
192 
193   public static Builder create(String eventId, GqlCommonEventMetadataInput eventMetadataInput) {
194     return new Builder(eventId, eventMetadataInput);
195   }
196 
197   public static class Builder extends AbstractCommand.Builder<GqlEvent> {
198 
199     private final String eventId;
200 
201     private final GqlCommonEventMetadataInput eventMetadataInput;
202 
203     public Builder(String eventId, GqlCommonEventMetadataInput eventMetadataInput) {
204       this.eventId = eventId;
205       this.eventMetadataInput = eventMetadataInput;
206     }
207 
208     @Override
209     public void validate() {
210       super.validate();
211       if (eventId == null || eventId.isEmpty()) {
212         throw new IllegalStateException("Event ID cannot be null or empty");
213       }
214       if (eventMetadataInput == null) {
215         throw new IllegalStateException("Event metadata cannot be null");
216       }
217     }
218 
219     @Override
220     public UpdateEventCommand build() {
221       validate();
222       return new UpdateEventCommand(this);
223     }
224   }
225 
226 }