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.camel.processor;
020
021import static java.util.Collections.singletonList;
022import static java.util.Optional.empty;
023import static java.util.Optional.of;
024import static java.util.Optional.ofNullable;
025import static java.util.stream.Collectors.toList;
026import static org.fcrepo.camel.FcrepoHeaders.FCREPO_AGENT;
027import static org.fcrepo.camel.FcrepoHeaders.FCREPO_DATE_TIME;
028import static org.fcrepo.camel.FcrepoHeaders.FCREPO_EVENT_ID;
029import static org.fcrepo.camel.FcrepoHeaders.FCREPO_EVENT_TYPE;
030import static org.fcrepo.camel.FcrepoHeaders.FCREPO_RESOURCE_TYPE;
031import static org.fcrepo.camel.FcrepoHeaders.FCREPO_URI;
032
033import java.io.InputStream;
034import java.io.IOException;
035import java.util.ArrayList;
036import java.util.HashMap;
037import java.util.HashSet;
038import java.util.List;
039import java.util.Map;
040import java.util.Optional;
041import java.util.Set;
042
043import com.fasterxml.jackson.databind.JsonNode;
044import com.fasterxml.jackson.databind.ObjectMapper;
045
046import org.apache.camel.Exchange;
047import org.apache.camel.Processor;
048
049/**
050 * Converts a Fedora Message into camel-based headers.
051 *
052 * @author acoburn
053 */
054public class EventProcessor implements Processor {
055
056    private static final ObjectMapper mapper = new ObjectMapper();
057
058    /**
059     * Process the Fedora message
060     *
061     * @param exchange the current camel message exchange
062     */
063    public void process(final Exchange exchange) throws IOException {
064        final Object body = exchange.getIn().getBody();
065        final Map<String, List<String>> data = new HashMap<>();
066        if (body != null) {
067            // In the event that the message was already converted to a Map
068            if (body instanceof Map) {
069                data.putAll(getValuesFromMap((Map)body));
070            } else if (body instanceof String) {
071                data.putAll(getValuesFromJson(mapper.readTree((String)body)));
072            } else if (body instanceof InputStream) {
073                data.putAll(getValuesFromJson(mapper.readTree((InputStream)body)));
074            }
075        }
076
077        final Set<String> singleValuedFields = new HashSet<String>();
078        singleValuedFields.add(FCREPO_URI);
079        singleValuedFields.add(FCREPO_DATE_TIME);
080        singleValuedFields.add(FCREPO_EVENT_ID);
081
082        data.entrySet().stream().filter(entry -> entry.getValue() != null)
083                .filter(entry -> !entry.getValue().isEmpty()).forEach(entry -> {
084                    if (singleValuedFields.contains(entry.getKey())) {
085                        exchange.getIn().setHeader(entry.getKey(), entry.getValue().get(0));
086                    } else {
087                        exchange.getIn().setHeader(entry.getKey(), entry.getValue());
088                    }
089                });
090    }
091
092    private Map<String, List<String>> getValuesFromJson(final JsonNode body) {
093        final Map<String, List<String>> data = new HashMap<>();
094
095        getValues(body, "@id").ifPresent(id -> data.put(FCREPO_EVENT_ID, id));
096        getValues(body, "id").ifPresent(id -> data.putIfAbsent(FCREPO_EVENT_ID, id));
097
098        getValues(body, "published").ifPresent(date -> data.putIfAbsent(FCREPO_DATE_TIME, date));
099        getValues(body, "type").map(EventProcessor::toUris).ifPresent(type -> data.put(FCREPO_EVENT_TYPE, type));
100
101        if (body.has("object")) {
102            final JsonNode object = body.get("object");
103            getValues(object, "@id").ifPresent(id -> data.put(FCREPO_URI, id));
104            getValues(object, "id").ifPresent(id -> data.putIfAbsent(FCREPO_URI, id));
105
106            getValues(object, "@type").ifPresent(type -> data.put(FCREPO_RESOURCE_TYPE, type));
107            getValues(object, "type").ifPresent(type -> data.putIfAbsent(FCREPO_RESOURCE_TYPE, type));
108        }
109
110        if (body.has("actor")) {
111            final JsonNode actor = body.get("actor");
112            if (actor.isArray()) {
113                final List<String> agents = new ArrayList<>();
114                for (final JsonNode agent : actor) {
115                    getString(agent, "name").ifPresent(agents::add);
116                    getString(agent, "id").ifPresent(agents::add);
117                }
118                data.put(FCREPO_AGENT, agents);
119            } else {
120                getString(actor, "name").ifPresent(name -> data.put(FCREPO_AGENT, singletonList(name)));
121                getString(actor, "id").ifPresent(name -> data.put(FCREPO_AGENT, singletonList(name)));
122            }
123        }
124        return data;
125    }
126
127    private static List<String> toUris(final List<String> jsonldValues) {
128        return jsonldValues.stream().map(ActivityStreamTerms::expand).collect(toList());
129    }
130
131    private static Optional<String> getString(final JsonNode node, final String fieldName) {
132        if (node.has(fieldName)) {
133            final JsonNode field = node.get(fieldName);
134            if (field.isTextual()) {
135                return of(field.asText());
136            }
137        }
138        return empty();
139    }
140
141    private static Optional<List<String>> getValues(final JsonNode node, final String fieldName) {
142        if (node.has(fieldName)) {
143            final JsonNode field = node.get(fieldName);
144            if (field.isArray()) {
145                final List<String> elements = new ArrayList<>();
146                field.elements().forEachRemaining(elem -> {
147                    if (elem.isTextual()) {
148                        elements.add(elem.asText());
149                    }
150                });
151                return of(elements);
152            } else if (field.isTextual()) {
153                return of(singletonList(field.asText()));
154            }
155        }
156        return empty();
157    }
158
159    @SuppressWarnings("unchecked")
160    private static Map<String, List<String>> getValuesFromMap(final Map body) {
161        final Map<String, Object> values = (Map<String, Object>)body;
162        final Map<String, List<String>> data = new HashMap<>();
163        if (values.containsKey("@id")) {
164            data.put(FCREPO_EVENT_ID, singletonList((String) values.get("@id")));
165        }
166        if (values.containsKey("id")) {
167            data.putIfAbsent(FCREPO_EVENT_ID, singletonList((String) values.get("id")));
168        }
169
170        if (values.containsKey("@type")) {
171            data.put(FCREPO_EVENT_TYPE, toUris((List<String>) values.get("@type")));
172        }
173        if (values.containsKey("type")) {
174            data.putIfAbsent(FCREPO_EVENT_TYPE, toUris((List<String>) values.get("type")));
175        }
176
177        if (values.containsKey("published")) {
178            data.putIfAbsent(FCREPO_DATE_TIME, singletonList((String) values.get("published")));
179        }
180
181        final Map<String, Object> object = (Map<String, Object>) values.get("object");
182
183        if (object != null) {
184            if (object.containsKey("type")) {
185                data.put(FCREPO_RESOURCE_TYPE, (List<String>) object.get("type"));
186            }
187            data.put(FCREPO_URI, singletonList((String) object.get("id")));
188        }
189
190        final List<Map<String, String>> actor = (List<Map<String, String>>) values.get("actor");
191        if (actor != null) {
192            data.put(FCREPO_AGENT,
193                    actor.stream().map(agent -> ofNullable(agent.get("name")).orElse(agent.get("id")))
194                            .collect(toList()));
195        }
196
197        return data;
198    }
199}