View Javadoc

1   package com.atlassian.plugin.osgi.factory;
2   
3   import com.atlassian.plugin.AutowireCapablePlugin;
4   import com.atlassian.plugin.IllegalPluginStateException;
5   import com.atlassian.plugin.InstallationMode;
6   import com.atlassian.plugin.ModuleDescriptor;
7   import com.atlassian.plugin.PluginArtifact;
8   import com.atlassian.plugin.PluginState;
9   import com.atlassian.plugin.event.PluginEventListener;
10  import com.atlassian.plugin.event.PluginEventManager;
11  import com.atlassian.plugin.event.events.PluginContainerFailedEvent;
12  import com.atlassian.plugin.event.events.PluginContainerRefreshedEvent;
13  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
14  import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
15  import com.atlassian.plugin.event.events.PluginRefreshedEvent;
16  import com.atlassian.plugin.impl.AbstractPlugin;
17  import com.atlassian.plugin.module.ContainerAccessor;
18  import com.atlassian.plugin.module.ContainerManagedPlugin;
19  import com.atlassian.plugin.osgi.container.OsgiContainerException;
20  import com.atlassian.plugin.osgi.container.OsgiContainerManager;
21  import com.atlassian.plugin.osgi.event.PluginServiceDependencyWaitEndedEvent;
22  import com.atlassian.plugin.osgi.event.PluginServiceDependencyWaitStartingEvent;
23  import com.atlassian.plugin.osgi.event.PluginServiceDependencyWaitTimedOutEvent;
24  import com.atlassian.plugin.osgi.external.ListableModuleDescriptorFactory;
25  import com.atlassian.plugin.util.PluginUtils;
26  import com.google.common.annotations.VisibleForTesting;
27  import org.dom4j.Element;
28  import org.osgi.framework.Bundle;
29  import org.osgi.framework.BundleContext;
30  import org.osgi.framework.BundleEvent;
31  import org.osgi.framework.BundleException;
32  import org.osgi.framework.BundleListener;
33  import org.osgi.framework.ServiceReference;
34  import org.osgi.framework.SynchronousBundleListener;
35  import org.osgi.service.packageadmin.PackageAdmin;
36  import org.osgi.util.tracker.ServiceTracker;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  import java.io.InputStream;
41  import java.net.URL;
42  import java.util.Date;
43  import java.util.HashMap;
44  import java.util.Map;
45  import java.util.Set;
46  import java.util.concurrent.CopyOnWriteArraySet;
47  
48  import static com.google.common.base.Preconditions.checkNotNull;
49  
50  /**
51   * Plugin that wraps an OSGi bundle that does contain a plugin descriptor.  The actual bundle is not created until the
52   * {@link #install()} method is invoked.  Any attempt to access a method that requires a bundle will throw an
53   * {@link com.atlassian.plugin.IllegalPluginStateException}.
54   *
55   * This class uses a {@link OsgiPluginHelper} to represent different behaviors of key methods in different states.
56   * {@link OsgiPluginUninstalledHelper} implements the methods when the plugin hasn't yet been installed into the
57   * OSGi container, while {@link OsgiPluginInstalledHelper} implements the methods when the bundle is available.  This
58   * leaves this class to manage the {@link PluginState} and interactions with the event system.
59   */
60  //@Threadsafe
61  public class OsgiPlugin extends AbstractPlugin implements AutowireCapablePlugin, ContainerManagedPlugin
62  {
63      /**
64       * Manifest key for the Atlassian plugin key entry
65       */
66      public static final String ATLASSIAN_PLUGIN_KEY = "Atlassian-Plugin-Key";
67  
68      /**
69       * Manifest key denoting Atlassian remote plugins
70       */
71      public static final String REMOTE_PLUGIN_KEY = "Remote-Plugin";
72  
73      private final Map<String, Element> moduleElements = new HashMap<String, Element>();
74      private final PluginEventManager pluginEventManager;
75      private final PackageAdmin packageAdmin;
76      private final Set<OutstandingDependency> outstandingDependencies = new CopyOnWriteArraySet<OutstandingDependency>();
77      private final Logger log = LoggerFactory.getLogger(this.getClass());
78      private final BundleListener bundleStartStopListener;
79      private final PluginArtifact originalPluginArtifact;
80  
81      private volatile boolean treatPluginContainerCreationAsRefresh = false;
82      private volatile OsgiPluginHelper helper;
83  
84      // Until the framework is actually done starting we want to ignore @RequiresRestart. Where this comes into play
85      // is when we have one version of a plugin (e.g. via bundled-plugins.zip) installed but then discover a newer
86      // one in installed-plugins. Clearly we can't "require a restart" between those two stages. And since nothing has
87      // been published outside of plugins yet (and thus can't be cached by the host app) the @RequiresRestart is
88      // meaningless.
89      private volatile boolean frameworkStarted = false;
90  
91      public OsgiPlugin(final String key, final OsgiContainerManager mgr, final PluginArtifact artifact, final PluginArtifact originalPluginArtifact, final PluginEventManager pluginEventManager)
92      {
93          this.originalPluginArtifact = checkNotNull(originalPluginArtifact);
94          this.pluginEventManager = checkNotNull(pluginEventManager);
95  
96          this.helper = new OsgiPluginUninstalledHelper(
97                  checkNotNull(key, "The plugin key is required"),
98                  checkNotNull(mgr, "The osgi container is required"),
99                  checkNotNull(artifact, "The plugin artifact is required"));
100         this.packageAdmin = extractPackageAdminFromOsgi(mgr);
101 
102         this.bundleStartStopListener = new SynchronousBundleListener()
103         {
104             public void bundleChanged(final BundleEvent bundleEvent)
105             {
106                 if (bundleEvent.getBundle() == getBundle())
107                 {
108                     if (bundleEvent.getType() == BundleEvent.STOPPING)
109                     {
110                         helper.onDisable();
111                         setPluginState(PluginState.DISABLED);
112                     }
113                     else if (bundleEvent.getType() == BundleEvent.STARTED)
114                     {
115                         BundleContext ctx = getBundle().getBundleContext();
116                         helper.onEnable(createServiceTrackers(ctx));
117                         setPluginState(PluginState.ENABLED);
118                     }
119                 }
120             }
121         };
122     }
123 
124     /**
125      * Only used for testing
126      * @param helper The helper to use
127      */
128     @VisibleForTesting
129     OsgiPlugin(PluginEventManager pluginEventManager, OsgiPluginHelper helper)
130     {
131         this.helper = helper;
132         this.pluginEventManager = pluginEventManager;
133         this.packageAdmin = null;
134         this.bundleStartStopListener = null;
135         this.originalPluginArtifact = null;
136     }
137 
138     /**
139      * @return The active bundle
140      * @throws IllegalPluginStateException if the bundle hasn't been created yet
141      */
142     public Bundle getBundle() throws IllegalPluginStateException
143     {
144         return helper.getBundle();
145     }
146 
147     @Override
148     public InstallationMode getInstallationMode()
149     {
150         return helper.isRemotePlugin() ? InstallationMode.REMOTE : InstallationMode.LOCAL;
151     }
152 
153     /**
154      * @return true
155      */
156     public boolean isUninstallable()
157     {
158         return true;
159     }
160 
161     /**
162      * @return true
163      */
164     public boolean isDynamicallyLoaded()
165     {
166         return true;
167     }
168 
169     /**
170      * @return true
171      */
172     public boolean isDeleteable()
173     {
174         return true;
175     }
176 
177 
178     @Override
179     public Date getDateInstalled()
180     {
181         long date = getPluginArtifact().toFile().lastModified();
182         if (date == 0)
183         {
184             date = getDateLoaded().getTime();
185         }
186         return new Date(date);
187     }
188 
189     public PluginArtifact getPluginArtifact()
190     {
191         return originalPluginArtifact;
192     }
193 
194     /**
195      * @param clazz The name of the class to be loaded
196      * @param callingClass The class calling the loading (used to help find a classloader)
197      * @param <T> The class type
198      * @return The class instance, loaded from the OSGi bundle
199      * @throws ClassNotFoundException If the class cannot be found
200      * @throws IllegalPluginStateException if the bundle hasn't been created yet
201      */
202     public <T> Class<T> loadClass(final String clazz, final Class<?> callingClass) throws ClassNotFoundException, IllegalPluginStateException
203     {
204         return helper.loadClass(clazz, callingClass);
205     }
206 
207     /**
208      * @param name The resource name
209      * @return The resource URL, null if not found
210      * @throws IllegalPluginStateException if the bundle hasn't been created yet
211      */
212     public URL getResource(final String name) throws IllegalPluginStateException
213     {
214         return helper.getResource(name);
215     }
216 
217     /**
218      * @param name The name of the resource to be loaded.
219      * @return Null if not found
220      * @throws IllegalPluginStateException if the bundle hasn't been created yet
221      */
222     public InputStream getResourceAsStream(final String name) throws IllegalPluginStateException
223     {
224         return helper.getResourceAsStream(name);
225     }
226 
227     /**
228      * @return The classloader to load classes and resources from the bundle
229      * @throws IllegalPluginStateException if the bundle hasn't been created yet
230      */
231     public ClassLoader getClassLoader() throws IllegalPluginStateException
232     {
233         return helper.getClassLoader();
234     }
235 
236     /**
237      * Called when the plugin container for the bundle has failed to be created.  This means the bundle is still
238      * active, but the plugin container is not available, so for our purposes, the plugin shouldn't be enabled.
239      *
240      * @param event The plugin container failed event
241      * @throws com.atlassian.plugin.IllegalPluginStateException If the plugin key hasn't been set yet
242      */
243     @PluginEventListener
244     public void onPluginContainerFailed(final PluginContainerFailedEvent event) throws IllegalPluginStateException
245     {
246         if (getKey() == null)
247         {
248             throw new IllegalPluginStateException("Plugin key must be set");
249         }
250         if (getKey().equals(event.getPluginKey()))
251         {
252             logAndClearOustandingDependencies();
253             // TODO: do something with the exception more than logging
254             getLog().error("Unable to start the plugin container for plugin " + getKey(), event.getCause());
255             setPluginState(PluginState.DISABLED);
256         }
257     }
258 
259     @PluginEventListener
260     public void onPluginFrameworkStartedEvent(final PluginFrameworkStartedEvent event)
261     {
262         frameworkStarted = true;
263     }
264 
265     @PluginEventListener
266     public void onPluginFrameworkShutdownEvent(final PluginFrameworkShutdownEvent event)
267     {
268         frameworkStarted = false;
269     }
270 
271     @PluginEventListener
272     public void onServiceDependencyWaitStarting(PluginServiceDependencyWaitStartingEvent event)
273     {
274         if (event.getPluginKey() != null && event.getPluginKey().equals(getKey()))
275         {
276             OutstandingDependency dep = new OutstandingDependency(event.getBeanName(), String.valueOf(event.getFilter()));
277             outstandingDependencies.add(dep);
278             getLog().info(generateOutstandingDependencyLogMessage(dep, "Waiting for"));
279         }
280     }
281 
282     @PluginEventListener
283     public void onServiceDependencyWaitEnded(PluginServiceDependencyWaitEndedEvent event)
284     {
285         if (event.getPluginKey() != null && event.getPluginKey().equals(getKey()))
286         {
287             OutstandingDependency dep = new OutstandingDependency(event.getBeanName(), String.valueOf(event.getFilter()));
288             outstandingDependencies.remove(dep);
289             getLog().info(generateOutstandingDependencyLogMessage(dep, "Found"));
290         }
291     }
292 
293     @PluginEventListener
294     public void onServiceDependencyWaitEnded(PluginServiceDependencyWaitTimedOutEvent event)
295     {
296         if (event.getPluginKey() != null && event.getPluginKey().equals(getKey()))
297         {
298             OutstandingDependency dep = new OutstandingDependency(event.getBeanName(), String.valueOf(event.getFilter()));
299             outstandingDependencies.remove(dep);
300             getLog().error(generateOutstandingDependencyLogMessage(dep, "Timeout waiting for "));
301         }
302     }
303 
304     private String generateOutstandingDependencyLogMessage(OutstandingDependency dep, String action)
305     {
306         StringBuilder sb = new StringBuilder();
307         sb.append(action).append(" ");
308         sb.append("service '").append(dep.getBeanName()).append("' for plugin '").append(getKey()).append("' with filter ").append(dep.getFilter());
309         return sb.toString();
310     }
311 
312     /**
313      * Called when the plugin container for the bundle has been created or refreshed.  If this is the first time the
314      * context has been refreshed, then it is a new context.  Otherwise, this means that the bundle has been reloaded,
315      * usually due to a dependency upgrade.
316      *
317      * @param event The event
318      * @throws com.atlassian.plugin.IllegalPluginStateException If the plugin key hasn't been set yet
319      */
320     @PluginEventListener
321     public void onPluginContainerRefresh(final PluginContainerRefreshedEvent event) throws IllegalPluginStateException
322     {
323         if (getKey() == null)
324         {
325             throw new IllegalPluginStateException("Plugin key must be set");
326         }
327         if (getKey().equals(event.getPluginKey()))
328         {
329             outstandingDependencies.clear();
330             helper.setPluginContainer(event.getContainer());
331             if (!compareAndSetPluginState(PluginState.ENABLING, PluginState.ENABLED) && getPluginState() != PluginState.ENABLED)
332             {
333                 log.warn("Ignoring the bean container that was just created for plugin " + getKey() + ".  The plugin " +
334                           "is in an invalid state, " + getPluginState() + ", that doesn't support a transition to " +
335                           "enabled.  Most likely, it was disabled due to a timeout.");
336                 helper.setPluginContainer(null);
337                 return;
338             }
339 
340             // Only send refresh event on second creation
341             if (treatPluginContainerCreationAsRefresh)
342             {
343                 pluginEventManager.broadcast(new PluginRefreshedEvent(this));
344             }
345             else
346             {
347                 treatPluginContainerCreationAsRefresh = true;
348             }
349         }
350     }
351 
352     /**
353      * Creates and autowires the class, preferring constructor inject, then falling back to private field and setter
354      *
355      * @throws IllegalPluginStateException if the bundle hasn't been created yet
356      */
357     public <T> T autowire(final Class<T> clazz) throws IllegalPluginStateException
358     {
359         return autowire(clazz, AutowireStrategy.AUTOWIRE_AUTODETECT);
360     }
361 
362     /**
363      * Creates and autowires the class
364      *
365      * @throws IllegalPluginStateException if the bundle hasn't been created yet
366      */
367     public <T> T autowire(final Class<T> clazz, final AutowireStrategy autowireStrategy) throws IllegalPluginStateException
368     {
369         return helper.getRequiredContainerAccessor().createBean(clazz);
370     }
371 
372     /**
373      * Autowires the instance using plugin container's default injection algorithm
374      *
375      * @throws IllegalPluginStateException if the bundle hasn't been created yet
376      */
377     public void autowire(final Object instance) throws IllegalStateException
378     {
379         autowire(instance, AutowireStrategy.AUTOWIRE_AUTODETECT);
380     }
381 
382     /**
383      * Autowires the instance
384      *
385      * @throws IllegalPluginStateException if the bundle hasn't been created yet
386      */
387     public void autowire(final Object instance, final AutowireStrategy autowireStrategy) throws IllegalPluginStateException
388     {
389         helper.getRequiredContainerAccessor().injectBean(instance);
390     }
391 
392     /**
393      * Determines which plugins are required for this one to operate based on tracing the "wires" or packages that
394      * are imported by this plugin.  Bundles that provide those packages are determined to be required plugins.
395      *
396      * @return A set of bundle symbolic names, or plugin keys.  Empty set if none.
397      * @since 2.2.0
398      */
399     @Override
400     public Set<String> getRequiredPlugins() throws IllegalPluginStateException
401     {
402         return helper.getRequiredPlugins();
403     }
404 
405     @Override
406     public String toString()
407     {
408         return getKey();
409     }
410 
411     /**
412      * Installs the plugin artifact into OSGi
413      *
414      * @throws IllegalPluginStateException if the bundle hasn't been created yet
415      */
416     @Override
417     protected void installInternal() throws IllegalPluginStateException
418     {
419         log.debug("Installing OSGi plugin '{}'", getKey());
420         Bundle bundle = helper.install();
421         helper = new OsgiPluginInstalledHelper(bundle, packageAdmin);
422     }
423 
424     /**
425      * Enables the plugin by setting the OSGi bundle state to enabled.
426      *
427      * @return {@link PluginState#ENABLED}if the container is being refreshed or {@link PluginState#ENABLING} if we are waiting
428      * on a plugin container (first time being activated, as all subsequent times are considered refreshes)
429      * @throws OsgiContainerException If the underlying OSGi system threw an exception or we tried to enable the bundle
430      * when it was in an invalid state
431      * @throws IllegalPluginStateException if the bundle hasn't been created yet
432      */
433     @Override
434     protected synchronized PluginState enableInternal() throws OsgiContainerException, IllegalPluginStateException
435     {
436         log.debug("Enabling OSGi plugin '{}'", getKey());
437         PluginState stateResult;
438         try
439         {
440             if (getBundle().getState() == Bundle.ACTIVE)
441             {
442                 log.debug("Plugin '{}' bundle is already active, not doing anything", getKey());
443                 stateResult = PluginState.ENABLED;
444             }
445             else if ((getBundle().getState() == Bundle.RESOLVED) || (getBundle().getState() == Bundle.INSTALLED))
446             {
447                 pluginEventManager.register(this);
448                 if (!treatPluginContainerCreationAsRefresh)
449                 {
450                     stateResult = PluginState.ENABLING;
451                     // Set it immediately, since the plugin container refresh event could happen at any time
452                     setPluginState(stateResult);
453                 }
454                 else
455                 {
456                     stateResult = PluginState.ENABLED;
457                 }
458                 log.debug("Plugin '{}' bundle is resolved or installed, starting.", getKey());
459                 getBundle().start();
460                 final BundleContext ctx = getBundle().getBundleContext();
461                 helper.onEnable(createServiceTrackers(ctx));
462 
463                 // ensure the bean factory is removed when the bundle is stopped
464                 ctx.addBundleListener(bundleStartStopListener);
465             }
466             else
467             {
468                 throw new OsgiContainerException("Cannot enable the plugin '" + getKey() + "' when the bundle is not in the resolved or installed state: "
469                         + getBundle().getState() + "(" + getBundle().getBundleId() + ")");
470             }
471 
472             // Only set state to enabling if it hasn't already been enabled via another thread notifying of a plugin
473             // container creation during the execution of this method
474             return (getPluginState() != PluginState.ENABLED ? stateResult : PluginState.ENABLED);
475         }
476         catch (final BundleException e)
477         {
478             log.error("Detected an error (BundleException) enabling the plugin '" + getKey() + "' : " + e.getMessage() + ". " +
479                       " This error usually occurs when your plugin imports a package from another bundle with a specific version constraint " +
480                     "and either the bundle providing that package doesn't meet those version constraints, or there is no bundle " +
481                     "available that provides the specified package. For more details on how to fix this, see " +
482                     "https://developer.atlassian.com/x/mQAN");
483             throw new OsgiContainerException("Cannot start plugin: " + getKey(), e);
484         }
485     }
486 
487     private ServiceTracker[] createServiceTrackers(BundleContext ctx)
488     {
489         return new ServiceTracker[] {
490                 new ServiceTracker(ctx, ModuleDescriptor.class.getName(),
491                         new ModuleDescriptorServiceTrackerCustomizer(this, pluginEventManager)),
492                 new ServiceTracker(ctx, ListableModuleDescriptorFactory.class.getName(),
493                         new UnrecognizedModuleDescriptorServiceTrackerCustomizer(this, pluginEventManager))
494         };
495     }
496 
497     /**
498      * Disables the plugin by changing the bundle state back to resolved
499      *
500      * @throws OsgiContainerException If the OSGi system threw an exception
501      * @throws IllegalPluginStateException if the bundle hasn't been created yet
502      */
503     @Override
504     protected synchronized void disableInternal() throws OsgiContainerException, IllegalPluginStateException
505     {
506         // Only disable underlying bundle if this is a truly dynamic plugin
507         if (!requiresRestart())
508         {
509             try
510             {
511                 if (getPluginState() == PluginState.DISABLING)
512                 {
513                     logAndClearOustandingDependencies();
514                 }
515                 helper.onDisable();
516                 pluginEventManager.unregister(this);
517                 getBundle().stop();
518                 treatPluginContainerCreationAsRefresh = false;
519             }
520             catch (final BundleException e)
521             {
522                 log.error("Detected an error (BundleException) disabling the plugin '" + getKey() + "' : " + e.getMessage() + ".");
523                 throw new OsgiContainerException("Cannot stop plugin: " + getKey(), e);
524             }
525         }
526     }
527 
528     private boolean requiresRestart()
529     {
530         return frameworkStarted && PluginUtils.doesPluginRequireRestart(this);
531     }
532 
533     private void logAndClearOustandingDependencies()
534     {
535         for (OutstandingDependency dep : outstandingDependencies)
536         {
537             getLog().error(generateOutstandingDependencyLogMessage(dep, "Never resolved"));
538         }
539         outstandingDependencies.clear();
540     }
541 
542     /**
543      * Uninstalls the bundle from the OSGi container
544      * @throws OsgiContainerException If the underlying OSGi system threw an exception
545      * @throws IllegalPluginStateException if the bundle hasn't been created yet
546      */
547     @Override
548     protected void uninstallInternal() throws OsgiContainerException, IllegalPluginStateException
549     {
550         int retryCount=0;
551         Exception rootCause = null;
552         final long sleepTime = 500L;
553         while (true)
554         {
555             try
556             {
557                 if (getBundle().getState() != Bundle.UNINSTALLED)
558                 {
559                     pluginEventManager.unregister(this);
560                     getBundle().uninstall();
561                     helper.onUninstall();
562                     setPluginState(PluginState.UNINSTALLED);
563                     break;
564                 }
565                 else
566                 {
567                     log.debug("Trying to uninstall '{}', but this has already been uninstalled", getKey());
568                     pluginEventManager.unregister(this);
569                     break;
570                 }
571             }
572             catch (final BundleException e)
573             {
574                 rootCause = retryCount == 0 ? e : rootCause;
575                 retryCount++;
576 
577                 if (retryCount < 3)
578                 {
579                     log.debug("Try {} will Retry again in  {} mSecs", retryCount, sleepTime );
580                     log.debug(e.getMessage(), e);
581                     try {
582                         Thread.sleep(sleepTime);
583                     } catch (InterruptedException e1) {
584                         throw new OsgiContainerException("Cannot uninstall bundle, as thread was interrupted");
585                     }
586                 }
587                 else
588                 {
589                     log.error("Detected an error (BundleException) disabling the plugin '{}'." , getKey() );
590                     log.error(rootCause.getMessage(), rootCause);
591                     throw new OsgiContainerException("Cannot uninstall bundle " + getBundle().getSymbolicName());
592                 }
593             }
594         }
595     }
596 
597     /**
598      * Adds a module descriptor XML element for later processing, needed for dynamic module support
599      *
600      * @param key The module key
601      * @param element The module element
602      */
603     void addModuleDescriptorElement(final String key, final Element element)
604     {
605         moduleElements.put(key, element);
606     }
607 
608     /**
609      * Exposes {@link #removeModuleDescriptor(String)} for package-protected classes
610      *
611      * @param key The module descriptor key
612      */
613     void clearModuleDescriptor(String key)
614     {
615         removeModuleDescriptor(key);
616     }
617 
618     /**
619      * Gets the module elements for dynamic module descriptor handling.  Doesn't need to return a copy or anything
620      * immutable because it is only accessed by package-private helper classes
621      *
622      * @return The map of module keys to module XML elements
623      */
624     Map<String, Element> getModuleElements()
625     {
626         return moduleElements;
627     }
628 
629     /**
630      * Extracts the {@link PackageAdmin} instance from the OSGi container
631      * @param mgr The OSGi container manager
632      * @return The package admin instance, should never be null
633      */
634     private PackageAdmin extractPackageAdminFromOsgi(OsgiContainerManager mgr)
635     {
636         // Get the system bundle (always bundle 0)
637         Bundle bundle = mgr.getBundles()[0];
638 
639         // We assume the package admin will always be available
640         final ServiceReference ref = bundle.getBundleContext()
641                 .getServiceReference(PackageAdmin.class.getName());
642         return (PackageAdmin) bundle.getBundleContext()
643                 .getService(ref);
644     }
645 
646     public ContainerAccessor getContainerAccessor()
647     {
648         return helper.getContainerAccessor();
649     }
650 
651     private static class OutstandingDependency
652     {
653         private final String beanName;
654         private final String filter;
655 
656         public OutstandingDependency(String beanName, String filter)
657         {
658             this.beanName = beanName;
659             this.filter = filter;
660         }
661 
662         public String getBeanName()
663         {
664             return beanName;
665         }
666 
667         public String getFilter()
668         {
669             return filter;
670         }
671 
672         @Override
673         public boolean equals(Object o)
674         {
675             if (this == o)
676             {
677                 return true;
678             }
679             if (o == null || getClass() != o.getClass())
680             {
681                 return false;
682             }
683 
684             OutstandingDependency that = (OutstandingDependency) o;
685 
686             if (beanName != null ? !beanName.equals(that.beanName) : that.beanName != null)
687             {
688                 return false;
689             }
690             if (!filter.equals(that.filter))
691             {
692                 return false;
693             }
694 
695             return true;
696         }
697 
698         @Override
699         public int hashCode()
700         {
701             int result = beanName != null ? beanName.hashCode() : 0;
702             result = 31 * result + filter.hashCode();
703             return result;
704         }
705     }
706 }