001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.portal.event.impl;
018:
019: import java.lang.reflect.Method;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.apache.avalon.framework.activity.Disposable;
027: import org.apache.avalon.framework.activity.Initializable;
028: import org.apache.avalon.framework.configuration.Configurable;
029: import org.apache.avalon.framework.configuration.Configuration;
030: import org.apache.avalon.framework.configuration.ConfigurationException;
031: import org.apache.avalon.framework.container.ContainerUtil;
032: import org.apache.avalon.framework.context.Context;
033: import org.apache.avalon.framework.context.ContextException;
034: import org.apache.avalon.framework.context.Contextualizable;
035: import org.apache.avalon.framework.logger.AbstractLogEnabled;
036: import org.apache.avalon.framework.service.ServiceException;
037: import org.apache.avalon.framework.service.ServiceManager;
038: import org.apache.avalon.framework.service.ServiceSelector;
039: import org.apache.avalon.framework.service.Serviceable;
040: import org.apache.avalon.framework.thread.ThreadSafe;
041: import org.apache.cocoon.ProcessingException;
042: import org.apache.cocoon.components.ContextHelper;
043: import org.apache.cocoon.portal.PortalService;
044: import org.apache.cocoon.portal.event.Event;
045: import org.apache.cocoon.portal.event.EventConverter;
046: import org.apache.cocoon.portal.event.EventManager;
047: import org.apache.cocoon.portal.event.Publisher;
048: import org.apache.cocoon.portal.event.Receiver;
049: import org.apache.cocoon.portal.event.Register;
050: import org.apache.cocoon.portal.event.Subscriber;
051: import org.apache.cocoon.portal.event.aspect.EventAspect;
052: import org.apache.cocoon.util.ClassUtils;
053: import org.apache.cocoon.util.Deprecation;
054:
055: /**
056: * This is the default implementation of the event manager.
057: *
058: * @author <a href="mailto:cziegeler@s-und-n.de">Carsten Ziegeler</a>
059: * @author <a href="mailto:volker.schmitt@basf-it-services.com">Volker Schmitt</a>
060: *
061: * @version CVS $Id: DefaultEventManager.java 569269 2007-08-24 06:27:40Z cziegeler $
062: */
063: public class DefaultEventManager extends AbstractLogEnabled implements
064: EventManager, Serviceable, Initializable, ThreadSafe,
065: Configurable, Disposable, Contextualizable, Publisher, Register {
066:
067: private final String rootEventType = Event.class.getName();
068: private Class eventClass;
069: /** The list of all subscribers. */
070: private List subscribers = new ArrayList();
071: /** The list of all receivers */
072: private Map receivers = new HashMap();
073:
074: private ServiceManager manager;
075: private Configuration configuration;
076:
077: protected EventAspectChain chain;
078:
079: protected ServiceSelector aspectSelector;
080:
081: protected Context context;
082:
083: /** The portal service */
084: protected PortalService service;
085:
086: /** Introspected receiver classes */
087: protected Map receiverClasses = new HashMap();
088:
089: /**
090: * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
091: */
092: public void service(ServiceManager manager) throws ServiceException {
093: this .manager = manager;
094: this .service = (PortalService) manager
095: .lookup(PortalService.ROLE);
096: }
097:
098: /* (non-Javadoc)
099: * @see org.apache.cocoon.portal.event.EventManager#getPublisher()
100: */
101: public Publisher getPublisher() {
102: return this ;
103: }
104:
105: /* (non-Javadoc)
106: * @see org.apache.cocoon.portal.event.EventManager#getRegister()
107: */
108: public Register getRegister() {
109: return this ;
110: }
111:
112: /**
113: * Helper method to get the current object model
114: */
115: protected Map getObjectModel() {
116: return ContextHelper.getObjectModel(this .context);
117: }
118:
119: /* (non-Javadoc)
120: * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
121: */
122: public void configure(Configuration conf)
123: throws ConfigurationException {
124: this .configuration = conf;
125: }
126:
127: /* (non-Javadoc)
128: * @see org.apache.avalon.framework.activity.Disposable#dispose()
129: */
130: public void dispose() {
131: if (this .manager != null) {
132: if (this .chain != null) {
133: this .chain.dispose(this .aspectSelector);
134: }
135: this .manager.release(this .aspectSelector);
136: this .aspectSelector = null;
137: this .manager.release(this .service);
138: this .service = null;
139: this .manager = null;
140: }
141: }
142:
143: /* (non-Javadoc)
144: * @see org.apache.avalon.framework.activity.Initializable#initialize()
145: */
146: public void initialize() throws Exception {
147: this .eventClass = Class.forName(this .rootEventType);
148: if (this .getLogger().isDebugEnabled()) {
149: this .getLogger().debug(
150: "Initialising eventClass " + this .eventClass);
151: }
152:
153: // FIXME - the following configuration is not portal specific, it's global!
154: // subscribe all configured roles
155: Configuration roles = this .configuration.getChild(
156: "subscriber-roles", false);
157: if (roles != null) {
158: Configuration[] rolesConf = roles.getChildren("role");
159: for (int i = 0; i < rolesConf.length; i++) {
160: final Configuration current = rolesConf[i];
161: final String name = current.getAttribute("name");
162:
163: Subscriber subscriber = null;
164: try {
165: subscriber = (Subscriber) this .manager.lookup(name);
166: Deprecation.logger
167: .warn("Subscriber is deprecated. Please convert the following component to a Receiver: "
168: + subscriber.getClass().getName());
169: this .subscribe(subscriber);
170: } finally {
171: this .manager.release(subscriber);
172: }
173: }
174: }
175: // subscribe all configured classes
176: Configuration classes = this .configuration.getChild(
177: "subscriber-classes", false);
178: if (classes != null) {
179: Configuration[] classesConf = classes.getChildren("class");
180: for (int i = 0; i < classesConf.length; i++) {
181: final Configuration current = classesConf[i];
182: final String name = current.getAttribute("name");
183:
184: Deprecation.logger
185: .warn("Subscriber is deprecated. Please convert the following component to a Receiver: "
186: + name);
187: Subscriber subscriber = (Subscriber) ClassUtils
188: .newInstance(name);
189: ContainerUtil.enableLogging(subscriber, this
190: .getLogger());
191: ContainerUtil.contextualize(subscriber, this .context);
192: ContainerUtil.service(subscriber, this .manager);
193: ContainerUtil.initialize(subscriber);
194: this .subscribe(subscriber);
195: }
196: }
197: // subscribe all configured receiver roles
198: roles = this .configuration.getChild("receiver-roles", false);
199: if (roles != null) {
200: Configuration[] rolesConf = roles.getChildren("role");
201: for (int i = 0; i < rolesConf.length; i++) {
202: final Configuration current = rolesConf[i];
203: final String name = current.getAttribute("name");
204:
205: Receiver receiver = null;
206: try {
207: receiver = (Receiver) this .manager.lookup(name);
208: this .subscribe(receiver);
209: } finally {
210: this .manager.release(receiver);
211: }
212: }
213: }
214: // subscribe all configured receiver classes
215: classes = this .configuration
216: .getChild("receiver-classes", false);
217: if (classes != null) {
218: Configuration[] classesConf = classes.getChildren("class");
219: for (int i = 0; i < classesConf.length; i++) {
220: final Configuration current = classesConf[i];
221: final String name = current.getAttribute("name");
222:
223: Receiver receiver = (Receiver) ClassUtils
224: .newInstance(name);
225: ContainerUtil.enableLogging(receiver, this .getLogger());
226: ContainerUtil.contextualize(receiver, this .context);
227: ContainerUtil.service(receiver, this .manager);
228: ContainerUtil.initialize(receiver);
229: this .subscribe(receiver);
230: }
231: }
232:
233: }
234:
235: /* (non-Javadoc)
236: * @see org.apache.cocoon.portal.event.Publisher#publish(org.apache.cocoon.portal.event.Event)
237: */
238: public void publish(final Event event) {
239: this .send(event);
240: }
241:
242: /* (non-Javadoc)
243: * @see org.apache.cocoon.portal.event.Register#subscribe(org.apache.cocoon.portal.event.Subscriber)
244: */
245: public void subscribe(final Subscriber subscriber) {
246: if (!this .eventClass
247: .isAssignableFrom(subscriber.getEventType())) {
248: throw new RuntimeException("Invalid event type "
249: + subscriber.getEventType() + " for subscriber "
250: + subscriber);
251: }
252:
253: if (this .getLogger().isDebugEnabled()) {
254: this .getLogger().debug(
255: "Subscribing event "
256: + subscriber.getEventType().getName());
257: }
258:
259: // Add to list but prevent duplicate subscriptions
260: if (!this .subscribers.contains(subscriber)) {
261: this .subscribers.add(subscriber);
262: if (this .getLogger().isDebugEnabled()) {
263: this .getLogger().debug(
264: "Subscribed Event "
265: + subscriber.getEventType().getName());
266: this .getLogger().debug(
267: "Subscribers now active: "
268: + this .subscribers.size());
269: }
270: }
271: }
272:
273: /* (non-Javadoc)
274: * @see org.apache.cocoon.portal.event.Register#unsubscribe(org.apache.cocoon.portal.event.Subscriber)
275: */
276: public void unsubscribe(Subscriber subscriber) {
277:
278: if (!this .eventClass
279: .isAssignableFrom(subscriber.getEventType())) {
280: throw new RuntimeException("Invalid event type "
281: + subscriber.getEventType() + " for unsubscribing "
282: + subscriber);
283: }
284: if (this .subscribers.contains(subscriber)) {
285: this .subscribers.remove(subscriber);
286: if (this .getLogger().isDebugEnabled()) {
287: this .getLogger().debug(
288: "Unsubscribed Event "
289: + subscriber.getEventType().getName());
290: this .getLogger().debug(
291: "Subscribers now active: "
292: + this .subscribers.size());
293: }
294: } else {
295: this .getLogger().warn(
296: "Subscriber " + subscriber + " not found");
297: }
298: }
299:
300: /* (non-Javadoc)
301: * @see org.apache.cocoon.portal.event.EventManager#processEvents()
302: */
303: public void processEvents() throws ProcessingException {
304: if (this .configuration != null) {
305: synchronized (this ) {
306: if (this .configuration != null) {
307: try {
308: this .aspectSelector = (ServiceSelector) this .manager
309: .lookup(EventAspect.ROLE + "Selector");
310: this .chain = new EventAspectChain();
311: this .chain.configure(this .aspectSelector,
312: this .configuration
313: .getChild("event-aspects"));
314: } catch (ConfigurationException ce) {
315: throw new ProcessingException(
316: "Unable configure component.", ce);
317: } catch (ServiceException ce) {
318: throw new ProcessingException(
319: "Unable to lookup component.", ce);
320: }
321: this .configuration = null;
322: }
323: }
324: }
325: DefaultEventAspectContext context = new DefaultEventAspectContext(
326: this .chain);
327: EventConverter converter = null;
328: PortalService service = null;
329: try {
330: service = (PortalService) this .manager
331: .lookup(PortalService.ROLE);
332: converter = (EventConverter) this .manager
333: .lookup(EventConverter.ROLE);
334: Publisher publisher = this .getPublisher();
335:
336: converter.start();
337:
338: // Invoke aspects
339: context.setEventPublisher(publisher);
340: context.setObjectModel(this .getObjectModel());
341: context.setEventConverter(converter);
342: context.invokeNext(service);
343:
344: converter.finish();
345:
346: } catch (ServiceException ce) {
347: throw new ProcessingException(
348: "Unable to lookup component.", ce);
349: } finally {
350: this .manager.release(converter);
351: this .manager.release(service);
352: }
353:
354: }
355:
356: /**
357: * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
358: */
359: public void contextualize(Context context) throws ContextException {
360: this .context = context;
361: }
362:
363: /**
364: * @see org.apache.cocoon.portal.event.EventManager#send(org.apache.cocoon.portal.event.Event)
365: */
366: public void send(Event event) {
367: if (this .getLogger().isDebugEnabled()) {
368: this .getLogger().debug(
369: "Publishing event " + event.getClass());
370: }
371: for (Iterator e = this .subscribers.iterator(); e.hasNext();) {
372: Subscriber subscriber = (Subscriber) e.next();
373: if (subscriber.getEventType().isAssignableFrom(
374: event.getClass())
375: && (subscriber.getFilter() == null || subscriber
376: .getFilter().filter(event))) {
377: if (this .getLogger().isDebugEnabled()) {
378: this .getLogger().info(
379: "Informing subscriber " + subscriber
380: + " of event " + event.getClass());
381: }
382: subscriber.inform(event);
383: }
384: }
385: for (Iterator re = this .receivers.entrySet().iterator(); re
386: .hasNext();) {
387: final Map.Entry current = (Map.Entry) re.next();
388: final Receiver receiver = (Receiver) current.getKey();
389: final List methodInfos = (List) current.getValue();
390: boolean found = false;
391: final Iterator ci = methodInfos.iterator();
392: while (!found && ci.hasNext()) {
393: final MethodInfo info = (MethodInfo) ci.next();
394: if (info.eventClass.isAssignableFrom(event.getClass())) {
395: if (this .getLogger().isDebugEnabled()) {
396: this .getLogger().info(
397: "Informing receiver " + receiver
398: + " of event "
399: + event.getClass());
400: }
401: try {
402: info.method.invoke(receiver, new Object[] {
403: event, this .service });
404: } catch (Exception ignore) {
405: this .getLogger().warn(
406: "Exception during event dispatching on receiver "
407: + receiver + " and event "
408: + event, ignore);
409: }
410: found = true;
411: }
412: }
413: }
414: }
415:
416: protected static final class MethodInfo {
417:
418: public Class eventClass;
419: public Method method;
420: }
421:
422: protected synchronized List introspect(Class receiverClass) {
423: List result = (List) this .receiverClasses.get(receiverClass
424: .getName());
425: if (result == null) {
426: result = new ArrayList();
427: Method[] methods = receiverClass.getMethods();
428: for (int i = 0; i < methods.length; i++) {
429: final Method current = methods[i];
430: if (current.getName().equals("inform")) {
431: final Class[] params = current.getParameterTypes();
432: if (params.length == 2
433: && params[1].getName().equals(
434: PortalService.class.getName())) {
435: if (this .eventClass.isAssignableFrom(params[0])) {
436: MethodInfo info = new MethodInfo();
437: info.eventClass = params[0];
438: info.method = current;
439: result.add(info);
440: }
441: }
442: }
443: }
444: if (result.size() == 0) {
445: result = null;
446: }
447: }
448: return result;
449: }
450:
451: /**
452: * @see org.apache.cocoon.portal.event.EventManager#subscribe(org.apache.cocoon.portal.event.Receiver)
453: */
454: public void subscribe(Receiver receiver) {
455: List infos = this .introspect(receiver.getClass());
456: if (infos == null) {
457: throw new RuntimeException("Invalid event receiver type: "
458: + receiver);
459: }
460:
461: // Add to list but prevent duplicate subscriptions
462: List eventClassesForReceiver = (List) this .receivers
463: .get(receiver);
464: if (eventClassesForReceiver == null) {
465: this .receivers.put(receiver, infos);
466: }
467: if (this .getLogger().isDebugEnabled()) {
468: for (int i = 0; i < infos.size(); i++) {
469: this
470: .getLogger()
471: .debug(
472: "Receiver "
473: + receiver
474: + " subscribed for event: "
475: + ((MethodInfo) infos.get(i)).eventClass
476: .getName());
477: }
478: }
479: }
480:
481: /**
482: * @see org.apache.cocoon.portal.event.EventManager#unsubscribe(org.apache.cocoon.portal.event.Receiver)
483: */
484: public void unsubscribe(Receiver receiver) {
485: if (this .getLogger().isDebugEnabled()) {
486: this .getLogger().debug(
487: "Receiver " + receiver + " unsubscribed.");
488: }
489: this.receivers.remove(receiver);
490: }
491:
492: }
|