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