001/*
002 * (C) Copyright 2006-2013 Nuxeo SA (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 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.core.event.impl;
023
024import java.io.Serializable;
025import java.rmi.dgc.VMID;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Map;
031import java.util.Map.Entry;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.ecm.core.api.CoreInstance;
036import org.nuxeo.ecm.core.api.CoreSession;
037import org.nuxeo.ecm.core.api.DocumentModel;
038import org.nuxeo.ecm.core.api.DocumentNotFoundException;
039import org.nuxeo.ecm.core.api.DocumentRef;
040import org.nuxeo.ecm.core.event.DeletedDocumentModel;
041import org.nuxeo.ecm.core.event.Event;
042import org.nuxeo.ecm.core.event.EventBundle;
043import org.nuxeo.ecm.core.event.EventContext;
044import org.nuxeo.ecm.core.event.ReconnectedEventBundle;
045import org.nuxeo.runtime.api.Framework;
046import org.nuxeo.runtime.api.login.NuxeoLoginContext;
047
048/**
049 * Default implementation for an {@link EventBundle} that need to be reconnected to a usable Session.
050 *
051 * @author tiry
052 */
053public class ReconnectedEventBundleImpl implements ReconnectedEventBundle {
054
055    private static final long serialVersionUID = 1L;
056
057    protected EventBundle sourceEventBundle;
058
059    /** Lister name or names. */
060    protected String listenerName;
061
062    protected transient List<Event> reconnectedEvents;
063
064    protected transient NuxeoLoginContext loginCtx;
065
066    protected transient CoreSession reconnectedCoreSession;
067
068    private static final Log log = LogFactory.getLog(ReconnectedEventBundleImpl.class);
069
070    protected ReconnectedEventBundleImpl() {
071    }
072
073    public ReconnectedEventBundleImpl(EventBundle sourceEventBundle) {
074        this.sourceEventBundle = sourceEventBundle;
075    }
076
077    /** @since 5.6 */
078    public ReconnectedEventBundleImpl(EventBundle sourceEventBundle, String listenerName) {
079        this.sourceEventBundle = sourceEventBundle;
080        this.listenerName = listenerName;
081    }
082
083    protected CoreSession getReconnectedCoreSession(String repoName, String originatingUsername) {
084        if (reconnectedCoreSession == null) {
085            loginCtx = Framework.loginSystem();
086            reconnectedCoreSession = CoreInstance.getCoreSessionSystem(repoName, originatingUsername);
087        } else {
088            // Sanity Check
089            if (!reconnectedCoreSession.getRepositoryName().equals(repoName)) {
090                if (repoName != null) {
091                    throw new IllegalStateException("Can no reconnected a Bundle tied to several Core instances !");
092                }
093            }
094        }
095        return reconnectedCoreSession;
096    }
097
098    protected List<Event> getReconnectedEvents() {
099        if (reconnectedEvents == null) {
100            reconnectedEvents = new ArrayList<>();
101            for (Event event : sourceEventBundle) {
102                EventContext ctx = event.getContext();
103                String repositoryName = ctx.getRepositoryName();
104                CoreSession session;
105                if (repositoryName == null) {
106                    session = null;
107                } else {
108                    String originatingUsername = ctx.getPrincipal().getActingUser();
109                    session = getReconnectedCoreSession(repositoryName, originatingUsername);
110                }
111
112                List<Object> newArgs = new ArrayList<>();
113                for (Object arg : ctx.getArguments()) {
114                    Object newArg = arg;
115                    if (refetchDocumentModel(session, arg) && session.getPrincipal() != null) { // NOSONAR
116                        DocumentModel oldDoc = (DocumentModel) arg;
117                        DocumentRef ref = oldDoc.getRef();
118                        if (ref != null) {
119                            try {
120                                if (session.exists(oldDoc.getRef())) {
121                                    newArg = session.getDocument(oldDoc.getRef());
122                                } else {
123                                    // probably deleted doc
124                                    newArg = new DeletedDocumentModel(oldDoc);
125                                }
126                            } catch (DocumentNotFoundException e) {
127                                log.error("Can not refetch Doc with ref " + ref.toString(), e);
128                            }
129                        }
130                    }
131                    // XXX treat here other cases !!!!
132                    newArgs.add(newArg);
133                }
134
135                EventContext newCtx = null;
136                if (ctx instanceof DocumentEventContext) {
137                    newCtx = new DocumentEventContext(session, ctx.getPrincipal(), (DocumentModel) newArgs.get(0),
138                            (DocumentRef) newArgs.get(1));
139                } else {
140                    newCtx = new EventContextImpl(session, ctx.getPrincipal());
141                    ((EventContextImpl) newCtx).setArgs(newArgs.toArray());
142                }
143
144                Map<String, Serializable> newProps = new HashMap<>();
145                for (Entry<String, Serializable> prop : ctx.getProperties().entrySet()) {
146                    Serializable propValue = prop.getValue();
147                    if (refetchDocumentModel(session, propValue)) {
148                        DocumentModel oldDoc = (DocumentModel) propValue;
149                        DocumentRef oldRef = oldDoc.getRef();
150                        try {
151                            if (session.exists(oldRef)) { // NOSONAR
152                                propValue = session.getDocument(oldRef);
153                            } else {
154                                log.warn("Listener " + (listenerName == null ? "" : "'" + listenerName + "' ")
155                                        + "cannot refetch missing document: " + oldRef + " ("
156                                        + oldDoc.getPathAsString() + ")");
157                            }
158                        } catch (DocumentNotFoundException e) {
159                            log.error("Can not refetch Doc with ref " + oldRef, e);
160                        }
161                    }
162                    // XXX treat here other cases !!!!
163                    newProps.put(prop.getKey(), propValue);
164                }
165                newCtx.setProperties(newProps);
166                Event newEvt = new EventImpl(event.getName(), newCtx, event.getFlags(), event.getTime());
167                reconnectedEvents.add(newEvt);
168            }
169        }
170        return reconnectedEvents;
171    }
172
173    protected boolean refetchDocumentModel(CoreSession session, Object eventProperty) {
174        if (eventProperty instanceof DocumentModel && session != null) {
175            DocumentModel doc = (DocumentModel) eventProperty;
176            if (Boolean.TRUE.equals(doc.getContextData(SKIP_REFETCH_DOCUMENT_CONTEXT_KEY))) {
177                return false;
178            }
179            return true;
180        }
181        return false;
182    }
183
184    @Override
185    public String getName() {
186        return sourceEventBundle.getName();
187    }
188
189    @Override
190    public VMID getSourceVMID() {
191        return sourceEventBundle.getSourceVMID();
192    }
193
194    @Override
195    public boolean hasRemoteSource() {
196        return sourceEventBundle.hasRemoteSource();
197    }
198
199    @Override
200    public boolean isEmpty() {
201        return sourceEventBundle.isEmpty();
202    }
203
204    @Override
205    public Event peek() {
206        return getReconnectedEvents().get(0);
207    }
208
209    @Override
210    public void push(Event event) {
211        throw new UnsupportedOperationException();
212    }
213
214    @Override
215    public int size() {
216        return sourceEventBundle.size();
217    }
218
219    @Override
220    public Iterator<Event> iterator() {
221        return getReconnectedEvents().iterator();
222    }
223
224    @Override
225    public void disconnect() {
226        reconnectedCoreSession = null;
227        reconnectedEvents = null;
228        if (loginCtx != null) {
229            loginCtx.close();
230            loginCtx = null;
231        }
232    }
233
234    @Override
235    public boolean containsEventName(String eventName) {
236        return sourceEventBundle.containsEventName(eventName);
237    }
238
239    public List<String> getEventNames() {
240        List<String> eventNames = new ArrayList<>();
241        for (Event event : sourceEventBundle) {
242            eventNames.add(event.getName());
243        }
244        return eventNames;
245    }
246}