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