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.kernel.impl.observer;
020
021import org.fcrepo.kernel.api.identifiers.FedoraId;
022import org.fcrepo.kernel.api.observer.Event;
023import org.fcrepo.kernel.api.observer.EventType;
024import org.fcrepo.kernel.api.operations.ResourceOperation;
025import org.fcrepo.kernel.impl.util.UserUtil;
026
027import java.net.URI;
028import java.time.Instant;
029import java.util.HashSet;
030import java.util.Objects;
031import java.util.Set;
032
033/**
034 * Converts a ResourceOperation into an Event.
035 *
036 * @author pwinckles
037 */
038public class ResourceOperationEventBuilder implements EventBuilder {
039
040    private FedoraId fedoraId;
041    private Set<EventType> types;
042    private Set<String> resourceTypes;
043    private String userID;
044    private String userAgent;
045    private String baseUrl;
046    private Instant date;
047    private String userAgentBaseUri;
048
049    /**
050     * Creates a new EventBuilder based on an ResourceOperation
051     *
052     * @param fedoraId the FedoraId the operation is on
053     * @param operation the ResourceOperation to create an event for
054     * @param userAgentBaseUri the base uri of the user agent, optional
055     * @return new builder
056     */
057    public static ResourceOperationEventBuilder fromResourceOperation(final FedoraId fedoraId,
058                                                                      final ResourceOperation operation,
059                                                                      final String userAgentBaseUri) {
060        final var builder = new ResourceOperationEventBuilder();
061        builder.fedoraId = fedoraId;
062        builder.date = Instant.now();
063        builder.resourceTypes = new HashSet<>();
064        builder.userID = operation.getUserPrincipal();
065        builder.types = new HashSet<>();
066        builder.types.add(mapOperationToEventType(operation));
067        builder.userAgentBaseUri = userAgentBaseUri;
068        return builder;
069    }
070
071    private static EventType mapOperationToEventType(final ResourceOperation operation) {
072        switch (operation.getType()) {
073            case CREATE:
074                return EventType.RESOURCE_CREATION;
075            case UPDATE:
076                return EventType.RESOURCE_MODIFICATION;
077            case DELETE:
078                return EventType.RESOURCE_DELETION;
079            case PURGE:
080                return EventType.RESOURCE_PURGE;
081            case FOLLOW:
082                return EventType.INBOUND_REFERENCE;
083            default:
084                throw new IllegalStateException(
085                        String.format("There is no EventType mapping for ResourceOperation type %s on operation %s",
086                                operation.getType(), operation));
087        }
088    }
089
090    private ResourceOperationEventBuilder() {
091        // Intentionally left blank
092    }
093
094    @Override
095    public EventBuilder merge(final EventBuilder other) {
096        if (other == null) {
097            return this;
098        }
099
100        if (!(other instanceof ResourceOperationEventBuilder)) {
101            throw new IllegalStateException(
102                    String.format("Cannot merge EventBuilders because they are different types <%s> and <%s>",
103                            this.getClass(), other.getClass()));
104        }
105
106        final var otherCast = (ResourceOperationEventBuilder) other;
107
108        if (!this.fedoraId.equals(otherCast.fedoraId)) {
109            throw new IllegalStateException(
110                    String.format("Cannot merge events because they are for different resources: <%s> and <%s>",
111                            this, otherCast));
112        }
113
114        this.types.addAll(otherCast.types);
115        this.resourceTypes.addAll(otherCast.resourceTypes);
116
117        if (this.date.isBefore(otherCast.date)) {
118            this.date = otherCast.date;
119        }
120
121        return this;
122    }
123
124    @Override
125    public EventBuilder withResourceTypes(final Set<String> resourceTypes) {
126        this.resourceTypes = Objects.requireNonNullElse(resourceTypes, new HashSet<>());
127        return this;
128    }
129
130    @Override
131    public EventBuilder withBaseUrl(final String baseUrl) {
132        this.baseUrl = baseUrl;
133        return this;
134    }
135
136    @Override
137    public EventBuilder withUserAgent(final String userAgent) {
138        this.userAgent = userAgent;
139        return this;
140    }
141
142    @Override
143    public Event build() {
144        URI userUri = null;
145        if (userID != null) {
146            userUri = UserUtil.getUserURI(userID, userAgentBaseUri);
147        }
148        return new EventImpl(fedoraId, types, resourceTypes, userID, userUri, userAgent, baseUrl, date);
149    }
150
151    @Override
152    public String toString() {
153        return "ResourceOperationEventBuilder{" +
154                "fedoraId=" + fedoraId +
155                ", types=" + types +
156                ", resourceTypes=" + resourceTypes +
157                ", userID='" + userID + '\'' +
158                ", userAgent='" + userAgent + '\'' +
159                ", baseUrl='" + baseUrl + '\'' +
160                ", date=" + date +
161                '}';
162    }
163
164}