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