001: /*
002: * <copyright>
003: *
004: * Copyright 2002-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.servicediscovery.plugin;
028:
029: import java.io.File;
030: import java.net.URL;
031: import java.util.Collection;
032:
033: import org.cougaar.core.agent.service.alarm.Alarm;
034: import org.cougaar.core.service.community.Community;
035: import org.cougaar.core.service.community.CommunityChangeEvent;
036: import org.cougaar.core.service.community.CommunityChangeListener;
037: import org.cougaar.core.service.community.CommunityResponse;
038: import org.cougaar.core.service.community.CommunityResponseListener;
039: import org.cougaar.core.service.community.CommunityService;
040: import org.cougaar.core.plugin.ComponentPlugin;
041: import org.cougaar.core.service.LoggingService;
042: import org.cougaar.servicediscovery.description.ProviderDescription;
043: import org.cougaar.servicediscovery.description.ProviderDescriptionImpl;
044: import org.cougaar.servicediscovery.service.RegistrationService;
045: import org.cougaar.util.Configuration;
046:
047: /**
048: * Simplified version of SDRegistrationPlugin that registers this agent
049: * using the <agent name>-profile.owl file if any in the plugin parameter-named YP agent.
050: * <p>
051: * This version of the plugin is somewhat simplified -- fewer error checks for example. It
052: * does not create {@link org.cougaar.servicediscovery.description.ProviderCapabilities} objects (used by more complex SDProviderPlugins).
053: * <p>
054: * First plugin argument is the name of the agent hosting the YP that we will register with.
055: *<p>
056: * The {@link SDRegistrationPluginBase} and extensions are more complex; they handle more errors,
057: * dynamic changes to the services provided, deal with society quiescence issues, create
058: * ProviderCapabilities, etc.
059: * Complex applications will likely want to use those plugins, but most users will prefer
060: * to extend this plugin. Extensions should over-rider the getServiceProfileURL() method,
061: * to specify where their application's service profiles are located. For an example,
062: * see the pizza application.
063: *
064: * @property org.cougaar.servicediscovery.plugin.SimpleRegistrationGracePeriod is the number
065: * of minutes after startup, during which we ignore SD registration Warnings, to allow the YP
066: * to start up. After this we complain more loudly. Default is 5 minutes.
067: *
068: * @see org.cougaar.pizza.plugin.SDRegistrationPlugin
069: **/
070: public class SimpleSDRegistrationPlugin extends ComponentPlugin {
071:
072: private static final String REGISTRATION_GRACE_PERIOD_PROPERTY = "org.cougaar.servicediscovery.plugin.SimpleRegistrationGracePeriod";
073:
074: private static final int DEFAULT_WARNING_SUPPRESSION_INTERVAL = 5; // in minutes
075:
076: private static final int WARNING_SUPPRESSION_INTERVAL;
077:
078: static {
079: WARNING_SUPPRESSION_INTERVAL = Integer.getInteger(
080: REGISTRATION_GRACE_PERIOD_PROPERTY,
081: DEFAULT_WARNING_SUPPRESSION_INTERVAL).intValue();
082: }
083:
084: private long warningCutoffTime = 0;
085:
086: protected static final String OWL_IDENTIFIER = ".profile.owl";
087:
088: private Alarm retryAlarm;
089:
090: // Make log service protected, so extensions can use it.
091: protected LoggingService log;
092:
093: private RegistrationService registrationService = null;
094: private CommunityService communityService = null;
095:
096: private YPInfo ypInfo;
097:
098: private ProviderDescription provD = null;
099:
100: public void setCommunityService(CommunityService cs) {
101: this .communityService = cs;
102: }
103:
104: public void setLoggingService(LoggingService log) {
105: this .log = log;
106: }
107:
108: public void setRegistrationService(RegistrationService rs) {
109: registrationService = rs;
110: }
111:
112: /**
113: * When the agent moves, we don't want dangling callbacks - so clear them. On resume, we'll just re-register
114: * from scratch, since we can't otherwise recover where we'd gotten to.
115: */
116: public void suspend() {
117: super .suspend();
118:
119: if (ypInfo != null) {
120: // Remove all community change notifications
121: if (log.isInfoEnabled()) {
122: log.info("removing community change listeners.");
123: }
124:
125: ypInfo.clearCommunity();
126: }
127: }
128:
129: /**
130: * This plugin has no subscriptions. It will execute exactly once, since the
131: * infrastructure calls the execute() method once at plugin startup.
132: */
133: protected void setupSubscriptions() {
134: }
135:
136: /**
137: * If this agent has a -profile.owl file, then ask for a handle on the named (in the only plugin parameter)
138: * YP agent's YP community. Once we have the community (may be
139: * a subsequent execute when our CommunityListener tells us
140: * we found the YP community), we call initialRegister
141: * to register this agent in the YP.
142: */
143: protected void execute() {
144: // Does this agent have a service profile
145: if (isProvider()) {
146: // If we haven't already gotten the YP info
147: if (ypInfo == null) {
148: // get the yp info
149: initYPInfo();
150: // Find our YP community
151: findYPCommunity();
152: }
153:
154: if (ypInfo.readyToRegister()) {
155: if (log.isDebugEnabled()) {
156: log.debug("Registering: " + getAgentIdentifier()
157: + " with "
158: + ypInfo.getCommunity().getName());
159: }
160: initialRegister();
161: }
162: }
163: }
164:
165: /**
166: * Do the actual registration of this provider in the YP. Get the
167: * ProviderDescription, and then invoke the UDDI4JRegistrationService, with
168: * a Callback to notify us when the registration completes (or fails).
169: *<p>
170: * Extenders might choose to over-ride this in an attempt to avoid
171: * using ProviderDescriptions at all.
172: */
173: protected void initialRegister() {
174: // FIXME: Is this check necessary?
175: if (!ypInfo.readyToRegister()) {
176: if (log.isDebugEnabled()) {
177: log.debug("Exiting initialRegister early - "
178: + " ypInfo not ready - " + " community "
179: + ypInfo.getCommunity().getName()
180: + " isRegistered " + ypInfo.getIsRegistered()
181: + " pendingRegistration "
182: + ypInfo.getPendingRegistration());
183: }
184: return;
185: } // end block to handle ypInfo not ready to register.
186:
187: // Wrap whole call to YP in a try/catch....
188: try {
189: // Get our ProviderDescription (what we're registering)
190: final ProviderDescription pd = getPD();
191:
192: if (pd == null) {
193: ypInfo.setIsRegistered(false);
194: ypInfo.setPendingRegistration(false); // okay to try again
195:
196: retryErrorLog("Problem getting ProviderDescription -- transient Jena error?"
197: + " Unable to add registration to "
198: + ypInfo.getCommunity().getName()
199: + ", try again later.");
200:
201: return;
202: }
203:
204: ypInfo.setPendingRegistration(true);
205:
206: // Create the callback - by which the YP will tell us when it finishes
207: RegistrationService.Callback cb = new RegistrationService.Callback() {
208:
209: /**
210: * YP Calls when the registration call completes. Note that the
211: * Object argument is only useful for debugging.
212: */
213: public void invoke(Object o) {
214: if (log.isInfoEnabled()) {
215: boolean success = ((Boolean) o).booleanValue();
216: log.info(pd.getProviderName()
217: + " initialRegister success = "
218: + success + " with "
219: + ypInfo.getCommunity().getName());
220: }
221:
222: ypInfo.setIsRegistered(true);
223: ypInfo.setPendingRegistration(false);
224: ypInfo.clearCommunity();
225:
226: retryAlarm = null;
227:
228: getBlackboardService().signalClientActivity();
229: } // end of invoke()
230:
231: /**
232: * YP Calls when there was an error trying to register the provider.
233: */
234: public void handle(Exception e) {
235: ypInfo.setPendingRegistration(false); // okay to try again
236: ypInfo.setIsRegistered(false);
237:
238: retryErrorLog(
239: "Problem adding ProviderDescription to "
240: + ypInfo.getCommunity().getName()
241: + ", try again later: "
242: + getAgentIdentifier(), e);
243: }
244: }; // end of Callback definition
245:
246: // actually submit the request: register at the given community,
247: // with the given Provider information, calling back to us using
248: // the given callback
249: registrationService.addProviderDescription(ypInfo
250: .getCommunity(), pd, cb);
251: } catch (RuntimeException e) {
252: ypInfo.setIsRegistered(false);
253: ypInfo.setPendingRegistration(false); // okay to try again
254:
255: retryErrorLog("Problem adding ProviderDescription to "
256: + ypInfo.getCommunity().getName()
257: + ", try again later: " + getAgentIdentifier(), e);
258: } // end of try/catch that actually does the registration (via callback)
259: } // end of initialRegister
260:
261: /**
262: * Issue an asynchronous query to the CommunityService, looking for the
263: * YP Community named by {@link #getYPCommunityName(String)}, with a registered
264: * {@link org.cougaar.servicediscovery.plugin.SimpleSDRegistrationPlugin.YPCommunityResponseListener} to learn about changes.
265: */
266: protected void findYPCommunity() {
267: Community ypCommunity = communityService.getCommunity(
268: getYPCommunityName(ypInfo.getAgentName()),
269: new YPCommunityResponseListener(ypInfo));
270:
271: if (ypCommunity != null) {
272: ypInfo.setCommunity(ypCommunity);
273: if (log.isDebugEnabled()) {
274: log.debug("Registering: " + getAgentIdentifier()
275: + " with " + ypInfo.getCommunity().getName());
276: }
277: initialRegister();
278: } else if (log.isDebugEnabled()) {
279: log.debug("waiting on community info "
280: + getYPCommunityName(ypInfo.getAgentName()));
281: }
282: }
283:
284: /**
285: * Create the YPInfo object for this instance.
286: * Takes the first plugin parameter as the name of the agent hosting the YP that
287: * we will register with.
288: *<p>
289: * Extenders might want a different mechanism for specifying the YP server
290: * to register with.
291: */
292: protected void initYPInfo() {
293: Collection params = getParameters();
294:
295: if (params.isEmpty()) {
296: IllegalArgumentException iae = new IllegalArgumentException();
297: log.error("SDRegistrationPlugin: no YP agent parameter"
298: + " - unable to register.", iae);
299: } else {
300: ypInfo = new YPInfo((String) params.iterator().next(),
301: null, false, false);
302: }
303:
304: if (log.isDebugEnabled())
305: log.debug(": ypInfo = " + ypInfo);
306: }
307:
308: /**
309: * Construct the name of the YP Community hosted at the given named agent.
310: * This version produces <AgentName>-YPCOMMUNITY.
311: *<p>
312: * Extenders could use a different convention for naming YP Communities.
313: *
314: * @param ypAgentName String name of the agent hosting a YP Server
315: * @return String name of the YP Community to look for.
316: */
317: protected String getYPCommunityName(String ypAgentName) {
318: // For now assume every YP represented by a YPCommunity called
319: // <yp agent name>-YPCOMMUNITY
320: return ypAgentName + "-YPCOMMUNITY";
321: }
322:
323: /**
324: * Get a ProviderDescription for this agent, us it to register in the YP. We do so by
325: * looking for a file in the {@link #getServiceProfileURL()} directory,
326: * named <AgentName>+{@link #OWL_IDENTIFIER}, and passing it through Jena.
327: *<p>
328: * Extenders could have an alternate mechanism for creating a ProviderDescription, including
329: * hard-coded content, messaging based, etc.
330: *
331: * @return ProviderDescription to register, null if unable to parse the provider description
332: */
333: protected ProviderDescription getPD() {
334: if (provD == null) {
335: if (log.isDebugEnabled()) {
336: log.debug(": getPD() parsing OWL.");
337: }
338:
339: ProviderDescription pd = new ProviderDescriptionImpl();
340: try {
341: URL serviceProfileURL = Configuration
342: .urlify(getServiceProfileURL().toString());
343: boolean ok = pd.parseOWL(serviceProfileURL,
344: getAgentIdentifier() + OWL_IDENTIFIER);
345:
346: // We have to check the return status because
347: // occasionally Jena seems to hiccup on the parse...
348: if (ok && (pd.getProviderName() != null)) {
349: if (log.isDebugEnabled()) {
350: log.debug(": getPD() successfully parsed OWL.");
351: }
352:
353: provD = pd;
354: } else {
355: if (log.isDebugEnabled()) {
356: log.debug(": getPD() unable to parse OWL."
357: + " ok = " + ok);
358: }
359: }
360: } catch (java.util.ConcurrentModificationException cme) {
361: // Jena can do a concurrent mod exception. See bug 3052
362: // Leave provD uninitialized
363: if (log.isDebugEnabled()) {
364: log
365: .debug(": getPD() ConcurrentModificationException - "
366: + cme);
367: }
368: } catch (java.net.MalformedURLException mue) {
369: log.error(
370: "getPD() couldn't find directory for service profiles, starting from "
371: + getServiceProfileURL(), mue);
372: }
373: }
374: return provD;
375: }
376:
377: /**
378: * Get the time (in millis) after which startup errors should be logged at ERROR.
379: * @return real time in millis after which transient errors are logged loudly
380: */
381: private long getWarningCutOffTime() {
382: if (warningCutoffTime == 0) {
383: warningCutoffTime = System.currentTimeMillis()
384: + WARNING_SUPPRESSION_INTERVAL * 60000;
385: }
386:
387: return warningCutoffTime;
388: }
389:
390: /**
391: * Log the given message, indicating we will retry, and set an Alarm to ensure we do.
392: * When an error occurs, but we'll be retrying later, treat it as a DEBUG
393: * at first. After a while it becomes an error.
394: */
395: private void retryErrorLog(String message) {
396: retryErrorLog(message, null);
397: }
398:
399: /**
400: * Log the given message and error, indicating we will retry, and set an Alarm to ensure we do.
401: * When an error occurs, but we'll be retrying later, treat it as a DEBUG
402: * at first. After a while it becomes an error.
403: */
404: private void retryErrorLog(String message, Throwable e) {
405:
406: // Note that we want this to be random because.. FIXME!!!!!
407: long absTime = getAlarmService().currentTimeMillis()
408: + (int) (Math.random() * 10000) + 1000;
409:
410: retryAlarm = new RetryAlarm(absTime);
411: getAlarmService().addAlarm(retryAlarm);
412:
413: if (System.currentTimeMillis() > getWarningCutOffTime()) {
414: if (e == null)
415: log.error(message);
416: else
417: log.error(message, e);
418: } else if (log.isDebugEnabled()) {
419: if (e == null)
420: log.debug(message);
421: else
422: log.debug(message, e);
423: }
424: }
425:
426: /**
427: * This agent is a provider if there is a provider file for it.
428: */
429: protected boolean isProvider() {
430: return getProviderFile().exists();
431: }
432:
433: /**
434: * Get the OWL service provider file named after this agent, if any.
435: */
436: private File getProviderFile() {
437: String owlFileName = getAgentIdentifier().toString()
438: + OWL_IDENTIFIER;
439: return new File(getServiceProfileURL().getFile() + owlFileName);
440: }
441:
442: /**
443: * Get the URL for the service profiles directory for this application.
444: * This is the only method application specific versions of this plugin
445: * will likely over-ride.
446: */
447: protected URL getServiceProfileURL() {
448: try {
449: return new URL(Configuration.getInstallURL()
450: + File.separator + "servicediscovery"
451: + File.separator + "data" + File.separator
452: + "serviceprofiles" + File.separator);
453: } catch (java.net.MalformedURLException mue) {
454: log
455: .error(
456: "Exception constructing service profile URL",
457: mue);
458: return null;
459: }
460: }
461:
462: /**
463: * Alarm used to retry registration when a previous error caused it to fail.
464: */
465: private class RetryAlarm implements Alarm {
466: private long expiresAt;
467: private boolean expired = false;
468:
469: public RetryAlarm(long expirationTime) {
470: expiresAt = expirationTime;
471: }
472:
473: public long getExpirationTime() {
474: return expiresAt;
475: }
476:
477: public synchronized void expire() {
478: if (!expired) {
479: expired = true;
480: getBlackboardService().signalClientActivity();
481: }
482: }
483:
484: public boolean hasExpired() {
485: return expired;
486: }
487:
488: public synchronized boolean cancel() {
489: boolean was = expired;
490: expired = true;
491: return was;
492: }
493:
494: public String toString() {
495: return "<RetryAlarm " + expiresAt
496: + (expired ? "(Expired) " : " ")
497: + "for SDCommunityBasedRegistrationPlugin at "
498: + getAgentIdentifier() + ">";
499: }
500: }
501:
502: /**
503: * Local store of all data related to a YP registration: the serve name, communinty,
504: * ChangeListender, and registration state.
505: */
506: private class YPInfo {
507: private String myYPAgentName;
508: private Community myYPCommunity;
509: private YPCommunityChangeListener myCommunityListener;
510: private boolean myIsRegistered;
511: private boolean myPendingRegistration;
512:
513: public YPInfo(String ypAgentName, Community ypCommunity,
514: boolean isRegistered, boolean pendingRegistration) {
515: myYPAgentName = ypAgentName;
516: myYPCommunity = ypCommunity;
517: myIsRegistered = isRegistered;
518: myPendingRegistration = pendingRegistration;
519: }
520:
521: public String getAgentName() {
522: return myYPAgentName;
523: }
524:
525: public void setAgentName(String ypAgentName) {
526: if (ypAgentName == null) {
527: clearCommunity();
528: } else {
529: myYPAgentName = ypAgentName;
530: }
531: }
532:
533: public Community getCommunity() {
534: return myYPCommunity;
535: }
536:
537: public void setCommunity(Community ypCommunity) {
538: if (ypCommunity == null) {
539: clearCommunity();
540: } else {
541: if (myYPCommunity == null) {
542: if (log.isDebugEnabled()) {
543: log.debug("adding listener for " + ypCommunity);
544: }
545: myYPCommunity = ypCommunity;
546:
547: // First time so set up change listener
548: myCommunityListener = new YPCommunityChangeListener(
549: this );
550: communityService.addListener(myCommunityListener);
551: } else {
552: myYPCommunity = ypCommunity;
553: }
554: }
555: }
556:
557: public void clearCommunity() {
558: if (log.isDebugEnabled()) {
559: log.debug("removing listener for " + myYPCommunity);
560: }
561: myYPCommunity = null;
562: if (myCommunityListener != null) {
563: communityService.removeListener(myCommunityListener);
564: }
565: }
566:
567: public boolean getIsRegistered() {
568: return myIsRegistered;
569: }
570:
571: public void setIsRegistered(boolean isRegistered) {
572: if ((myIsRegistered) && (!isRegistered)
573: && (log.isDebugEnabled())) {
574: RuntimeException re = new RuntimeException();
575: log.debug(
576: "setIsRegistered() going from true to false.",
577: re);
578: }
579: myIsRegistered = isRegistered;
580: }
581:
582: public boolean getPendingRegistration() {
583: return myPendingRegistration;
584: }
585:
586: public void setPendingRegistration(boolean pendingRegistration) {
587: myPendingRegistration = pendingRegistration;
588: }
589:
590: public boolean readyToRegister() {
591: return ((getCommunity() != null) && (!getIsRegistered()) && (!getPendingRegistration()));
592: }
593: }
594:
595: /**
596: * ResponseListener to listen for our CommunityService search for the YP Community.
597: */
598: private class YPCommunityResponseListener implements
599: CommunityResponseListener {
600: private YPInfo ypInfo;
601:
602: public YPCommunityResponseListener(YPInfo info) {
603: ypInfo = info;
604: }
605:
606: public void getResponse(CommunityResponse resp) {
607: if (log.isDebugEnabled()) {
608: log.debug("got Community info for "
609: + (Community) resp.getContent());
610: }
611:
612: Community ypCommunity = (Community) resp.getContent();
613:
614: ypInfo.setCommunity(ypCommunity);
615: getBlackboardService().signalClientActivity();
616: }
617: }
618:
619: /**
620: * ChangeListener to watch for changes at the YP Community.
621: */
622: private class YPCommunityChangeListener implements
623: CommunityChangeListener {
624: private YPInfo ypInfo;
625: String communityName;
626:
627: public YPCommunityChangeListener(YPInfo info) {
628: ypInfo = info;
629: communityName = ypInfo.getCommunity().getName();
630: }
631:
632: public void communityChanged(CommunityChangeEvent event) {
633: Community ypCommunity = event.getCommunity();
634:
635: // Paranoia code - bug in community code seems to lead to
636: // notifications with null communities.
637: // FIXME: This could be a Java assert
638: if (ypCommunity == null) {
639: if (log.isDebugEnabled()) {
640: log
641: .debug("received Community change info for a null community");
642: }
643: return;
644: }
645:
646: if (log.isDebugEnabled()) {
647: log.debug("got Community change info for "
648: + ypCommunity);
649: }
650:
651: if (ypCommunity.getName().equals(getCommunityName())) {
652: ypInfo.setCommunity(ypCommunity);
653:
654: if (ypInfo.readyToRegister()) {
655: if (log.isDebugEnabled()) {
656: log.debug("signalClientActivity for "
657: + ypCommunity);
658: }
659:
660: if (getBlackboardService() == null) {
661: if (log.isWarnEnabled())
662: log
663: .warn("ignoring change notification "
664: + " - getBlackboardService() returned null");
665: ypInfo.clearCommunity();
666: } else {
667: getBlackboardService().signalClientActivity();
668: }
669: }
670: } else if (log.isDebugEnabled()) {
671: log.debug("ignoring CommunityChangeEvent for "
672: + ypCommunity.getName() + " - listening for - "
673: + getCommunityName());
674: }
675:
676: }
677:
678: public String getCommunityName() {
679: return communityName;
680: }
681: }
682: }
|