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