001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.fcrepo.event.serialization;
020
021import static java.time.format.DateTimeFormatter.ISO_INSTANT;
022import static java.util.stream.Collectors.toList;
023
024import static org.fcrepo.kernel.api.RdfLexicon.ACTIVITY_STREAMS_NAMESPACE;
025import static org.fcrepo.kernel.api.RdfLexicon.PROV_NAMESPACE;
026import static org.slf4j.LoggerFactory.getLogger;
027
028import java.time.Instant;
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.List;
032import java.util.Objects;
033
034import com.fasterxml.jackson.annotation.JsonSubTypes;
035import com.fasterxml.jackson.annotation.JsonTypeInfo;
036import org.fcrepo.kernel.api.observer.Event;
037
038import org.slf4j.Logger;
039
040import com.fasterxml.jackson.annotation.JsonIgnore;
041import com.fasterxml.jackson.annotation.JsonProperty;
042
043/**
044 * A structure used for serializing a Event into JSON
045 * 
046 * @author acoburn
047 * @author dbernstein
048 * @author whikloj
049 */
050public class JsonLDEventMessage {
051
052    @JsonIgnore
053    private static final Logger LOGGER = getLogger(JsonLDEventMessage.class);
054
055    public static class ContextElement {
056
057        @JsonProperty("@id")
058        public final String id;
059
060        @JsonProperty("@type")
061        public final String type;
062
063        public ContextElement(final String id) {
064            this.id = id;
065            this.type = "@id";
066        }
067
068        public ContextElement(final String id, final String type) {
069            this.id = id;
070            this.type = type;
071        }
072    }
073
074    public static class Context {
075
076        public final String prov = "http://www.w3.org/ns/prov#";
077
078        public final String dcterms = "http://purl.org/dc/terms/";
079
080        public final String type = "@type";
081
082        public final String id = "@id";
083
084        public final ContextElement isPartOf = new ContextElement("dcterms:isPartOf");
085
086    }
087
088    public static class Object {
089
090        @JsonProperty("type")
091        public List<String> type;
092
093        @JsonProperty("id")
094        public String id;
095
096        @JsonProperty("isPartOf")
097        public String isPartOf;
098
099        public Object() {
100            // Needed to deserialize class.
101        }
102
103        public Object(final String id, final List<String> type, final String isPartOf) {
104            this.type = type;
105            this.id = id;
106            this.isPartOf = isPartOf;
107        }
108    }
109
110    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
111    @JsonSubTypes({
112            @JsonSubTypes.Type(value = Application.class, name = "Application"),
113            @JsonSubTypes.Type(value = Person.class, name = "Person") }
114    )
115    public static class Actor {
116        @JsonIgnore
117        public String type;
118
119        public Actor(final String type) {
120            this.type = type;
121        }
122    }
123
124    public static class Application extends Actor {
125
126        @JsonProperty("name")
127        public String name;
128
129        public Application() {
130            super("Application");
131        }
132
133        public Application(final String name, final String type) {
134            super(type);
135            this.name = name;
136        }
137    }
138
139    public static class Person extends Actor {
140
141        @JsonProperty("id")
142        public String id;
143
144        public Person() {
145            super("Person");
146        }
147
148        public Person(final String id, final String type) {
149            super(type);
150            this.id = id;
151        }
152    }
153
154    @JsonProperty("id")
155    public String id;
156
157    @JsonProperty("type")
158    public List<String> type;
159
160    @JsonProperty("name")
161    public String name;
162
163    @JsonProperty("published")
164    public Instant published;
165
166
167    @JsonProperty("actor")
168    public List<Actor> actor;
169
170    @JsonProperty("object")
171    public Object object;
172
173    @JsonProperty("@context")
174    public List<java.lang.Object> context;
175
176    public void setPublished(final String published) {
177        this.published = Instant.from(ISO_INSTANT.parse(published));
178    }
179
180    /**
181     * Populate a JsonLDEventMessage from a Event
182     * 
183     * @param evt The Fedora event
184     * @return a JsonLDEventMessage
185     */
186    public static JsonLDEventMessage from(final Event evt) {
187
188        final String baseUrl = evt.getBaseUrl();
189
190        // build objectId
191        final String objectId = baseUrl + evt.getPath();
192        // build event types list
193        final List<String> types = evt.getTypes()
194                .stream()
195                .map(rdfType -> rdfType.getTypeAbbreviated())
196                .collect(toList());
197        // comma-separated list for names of events (since name requires string rather than array)
198        final String name = String.join(", ", evt.getTypes()
199                .stream()
200                .map(rdfType -> rdfType.getName())
201                .collect(toList()));
202        // build resource types list
203        final List<String> resourceTypes = new ArrayList<>(evt.getResourceTypes());
204        if (!resourceTypes.contains(PROV_NAMESPACE + "Entity")) {
205            resourceTypes.add(PROV_NAMESPACE + "Entity");
206        }
207
208        // build actors list
209        final List<Actor> actor = new ArrayList<>();
210        actor.add(new Person(Objects.toString(evt.getUserURI()), "Person"));
211        final String softwareAgent = evt.getUserAgent();
212        if (softwareAgent != null) {
213            actor.add(new Application(softwareAgent, "Application"));
214        }
215
216        final JsonLDEventMessage msg = new JsonLDEventMessage();
217
218        msg.id = evt.getEventID();
219        msg.context = Arrays.asList(ACTIVITY_STREAMS_NAMESPACE, new Context());
220        msg.actor = actor;
221        msg.published = evt.getDate();
222        msg.type = types;
223        msg.name = name;
224        msg.object = new Object(objectId, resourceTypes, baseUrl);
225        return msg;
226    }
227}