001/*
002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 */
019package org.nuxeo.ecm.platform.audit.service;
020
021import static org.nuxeo.ecm.platform.audit.listener.StreamAuditEventListener.STREAM_AUDIT_ENABLED_PROP;
022
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
029import java.util.Set;
030
031import org.apache.logging.log4j.LogManager;
032import org.apache.logging.log4j.Logger;
033import org.nuxeo.ecm.platform.audit.api.AuditStorage;
034import org.nuxeo.ecm.platform.audit.api.DocumentHistoryReader;
035import org.nuxeo.ecm.platform.audit.api.document.DocumentHistoryReaderImpl;
036import org.nuxeo.ecm.platform.audit.service.extension.AdapterDescriptor;
037import org.nuxeo.ecm.platform.audit.service.extension.AuditBackendDescriptor;
038import org.nuxeo.ecm.platform.audit.service.extension.AuditBulkerDescriptor;
039import org.nuxeo.ecm.platform.audit.service.extension.AuditStorageDescriptor;
040import org.nuxeo.ecm.platform.audit.service.extension.EventDescriptor;
041import org.nuxeo.ecm.platform.audit.service.extension.ExtendedInfoDescriptor;
042import org.nuxeo.runtime.api.Framework;
043import org.nuxeo.runtime.logging.DeprecationLogger;
044import org.nuxeo.runtime.model.ComponentContext;
045import org.nuxeo.runtime.model.ComponentInstance;
046import org.nuxeo.runtime.model.ComponentManager;
047import org.nuxeo.runtime.model.ComponentManager.Listener;
048import org.nuxeo.runtime.model.ComponentName;
049import org.nuxeo.runtime.model.DefaultComponent;
050
051/**
052 * Event service configuration.
053 *
054 * @author <a href="mailto:ja@nuxeo.com">Julien Anguenot</a>
055 */
056public class NXAuditEventsService extends DefaultComponent {
057
058    public static final ComponentName NAME = new ComponentName(
059            "org.nuxeo.ecm.platform.audit.service.NXAuditEventsService");
060
061    private static final String EVENT_EXT_POINT = "event";
062
063    private static final String EXTENDED_INFO_EXT_POINT = "extendedInfo";
064
065    private static final String ADAPTER_POINT = "adapter";
066
067    /**
068     * If passed as true on the event properties, event not logged
069     *
070     * @since 5.7
071     */
072    public static final String DISABLE_AUDIT_LOGGER = "disableAuditLogger";
073
074    protected static final Logger log = LogManager.getLogger(NXAuditEventsService.class);
075
076    protected final Set<ExtendedInfoDescriptor> extendedInfoDescriptors = new HashSet<>();
077
078    protected final Map<String, List<ExtendedInfoDescriptor>> eventExtendedInfoDescriptors = new HashMap<>();
079
080    // the adapters that will injected in the EL context for extended
081    // information
082    protected final Set<AdapterDescriptor> documentAdapters = new HashSet<>();
083
084    protected final Set<String> eventNames = new HashSet<>();
085
086    protected AuditBackend backend;
087
088    protected AuditBackendDescriptor backendConfig = new AuditBackendDescriptor();
089
090    /**
091     * @deprecated since 10.10, audit bulker is now handled with nuxeo-stream, no replacement
092     */
093    @Deprecated
094    protected AuditBulker bulker;
095
096    /**
097     * @deprecated since 10.10, audit bulker is now handled with nuxeo-stream, no replacement
098     */
099    @Deprecated
100    protected AuditBulkerDescriptor bulkerConfig = new AuditBulkerDescriptor();
101
102    protected Map<String, AuditStorageDescriptor> auditStorageDescriptors = new HashMap<>();
103
104    protected Map<String, AuditStorage> auditStorages = new HashMap<>();
105
106    @Override
107    public int getApplicationStartedOrder() {
108        return backendConfig.getApplicationStartedOrder();
109    }
110
111    @Override
112    @SuppressWarnings("deprecation")
113    public void start(ComponentContext context) {
114        backend = backendConfig.newInstance(this);
115        backend.onApplicationStarted();
116        if (Framework.isBooleanPropertyFalse(STREAM_AUDIT_ENABLED_PROP)) {
117            bulker = bulkerConfig.newInstance(backend);
118            bulker.onApplicationStarted();
119        }
120        // init storages after runtime was started (as we don't have started order for storages which are backends)
121        Framework.getRuntime().getComponentManager().addListener(new Listener() {
122
123            @Override
124            public void afterStart(ComponentManager mgr, boolean isResume) {
125                for (Entry<String, AuditStorageDescriptor> descriptor : auditStorageDescriptors.entrySet()) {
126                    AuditStorage storage = descriptor.getValue().newInstance();
127                    if (storage instanceof AuditBackend) {
128                        ((AuditBackend) storage).onApplicationStarted();
129                    }
130                    auditStorages.put(descriptor.getKey(), storage);
131                }
132            }
133
134            @Override
135            public void afterStop(ComponentManager mgr, boolean isStandby) {
136                uninstall();
137            }
138
139        });
140    }
141
142    @Override
143    @SuppressWarnings("deprecation")
144    public void stop(ComponentContext context) {
145        try {
146            if (bulker != null) {
147                bulker.onApplicationStopped();
148            }
149        } finally {
150            backend.onApplicationStopped();
151            // clear storages
152            auditStorages.values().forEach(storage -> {
153                if (storage instanceof AuditBackend) {
154                    ((AuditBackend) storage).onApplicationStopped();
155                }
156            });
157            auditStorages.clear();
158        }
159    }
160
161    protected void doRegisterAdapter(AdapterDescriptor desc) {
162        log.debug("Registered adapter : {}", desc::getName);
163        documentAdapters.add(desc);
164    }
165
166    protected void doRegisterEvent(EventDescriptor desc) {
167        String eventName = desc.getName();
168        if (desc.getEnabled()) {
169            eventNames.add(eventName);
170            log.debug("Registered event: {}", eventName);
171            for (ExtendedInfoDescriptor extInfoDesc : desc.getExtendedInfoDescriptors()) {
172                if (extInfoDesc.getEnabled()) {
173                    if (eventExtendedInfoDescriptors.containsKey(eventName)) {
174                        eventExtendedInfoDescriptors.get(eventName).add(extInfoDesc);
175                    } else {
176                        List<ExtendedInfoDescriptor> toBeAdded = new ArrayList<>();
177                        toBeAdded.add(extInfoDesc);
178                        eventExtendedInfoDescriptors.put(eventName, toBeAdded);
179                    }
180                } else {
181                    if (eventExtendedInfoDescriptors.containsKey(eventName)) {
182                        eventExtendedInfoDescriptors.get(eventName).remove(extInfoDesc);
183                    }
184                }
185            }
186        } else if (eventNames.contains(eventName)) {
187            doUnregisterEvent(desc);
188        }
189    }
190
191    protected void doRegisterExtendedInfo(ExtendedInfoDescriptor desc) {
192        log.debug("Registered extended info mapping : {}", desc::getKey);
193        extendedInfoDescriptors.add(desc);
194    }
195
196    protected void doUnregisterAdapter(AdapterDescriptor desc) {
197        // FIXME: this doesn't look right
198        documentAdapters.remove(desc);
199        log.debug("Unregistered adapter: {}", desc::getName);
200    }
201
202    protected void doUnregisterEvent(EventDescriptor desc) {
203        eventNames.remove(desc.getName());
204        eventExtendedInfoDescriptors.remove(desc.getName());
205        log.debug("Unregistered event: {}", desc::getName);
206    }
207
208    protected void doUnregisterExtendedInfo(ExtendedInfoDescriptor desc) {
209        // FIXME: this doesn't look right
210        extendedInfoDescriptors.remove(desc);
211        log.debug("Unregistered extended info: {}", desc::getKey);
212    }
213
214    @Override
215    public <T> T getAdapter(Class<T> adapter) {
216        if (adapter.getCanonicalName().equals(DocumentHistoryReader.class.getCanonicalName())) {
217            return adapter.cast(new DocumentHistoryReaderImpl());
218        } else {
219            if (backend != null) {
220                return adapter.cast(backend);
221            } else {
222                log.error("Can not provide service {} since backend is undefined", adapter::getCanonicalName);
223                return null;
224            }
225        }
226    }
227
228    public Set<String> getAuditableEventNames() {
229        return eventNames;
230    }
231
232    public AuditBackend getBackend() {
233        return backend;
234    }
235
236    public Set<AdapterDescriptor> getDocumentAdapters() {
237        return documentAdapters;
238    }
239
240    /**
241     * @since 7.4
242     */
243    public Map<String, List<ExtendedInfoDescriptor>> getEventExtendedInfoDescriptors() {
244        return eventExtendedInfoDescriptors;
245    }
246
247    public Set<ExtendedInfoDescriptor> getExtendedInfoDescriptors() {
248        return extendedInfoDescriptors;
249    }
250
251    @Override
252    @SuppressWarnings("deprecation")
253    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
254        if (extensionPoint.equals(EVENT_EXT_POINT)) {
255            doRegisterEvent((EventDescriptor) contribution);
256        } else if (extensionPoint.equals(EXTENDED_INFO_EXT_POINT)) {
257            doRegisterExtendedInfo((ExtendedInfoDescriptor) contribution);
258        } else if (extensionPoint.equals(ADAPTER_POINT)) {
259            doRegisterAdapter((AdapterDescriptor) contribution);
260        } else if (contribution instanceof AuditBackendDescriptor) {
261            backendConfig = (AuditBackendDescriptor) contribution;
262        } else if (contribution instanceof AuditBulkerDescriptor) {
263            bulkerConfig = (AuditBulkerDescriptor) contribution;
264            String message = String.format(
265                    "AuditBulker on component %s is deprecated because it is now handled with nuxeo-stream, no replacement.",
266                    contributor.getName());
267            DeprecationLogger.log(message, "10.10");
268            Framework.getRuntime().getMessageHandler().addWarning(message);
269        } else if (contribution instanceof AuditStorageDescriptor) {
270            AuditStorageDescriptor auditStorageDesc = (AuditStorageDescriptor) contribution;
271            auditStorageDescriptors.put(auditStorageDesc.getId(), auditStorageDesc);
272        }
273    }
274
275    @Override
276    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
277        if (extensionPoint.equals(EVENT_EXT_POINT)) {
278            doUnregisterEvent((EventDescriptor) contribution);
279        } else if (extensionPoint.equals(EXTENDED_INFO_EXT_POINT)) {
280            doUnregisterExtendedInfo((ExtendedInfoDescriptor) contribution);
281        } else if (extensionPoint.equals(ADAPTER_POINT)) {
282            doUnregisterAdapter((AdapterDescriptor) contribution);
283        }
284    }
285
286    /**
287     * @since 9.3
288     */
289    public AuditStorage getAuditStorage(String id) {
290        return auditStorages.get(id);
291    }
292
293}