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