/*******************************************************************************
* Copyright (c) 2008, 2013 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.beans.core.internal.model;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.springframework.beans.factory.xml.DocumentDefaultsDefinition;
import org.springframework.ide.eclipse.beans.core.model.IBean;
import org.springframework.ide.eclipse.beans.core.model.IBeanAlias;
import org.springframework.ide.eclipse.beans.core.model.IBeansComponent;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfigEventListener;
import org.springframework.ide.eclipse.beans.core.model.IBeansImport;
import org.springframework.ide.eclipse.beans.core.model.IBeansModelElement;
import org.springframework.ide.eclipse.beans.core.model.IBeansModelElementTypes;
import org.springframework.ide.eclipse.beans.core.model.IBeansProject;
import org.springframework.ide.eclipse.beans.core.model.IImportedBeansConfig;
import org.springframework.ide.eclipse.core.io.ExternalFile;
import org.springframework.ide.eclipse.core.model.AbstractResourceModelElement;
import org.springframework.ide.eclipse.core.model.IModelElementVisitor;
import org.springframework.ide.eclipse.core.model.IModelSourceLocation;
import org.springframework.ide.eclipse.core.model.ModelUtils;
import org.springframework.ide.eclipse.core.model.validation.ValidationProblem;
import org.springframework.ide.eclipse.core.model.xml.XmlSourceLocation;
/**
* This class gathers common functionality for core model components representing a single instance of xml configuration
* file.
* @author Christian Dupuis
* @author Torsten Juergeleit
* @author Martin Lippert
*/
public abstract class AbstractBeansConfig extends AbstractResourceModelElement implements IBeansConfig {
/** The annotation-config element */
private static final String ANNOTATION_CONFIG_ELEMENT_NAME = "annotation-config";
/** The component-scan element */
private static final String COMPONENT_SCAN_ELEMENT_NAME = "component-scan";
/** The context namespace URI */
private static final String CONTEXT_NAMESPACE_URI = "http://www.springframework.org/schema/context";
/** List of aliases (in registration order) */
protected volatile Map<String, IBeanAlias> aliases = new LinkedHashMap<String, IBeanAlias>();
/** List of bean class names mapped to list of beans implementing the corresponding class */
protected volatile Map<String, Set<IBean>> beanClassesMap = new HashMap<String, Set<IBean>>();
/** List of bean names mapped beans (in registration order) */
protected volatile Map<String, IBean> beans = new LinkedHashMap<String, IBean>();
/** List of components (in registration order) */
protected volatile Set<IBeansComponent> components = new LinkedHashSet<IBeansComponent>();
/** Defaults values for this beans config file */
protected volatile DocumentDefaultsDefinition defaults;
/** This bean's config file */
protected volatile IFile file;
/** List of imports (in registration order) */
protected volatile Set<IBeansImport> imports = new CopyOnWriteArraySet<IBeansImport>();
/** Indicator for a beans configuration embedded in a ZIP file */
protected volatile boolean isArchived;
protected volatile boolean isBeanClassesMapPopulated = false;
protected volatile boolean isModelPopulated = false;
/** This bean config file's timestamp of last modification */
protected volatile long modificationTimestamp;
/** Set of parsing errors */
protected Set<ValidationProblem> problems = new CopyOnWriteArraySet<ValidationProblem>();
protected final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
protected final Lock r = rwl.readLock();
protected volatile Type type;
protected final Lock w = rwl.writeLock();
protected volatile Set<IBeansConfigEventListener> eventListeners = new LinkedHashSet<IBeansConfigEventListener>();
public AbstractBeansConfig(IBeansModelElement project, String name, Type type) {
super(project, name);
this.type = type;
}
/**
* {@inheritDoc}
*/
@Override
public void accept(IModelElementVisitor visitor, IProgressMonitor monitor) {
// First visit this config
if (!monitor.isCanceled() && visitor.visit(this, monitor)) {
// Now ask this config's imports
for (IBeansImport imp : getImports()) {
imp.accept(visitor, monitor);
if (monitor.isCanceled()) {
return;
}
}
// Now ask this config's aliases
for (IBeanAlias alias : getAliases()) {
alias.accept(visitor, monitor);
if (monitor.isCanceled()) {
return;
}
}
// Now ask this config's components
for (IBeansComponent component : getComponents()) {
component.accept(visitor, monitor);
if (monitor.isCanceled()) {
return;
}
}
// Finally ask this configs's beans
for (IBean bean : getBeans()) {
bean.accept(visitor, monitor);
if (monitor.isCanceled()) {
return;
}
}
}
}
/**
* {@inheritDoc}
*/
// TODO CD IDE-1079 commented out to prevent deadlocks
/*@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AbstractBeansConfig)) {
return false;
}
AbstractBeansConfig that = (AbstractBeansConfig) other;
if (!ObjectUtils.nullSafeEquals(this.isArchived, that.isArchived))
return false;
if (this.defaults != null && that.defaults != null && this.defaults != that.defaults) {
if (!ObjectUtils.nullSafeEquals(this.defaults.getLazyInit(), that.defaults.getLazyInit()))
return false;
if (!ObjectUtils.nullSafeEquals(this.defaults.getAutowire(), that.defaults.getAutowire()))
return false;
if (!ObjectUtils.nullSafeEquals(this.defaults.getDependencyCheck(), that.defaults.getDependencyCheck()))
return false;
if (!ObjectUtils.nullSafeEquals(this.defaults.getInitMethod(), that.defaults.getInitMethod()))
return false;
if (!ObjectUtils.nullSafeEquals(this.defaults.getDestroyMethod(), that.defaults.getDestroyMethod()))
return false;
if (!ObjectUtils.nullSafeEquals(this.defaults.getMerge(), that.defaults.getMerge()))
return false;
}
return super.equals(other);
}*/
/**
* {@inheritDoc}
*/
public IBeanAlias getAlias(String name) {
if (name != null) {
try {
r.lock();
IBeanAlias alias = aliases.get(name);
if (alias != null) {
return alias;
}
for (IBeansImport beansImport : imports) {
for (IBeansConfig bc : beansImport.getImportedBeansConfigs()) {
alias = bc.getAlias(name);
if (alias != null) {
return alias;
}
}
}
}
finally {
r.unlock();
}
}
return null;
}
/**
* {@inheritDoc}
*/
public Set<IBeanAlias> getAliases() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
Set<IBeanAlias> allAliases = new LinkedHashSet<IBeanAlias>(aliases.values());
for (IBeansImport beansImport : imports) {
for (IBeansConfig bc : beansImport.getImportedBeansConfigs()) {
allAliases.addAll(bc.getAliases());
}
}
return Collections.unmodifiableSet(new LinkedHashSet<IBeanAlias>(allAliases));
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public IBean getBean(String name) {
if (name != null) {
// Lazily initialization of this config
readConfig();
try {
r.lock();
IBean bean = beans.get(name);
if (bean != null) {
return bean;
}
for (IBeansImport beansImport : imports) {
for (IBeansConfig bc : beansImport.getImportedBeansConfigs()) {
bean = bc.getBean(name);
if (bean != null) {
return bean;
}
}
}
}
finally {
r.unlock();
}
}
return null;
}
/**
* {@inheritDoc}
*/
public Set<String> getBeanClasses() {
return Collections.unmodifiableSet(new LinkedHashSet<String>(getBeanClassesMap().keySet()));
}
/**
* {@inheritDoc}
*/
public Set<IBean> getBeans() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
Set<IBean> allBeans = new LinkedHashSet<IBean>(beans.values());
for (IBeansImport beansImport : imports) {
for (IBeansConfig bc : beansImport.getImportedBeansConfigs()) {
allBeans.addAll(bc.getBeans());
}
}
return Collections.unmodifiableSet(allBeans);
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public Set<IBean> getBeans(String className) {
if (isBeanClass(className)) {
return Collections.unmodifiableSet(getBeanClassesMap().get(className));
}
return Collections.emptySet();
}
/**
* {@inheritDoc}
*/
public Set<IBeansComponent> getComponents() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
Set<IBeansComponent> allComponents = new LinkedHashSet<IBeansComponent>(components);
for (IBeansImport beansImport : imports) {
for (IBeansConfig bc : beansImport.getImportedBeansConfigs()) {
allComponents.addAll(bc.getComponents());
}
}
return Collections.unmodifiableSet(allComponents);
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public String getDefaultAutowire() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
return (defaults != null ? defaults.getAutowire() : DEFAULT_AUTO_WIRE);
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public String getDefaultDependencyCheck() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
return (defaults != null ? defaults.getDependencyCheck() : DEFAULT_DEPENDENCY_CHECK);
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public String getDefaultDestroyMethod() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
return (defaults != null && defaults.getDestroyMethod() != null ? defaults.getDestroyMethod()
: DEFAULT_DESTROY_METHOD);
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public String getDefaultInitMethod() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
return (defaults != null && defaults.getInitMethod() != null ? defaults.getInitMethod()
: DEFAULT_INIT_METHOD);
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public String getDefaultLazyInit() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
return (defaults != null ? defaults.getLazyInit() : DEFAULT_LAZY_INIT);
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public String getDefaultMerge() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
// This default value was introduced with Spring 2.0 -> so we have
// to check for an empty string here as well
return (defaults != null && defaults.getMerge() != null && defaults.getMerge().length() > 0 ? defaults
.getMerge() : DEFAULT_MERGE);
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public IResource getElementResource() {
return file;
}
/**
* {@inheritDoc}
*/
public int getElementStartLine() {
// Lazily initialization of this config
readConfig();
IModelSourceLocation location = ModelUtils.getSourceLocation(defaults);
return (location != null ? location.getStartLine() : -1);
}
/**
* {@inheritDoc}
*/
public final int getElementType() {
return IBeansModelElementTypes.CONFIG_TYPE;
}
/**
* {@inheritDoc}
*/
public Set<IBeansImport> getImports() {
// Check the project if imports are enabled
IBeansProject project = BeansModelUtils.getParentOfClass(this, IBeansProject.class);
if (project != null && project.isImportsEnabled()) {
// Lazily initialization of this config
readConfig();
try {
r.lock();
return Collections.unmodifiableSet(imports);
}
finally {
r.unlock();
}
}
return Collections.emptySet();
}
/**
* Returns the set of {@link ValidationProblem}s that have been recored during initialization.
*/
public final Set<ValidationProblem> getProblems() {
// Lazily initialization of this config
readConfig();
try {
r.lock();
return Collections.unmodifiableSet(problems);
}
finally {
r.unlock();
}
}
/**
* {@inheritDoc}
*/
public Type getType() {
return type;
}
/**
* {@inheritDoc}
*/
public boolean hasBean(String name) {
if (name != null) {
// Lazily initialization of this config
readConfig();
try {
r.lock();
IBean bean = beans.get(name);
if (bean != null) {
return true;
}
for (IBeansImport beansImport : imports) {
for (IBeansConfig bc : beansImport.getImportedBeansConfigs()) {
bean = bc.getBean(name);
if (bean != null) {
return true;
}
}
}
return false;
}
finally {
r.unlock();
}
}
return false;
}
/**
* {@inheritDoc}
*/
// TODO CD IDE-1079 commented out to prevent deadlocks
/*@Override
public int hashCode() {
int hashCode = ObjectUtils.nullSafeHashCode(isArchived);
if (defaults != null) {
hashCode = getElementType() * hashCode + ObjectUtils.nullSafeHashCode(defaults.getLazyInit());
hashCode = getElementType() * hashCode + ObjectUtils.nullSafeHashCode(defaults.getAutowire());
hashCode = getElementType() * hashCode + ObjectUtils.nullSafeHashCode(defaults.getDependencyCheck());
hashCode = getElementType() * hashCode + ObjectUtils.nullSafeHashCode(defaults.getInitMethod());
hashCode = getElementType() * hashCode + ObjectUtils.nullSafeHashCode(defaults.getDestroyMethod());
hashCode = getElementType() * hashCode + ObjectUtils.nullSafeHashCode(defaults.getMerge());
}
return getElementType() * hashCode + super.hashCode();
}*/
/**
* {@inheritDoc}
*/
public boolean isBeanClass(String className) {
if (className != null) {
return getBeanClassesMap().containsKey(className);
}
return false;
}
/**
* {@inheritDoc}
*/
public final boolean isElementArchived() {
return isArchived;
}
/**
* {@inheritDoc}
*/
public boolean isExternal() {
return file instanceof ExternalFile;
}
/**
* {@inheritDoc}
*/
public boolean resourceChanged() {
return modificationTimestamp < getElementResource().getModificationStamp() || changedImportedBeansConfig();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return getElementName() + ": " + getBeans();
}
/**
* {@inheritDoc}
*/
public void registerEventListener(IBeansConfigEventListener configEventListener) {
if (configEventListener != null) {
try {
w.lock();
eventListeners.add(configEventListener);
}
finally {
w.unlock();
}
}
}
/**
* {@inheritDoc}
*/
public void unregisterEventListener(IBeansConfigEventListener configEventListener) {
if (configEventListener != null) {
try {
w.lock();
eventListeners.remove(configEventListener);
}
finally {
w.unlock();
}
}
}
private void addBeanClass(IBean bean, Map<String, Set<IBean>> beanClasses) {
// Get name of bean class - strip name of any inner class
String className = bean.getClassName();
if (className != null) {
int pos = className.indexOf('$');
if (pos > 0) {
className = className.substring(0, pos);
}
// Maintain a list of bean names within every entry in the
// bean class map
Set<IBean> beanClassBeans = beanClasses.get(className);
if (beanClassBeans == null) {
beanClassBeans = new LinkedHashSet<IBean>();
beanClasses.put(className, beanClassBeans);
}
beanClassBeans.add(bean);
}
}
private void addBeanClasses(IBean bean, Map<String, Set<IBean>> beanClasses) {
addBeanClass(bean, beanClasses);
for (IBean innerBean : BeansModelUtils.getInnerBeans(bean)) {
addBeanClass(innerBean, beanClasses);
}
}
private void addComponentBeanClasses(IBeansComponent component, Map<String, Set<IBean>> beanClasses) {
for (IBean bean : component.getBeans()) {
addBeanClasses(bean, beanClasses);
}
for (IBeansComponent innerComponent : component.getComponents()) {
addComponentBeanClasses(innerComponent, beanClasses);
}
}
private boolean changedImportedBeansConfig() {
try {
r.lock();
for (IBeansImport beanImport : imports) {
for (IImportedBeansConfig importedConfig : beanImport.getImportedBeansConfigs()) {
if (importedConfig.resourceChanged()) {
return true;
}
}
}
}
finally {
r.unlock();
}
return false;
}
/**
* Returns lazily initialized map with all bean classes used in this config.
*/
protected Map<String, Set<IBean>> getBeanClassesMap() {
if (!this.isBeanClassesMapPopulated) {
try {
w.lock();
if (this.isBeanClassesMapPopulated) {
return beanClassesMap;
}
beanClassesMap = new LinkedHashMap<String, Set<IBean>>();
for (IBeansComponent component : getComponents()) {
addComponentBeanClasses(component, beanClassesMap);
}
for (IBean bean : getBeans()) {
addBeanClasses(bean, beanClassesMap);
}
for (IBeansImport beansImport : imports) {
for (IBeansConfig bc : beansImport.getImportedBeansConfigs()) {
for (IBeansComponent component : bc.getComponents()) {
addComponentBeanClasses(component, beanClassesMap);
}
for (IBean bean : bc.getBeans()) {
addBeanClasses(bean, beanClassesMap);
}
}
}
}
finally {
this.isBeanClassesMapPopulated = true;
w.unlock();
}
}
return beanClassesMap;
}
public boolean doesAnnotationScanning() {
for (IBeansComponent component : this.getComponents()) {
boolean result = doesAnnotationScanning(component);
if (result) return true;
}
return false;
}
private boolean doesAnnotationScanning(IBeansComponent component) {
if (component.getElementSourceLocation() instanceof XmlSourceLocation) {
XmlSourceLocation location = (XmlSourceLocation) component.getElementSourceLocation();
if (COMPONENT_SCAN_ELEMENT_NAME.equals(location.getLocalName())
&& CONTEXT_NAMESPACE_URI.equals(location.getNamespaceURI())) {
return true;
}
else if (ANNOTATION_CONFIG_ELEMENT_NAME.equals(location.getLocalName())
&& CONTEXT_NAMESPACE_URI.equals(location.getNamespaceURI())) {
return true;
}
}
for (IBeansComponent childComponent : component.getComponents()) {
boolean result = doesAnnotationScanning(childComponent);
if (result) return true;
}
return false;
}
/**
* Read the resource backing this beans configuration and initialize all internal state.
*/
protected abstract void readConfig();
}