001/*
002 * (C) Copyright 2006-2011 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 *     Thierry Delprat
018 */
019
020package org.nuxeo.ecm.core.event.jms;
021
022import java.io.Serializable;
023import java.rmi.dgc.VMID;
024import java.security.Principal;
025import java.util.ArrayList;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.common.utils.Path;
033import org.nuxeo.ecm.core.api.CoreSession;
034import org.nuxeo.ecm.core.api.DocumentModel;
035import org.nuxeo.ecm.core.api.DocumentRef;
036import org.nuxeo.ecm.core.api.IdRef;
037import org.nuxeo.ecm.core.api.NuxeoException;
038import org.nuxeo.ecm.core.api.PathRef;
039import org.nuxeo.ecm.core.api.SimplePrincipal;
040import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
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.impl.DocumentEventContext;
045import org.nuxeo.ecm.core.event.impl.EventBundleImpl;
046import org.nuxeo.ecm.core.event.impl.EventContextImpl;
047import org.nuxeo.ecm.core.event.impl.EventImpl;
048
049/**
050 * Serializable representation of an {@link EventBundle} that is used for JMS forwarding.
051 *
052 * @author Thierry Delprat
053 */
054public class SerializableEventBundle implements Serializable {
055
056    private static final long serialVersionUID = 1L;
057
058    private static final Log log = LogFactory.getLog(SerializableEventBundle.class);
059
060    protected final List<Map<String, Serializable>> serialisableEvents;
061
062    protected final String eventBundleName;
063
064    protected final VMID sourceVMID;
065
066    protected boolean isDocumentEventContext = false;
067
068    protected String coreInstanceName;
069
070    public SerializableEventBundle(EventBundle events) {
071        eventBundleName = events.getName();
072        sourceVMID = events.getSourceVMID();
073        serialisableEvents = new ArrayList<Map<String, Serializable>>();
074
075        for (Event event : events) {
076            if (event.isLocal()) {
077                // local event should not be exported to JMS
078                continue;
079            }
080            CoreSession evtSession = event.getContext().getCoreSession();
081
082            String repoName = null;
083            if (evtSession != null) {
084                repoName = evtSession.getRepositoryName();
085                if (coreInstanceName == null) {
086                    coreInstanceName = repoName;
087                }
088            }
089
090            Map<String, Serializable> serializableEvent = new HashMap<String, Serializable>();
091
092            serializableEvent.put("name", event.getName());
093            serializableEvent.put("time", Long.toString(event.getTime()));
094            serializableEvent.put("contextProperties", (Serializable) event.getContext().getProperties());
095            if (evtSession != null) {
096                serializableEvent.put("contextSessionId", evtSession.getSessionId());
097            }
098            serializableEvent.put("principal", event.getContext().getPrincipal().getName());
099
100            serializableEvent.put("contextSessionRepositoryName", repoName);
101
102            if (event.getContext() instanceof DocumentEventContext) {
103                serializableEvent.put("isDocumentEventContext", true);
104            } else {
105                serializableEvent.put("isDocumentEventContext", false);
106            }
107
108            Object[] args = event.getContext().getArguments();
109            List<Serializable> listArgs = new ArrayList<Serializable>();
110            for (Object arg : args) {
111                if (arg instanceof DocumentModel) {
112                    DocumentModel doc = (DocumentModel) arg;
113                    String strRepresentation = doc.getRepositoryName() + ":" + doc.getId() + ":" + doc.getType() + ":"
114                            + doc.getPathAsString();
115                    listArgs.add("DOCREF:" + strRepresentation);
116                } else if (arg instanceof Serializable) {
117                    log.debug("Adding serializable argument of class " + arg.getClass().getCanonicalName());
118                    listArgs.add((Serializable) arg);
119                } else {
120                    listArgs.add(null);
121                }
122            }
123
124            serializableEvent.put("args", (Serializable) listArgs);
125            serialisableEvents.add(serializableEvent);
126        }
127    }
128
129    // Should not be necessary since this is noww done in CoreSession
130    protected Map<String, Serializable> filterContextProperties(Map<String, Serializable> properties) {
131        Map<String, Serializable> serializableProps = new HashMap<String, Serializable>();
132
133        for (String key : properties.keySet()) {
134            Object value = properties.get(key);
135            if (value instanceof Serializable) {
136                Serializable serializableValue = (Serializable) value;
137                serializableProps.put(key, serializableValue);
138            } else {
139                log.error("ContextMap contains non serializable object under key " + key);
140            }
141        }
142        return serializableProps;
143    }
144
145    public VMID getSourceVMID() {
146        return sourceVMID;
147    }
148
149    public String getEventBundleName() {
150        return eventBundleName;
151    }
152
153    public String getCoreInstanceName() {
154        return coreInstanceName;
155    }
156
157    public class EventBundleRelayedViaJMS extends EventBundleImpl {
158        private static final long serialVersionUID = 1L;
159
160        public EventBundleRelayedViaJMS() {
161            // init VMID
162            super(sourceVMID);
163        }
164    }
165
166    @SuppressWarnings("unchecked")
167    public EventBundle reconstructEventBundle(CoreSession session) throws CannotReconstruct {
168
169        if (!session.getRepositoryName().equals(coreInstanceName)) {
170            throw new CannotReconstruct("This session can not be used on this Bundle");
171        }
172        EventBundle bundle = new EventBundleRelayedViaJMS();
173
174        if (serialisableEvents == null) {
175            return null;
176        }
177
178        for (Map<String, Serializable> evt : serialisableEvents) {
179
180            String eventName = (String) evt.get("name");
181            Long time = Long.parseLong((String) evt.get("time"));
182
183            Map<String, Serializable> ctxProperties = (Map<String, Serializable>) evt.get("contextProperties");
184            Principal principal = new SimplePrincipal((String) evt.get("principal"));
185
186            List<Serializable> listArgs = (List<Serializable>) evt.get("args");
187
188            Object[] args = new Object[listArgs.size()];
189
190            int idx = 0;
191            for (Serializable sArg : listArgs) {
192                Object value;
193                if (sArg == null) {
194                    value = null;
195                } else if (sArg instanceof String) {
196                    String arg = (String) sArg;
197                    if (arg.startsWith("DOCREF:")) {
198                        String[] part = arg.split(":");
199                        DocumentRef idRef = new IdRef(part[2]);
200                        DocumentModel doc = null;
201                        if (session != null && session.exists(idRef)) {
202                            doc = session.getDocument(idRef);
203                        } else {
204                            String parentPath = new Path(part[4]).removeLastSegments(1).toString();
205                            doc = new DocumentModelImpl(session.getSessionId(), part[3], part[2], new Path(part[4]),
206                                    null, idRef, new PathRef(parentPath), null, null, null, null);
207                        }
208                        value = doc;
209                    } else {
210                        value = arg;
211                    }
212                } else {
213                    value = sArg;
214                }
215                args[idx] = value;
216                idx++;
217            }
218
219            EventContext ctx;
220            if ((Boolean) evt.get("isDocumentEventContext")) {
221                ctx = new DocumentEventContext(session, principal, (DocumentModel) args[0], (DocumentRef) args[1]);
222                // XXX we loose other args ...
223            } else {
224                ctx = new EventContextImpl(session, principal);
225                ((EventContextImpl) ctx).setArgs(args);
226            }
227
228            ctx.setProperties(ctxProperties);
229            Event e = new EventImpl(eventName, ctx, Event.FLAG_NONE, time);
230            bundle.push(e);
231        }
232        return bundle;
233    }
234
235    public static class CannotReconstruct extends NuxeoException {
236
237        private static final long serialVersionUID = 1L;
238
239        public CannotReconstruct(String message) {
240            super(message);
241        }
242
243    }
244
245}