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