001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2015 ForgeRock AS. 015 */ 016package org.forgerock.audit.events; 017 018import static org.forgerock.json.JsonValue.json; 019import static org.forgerock.json.JsonValue.object; 020 021import com.fasterxml.jackson.databind.ObjectMapper; 022import org.forgerock.json.JsonValue; 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025 026import java.io.BufferedInputStream; 027import java.io.IOException; 028import java.io.InputStream; 029import java.util.HashMap; 030import java.util.Map; 031 032/** 033 * Builder for {@link EventTopicsMetaData}. 034 */ 035public final class EventTopicsMetaDataBuilder { 036 037 private static final Logger logger = LoggerFactory.getLogger(EventTopicsMetaDataBuilder.class); 038 private static final String SCHEMA = "schema"; 039 private static final String PROPERTIES = "properties"; 040 private static final ObjectMapper MAPPER = new ObjectMapper(); 041 042 private JsonValue coreTopicSchemaExtensions = json(object()); 043 private JsonValue additionalTopicSchemas = json(object()); 044 045 private EventTopicsMetaDataBuilder() { 046 // private to force use of static factory method 047 } 048 049 /** 050 * Create a new instance of EventTopicsMetaDataBuilder that will populate {@link EventTopicsMetaData} objects its 051 * creates with the schema meta-data for core topics. 052 * 053 * @return a new instance of EventTopicsMetaDataBuilder. 054 */ 055 public static EventTopicsMetaDataBuilder coreTopicSchemas() { 056 return new EventTopicsMetaDataBuilder(); 057 } 058 059 /** 060 * Specifies additional fields that should be added to the schemas for core event topics. 061 * <p/> 062 * The extension must not redefine a property already defined in the core event topics. 063 * <p> 064 * Example of a valid extension: 065 * <pre> 066 * { 067 * "access": { 068 * "schema": { 069 * "$schema": "http://json-schema.org/draft-04/schema#", 070 * "id": "/", 071 * "type": "object", 072 * "properties": { 073 * "extraField": { 074 * "type": "string" 075 * } 076 * } 077 * } 078 * } 079 * } 080 * </pre> 081 * 082 * @param coreTopicSchemaExtensions 083 * the extension of the core event topics. 084 * @return this builder for method-chaining. 085 */ 086 public EventTopicsMetaDataBuilder withCoreTopicSchemaExtensions(JsonValue coreTopicSchemaExtensions) { 087 this.coreTopicSchemaExtensions = coreTopicSchemaExtensions == null ? json(object()) : coreTopicSchemaExtensions; 088 return this; 089 } 090 091 /** 092 * Specifies schemas for additional topics. 093 * <p/> 094 * Custom schema must always include _id, timestamp, transactionId and eventName fields. 095 * <p/> 096 * Example of a valid schema: 097 * <pre> 098 * "customTopic": { 099 * "schema": { 100 * "$schema": "http://json-schema.org/draft-04/schema#", 101 * "id": "/", 102 * "type": "object", 103 * "properties": { 104 * "_id": { 105 * "type": "string" 106 * }, 107 * "timestamp": { 108 * "type": "string" 109 * }, 110 * "transactionId": { 111 * "type": "string" 112 * }, 113 * "eventName": { 114 * "type": "string" 115 * }, 116 * "customField": { 117 * "type": "string" 118 * } 119 * } 120 * } 121 * } 122 * </pre> 123 * 124 * @param additionalTopicSchemas 125 * the schemas of the additional event topics. 126 * @return this builder for method-chaining. 127 */ 128 public EventTopicsMetaDataBuilder withAdditionalTopicSchemas(JsonValue additionalTopicSchemas) { 129 this.additionalTopicSchemas = additionalTopicSchemas == null ? json(object()) : additionalTopicSchemas;; 130 return this; 131 } 132 133 /** 134 * Create a new instance of {@link EventTopicsMetaData}. 135 * 136 * @return a new instance of {@link EventTopicsMetaData}. 137 */ 138 public EventTopicsMetaData build() { 139 Map<String, JsonValue> auditEventTopicSchemas = readCoreEventTopicSchemas(); 140 extendCoreEventTopicsSchemas(auditEventTopicSchemas); 141 addCustomEventTopicSchemas(auditEventTopicSchemas); 142 return new EventTopicsMetaData(auditEventTopicSchemas); 143 } 144 145 private Map<String, JsonValue> readCoreEventTopicSchemas() { 146 Map<String, JsonValue> auditEvents = new HashMap<>(); 147 try (final InputStream configStream = getResourceAsStream("/org/forgerock/audit/events.json")) { 148 final JsonValue predefinedEventTypes = new JsonValue(MAPPER.readValue(configStream, Map.class)); 149 for (String eventTypeName : predefinedEventTypes.keys()) { 150 auditEvents.put(eventTypeName, predefinedEventTypes.get(eventTypeName)); 151 } 152 return auditEvents; 153 } catch (IOException ioe) { 154 logger.error("Error while parsing core event topic schema definitions", ioe); 155 throw new RuntimeException(ioe); 156 } 157 } 158 159 private InputStream getResourceAsStream(String resourcePath) { 160 return new BufferedInputStream(getClass().getResourceAsStream(resourcePath)); 161 } 162 163 private void extendCoreEventTopicsSchemas(Map<String, JsonValue> auditEventTopicSchemas) { 164 for (String topic : coreTopicSchemaExtensions.keys()) { 165 if (auditEventTopicSchemas.containsKey(topic)) { 166 JsonValue coreEventType = auditEventTopicSchemas.get(topic); 167 JsonValue coreProperties = coreEventType.get(SCHEMA).get(PROPERTIES); 168 JsonValue extendedProperties = coreTopicSchemaExtensions.get(topic).get(SCHEMA).get(PROPERTIES); 169 170 for (String property : extendedProperties.keys()) { 171 if (coreProperties.isDefined(property)) { 172 logger.warn("Cannot override {} property of {} topic", property, topic); 173 } else { 174 coreProperties.add(property, extendedProperties.get(property)); 175 } 176 } 177 } 178 } 179 } 180 181 private void addCustomEventTopicSchemas(Map<String, JsonValue> auditEventTopicSchemas) { 182 for (String topic : additionalTopicSchemas.keys()) { 183 if (!auditEventTopicSchemas.containsKey(topic)) { 184 JsonValue additionalTopicSchema = additionalTopicSchemas.get(topic); 185 if (!additionalTopicSchema.get(SCHEMA).isDefined(PROPERTIES)) { 186 logger.warn("{} topic schema definition is invalid", topic); 187 } else { 188 auditEventTopicSchemas.put(topic, additionalTopicSchema); 189 } 190 } else { 191 logger.warn("Cannot override pre-defined event topic {}", topic); 192 } 193 } 194 } 195 196}