blob: 7a3dab60ed30c292ccb66f6cdbfd8bc5ba1292a5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.scrplugin.om;
import java.util.ArrayList;
import java.util.List;
import org.apache.felix.scrplugin.Constants;
import org.apache.felix.scrplugin.IssueLog;
import org.apache.felix.scrplugin.tags.*;
import org.apache.maven.plugin.MojoExecutionException;
/**
* <code>Component</code>
* is a described component.
*
*/
public class Component extends AbstractObject {
/** The name of the component. */
protected String name;
/** Is this component enabled? */
protected Boolean enabled;
/** Is this component immediately started. */
protected Boolean immediate;
/** The factory. */
protected String factory;
/** The implementation. */
protected Implementation implementation;
/** All properties. */
protected List<Property> properties = new ArrayList<Property>();
/** The corresponding service. */
protected Service service;
/** The references. */
protected List<Reference> references = new ArrayList<Reference>();
/** Is this an abstract description? */
protected boolean isAbstract;
/** Is this a descriptor to be ignored ? */
protected boolean isDs;
/** Configuration policy. (V1.1) */
protected String configurationPolicy;
/** Activation method. (V1.1) */
protected String activate;
/** Deactivation method. (V1.1) */
protected String deactivate;
/** Modified method. (V1.1) */
protected String modified;
/** The spec version. */
protected int specVersion;
/**
* Default constructor.
*/
public Component() {
this(null);
}
/**
* Constructor from java source.
*/
public Component(JavaTag t) {
super(t);
}
/**
* Get the spec version.
*/
public int getSpecVersion() {
return this.specVersion;
}
/**
* Set the spec version.
*/
public void setSpecVersion(int value) {
this.specVersion = value;
}
/**
* Return the associated java class description
*/
public JavaClassDescription getJavaClassDescription() {
if ( this.tag != null ) {
return this.tag.getJavaClassDescription();
}
return null;
}
/**
* @return All properties of this component.
*/
public List<Property> getProperties() {
return this.properties;
}
public void setProperties(List<Property> properties) {
this.properties = properties;
}
public void addProperty(Property property) {
this.properties.add(property);
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getFactory() {
return this.factory;
}
public void setFactory(String factory) {
this.factory = factory;
}
public Boolean isEnabled() {
return this.enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public Boolean isImmediate() {
return this.immediate;
}
public void setImmediate(Boolean immediate) {
this.immediate = immediate;
}
public Implementation getImplementation() {
return this.implementation;
}
public void setImplementation(Implementation implementation) {
this.implementation = implementation;
}
public Service getService() {
return this.service;
}
public void setService(Service service) {
this.service = service;
}
public List<Reference> getReferences() {
return this.references;
}
public void setReferences(List<Reference> references) {
this.references = references;
}
public void addReference(Reference ref) {
this.references.add(ref);
}
public boolean isAbstract() {
return this.isAbstract;
}
public void setAbstract(boolean isAbstract) {
this.isAbstract = isAbstract;
}
public boolean isDs() {
return isDs;
}
public void setDs(boolean isDs) {
this.isDs = isDs;
}
/**
* Get the name of the activate method (or null for default)
*/
public String getActivate() {
return this.activate;
}
/**
* Set the name of the deactivate method (or null for default)
*/
public void setDeactivate(final String value) {
this.deactivate = value;
}
/**
* Get the name of the deactivate method (or null for default)
*/
public String getDeactivate() {
return this.deactivate;
}
/**
* Set the name of the activate method (or null for default)
*/
public void setActivate(final String value) {
this.activate = value;
}
/**
* Set the name of the modified method (or null for default)
*/
public void setModified(final String value) {
this.modified = value;
}
/**
* Get the name of the modified method (or null for default)
*/
public String getModified() {
return this.modified;
}
/**
* Validate the component description.
* If errors occur a message is added to the issues list,
* warnings can be added to the warnings list.
*/
public void validate(final int specVersion, final IssueLog iLog)
throws MojoExecutionException {
final int currentIssueCount = iLog.getNumberOfErrors();
// nothing to check if this is ignored
if (!isDs()) {
return;
}
final JavaClassDescription javaClass = this.tag.getJavaClassDescription();
if (javaClass == null) {
iLog.addError(this.getMessage("Tag not declared in a Java Class"));
} else {
// if the service is abstract, we do not validate everything
if ( !this.isAbstract ) {
// ensure non-abstract, public class
if (!javaClass.isPublic()) {
iLog.addError(this.getMessage("Class must be public: " + javaClass.getName()));
}
if (javaClass.isAbstract() || javaClass.isInterface()) {
iLog.addError(this.getMessage("Class must be concrete class (not abstract or interface) : " + javaClass.getName()));
}
// no errors so far, let's continue
if ( iLog.getNumberOfErrors() == currentIssueCount ) {
final String activateName = this.activate == null ? "activate" : this.activate;
final String deactivateName = this.deactivate == null ? "deactivate" : this.deactivate;
// check activate and deactivate methods
this.checkLifecycleMethod(specVersion, javaClass, activateName, true, iLog);
this.checkLifecycleMethod(specVersion, javaClass, deactivateName, false, iLog);
if ( this.modified != null && specVersion == Constants.VERSION_1_1 ) {
this.checkLifecycleMethod(specVersion, javaClass, this.modified, true, iLog);
}
// ensure public default constructor
boolean constructorFound = true;
JavaMethod[] methods = javaClass.getMethods();
for (int i = 0; methods != null && i < methods.length; i++) {
if (methods[i].isConstructor()) {
// if public default, succeed
if (methods[i].isPublic()
&& (methods[i].getParameters() == null || methods[i].getParameters().length == 0)) {
constructorFound = true;
break;
}
// non-public/non-default constructor found, must have explicit
constructorFound = false;
}
}
if (!constructorFound) {
iLog.addError(this.getMessage("Class must have public default constructor: " + javaClass.getName()));
}
// verify properties
for(final Property prop : this.getProperties()) {
prop.validate(specVersion, iLog);
}
// verify service
boolean isServiceFactory = false;
if (this.getService() != null) {
if ( this.getService().getInterfaces().size() == 0 ) {
iLog.addError(this.getMessage("Service interface information is missing for @scr.service tag"));
}
this.getService().validate(specVersion, iLog);
isServiceFactory = this.getService().isServicefactory();
}
// serviceFactory must not be true for immediate of component factory
if (isServiceFactory && this.isImmediate() != null && this.isImmediate().booleanValue() && this.getFactory() != null) {
iLog.addError(this.getMessage("Component must not be a ServiceFactory, if immediate and/or component factory: " + javaClass.getName()));
}
// immediate must not be true for component factory
if (this.isImmediate() != null && this.isImmediate().booleanValue() && this.getFactory() != null) {
iLog.addError(this.getMessage("Component must not be immediate if component factory: " + javaClass.getName()));
}
}
}
if ( iLog.getNumberOfErrors() == currentIssueCount ) {
// verify references
for(final Reference ref : this.getReferences()) {
ref.validate(specVersion, this.isAbstract, iLog);
}
}
}
// check additional stuff if version is 1.1
if ( specVersion == Constants.VERSION_1_1 ) {
final String cp = this.getConfigurationPolicy();
if ( cp != null
&& !Constants.COMPONENT_CONFIG_POLICY_IGNORE.equals(cp)
&& !Constants.COMPONENT_CONFIG_POLICY_REQUIRE.equals(cp)
&& !Constants.COMPONENT_CONFIG_POLICY_OPTIONAL.equals(cp) ) {
iLog.addError(this.getMessage("Component has an unknown value for configuration policy: " + cp));
}
}
}
private static final String TYPE_COMPONENT_CONTEXT = "org.osgi.service.component.ComponentContext";
private static final String TYPE_BUNDLE_CONTEXT = "org.osgi.framework.BundleContext";
private static final String TYPE_MAP = "java.util.Map";
private static final String TYPE_INT = "int";
private static final String TYPE_INTEGER = "java.lang.Integer";
/**
* Check for existence of lifecycle methods.
* @param specVersion The spec version
* @param javaClass The java class to inspect.
* @param methodName The method name.
* @param warnings The list of warnings used to add new warnings.
*/
protected void checkLifecycleMethod(final int specVersion,
final JavaClassDescription javaClass,
final String methodName,
final boolean isActivate,
final IssueLog iLog)
throws MojoExecutionException {
// first candidate is (de)activate(ComponentContext)
JavaMethod method = javaClass.getMethodBySignature(methodName, new String[] {TYPE_COMPONENT_CONTEXT});
if ( method == null ) {
if ( specVersion == Constants.VERSION_1_1) {
// second candidate is (de)activate(BundleContext)
method = javaClass.getMethodBySignature(methodName, new String[] {TYPE_BUNDLE_CONTEXT});
if ( method == null ) {
// third candidate is (de)activate(Map)
method = javaClass.getMethodBySignature(methodName, new String[] {TYPE_MAP});
if ( method == null ) {
// if this is a deactivate method, we have two additional possibilities
// a method with parameter of type int and one of type Integer
if ( !isActivate ) {
method = javaClass.getMethodBySignature(methodName, new String[] {TYPE_INT});
if ( method == null ) {
method = javaClass.getMethodBySignature(methodName, new String[] {TYPE_INTEGER});
}
}
// fourth candidate is (de)activate with two or three arguments (type must be BundleContext, ComponentCtx and Map)
// as we have to iterate now and the fifth candidate is zero arguments
// we already store this option
JavaMethod zeroArgMethod = null;
JavaMethod found = method;
final JavaMethod[] methods = javaClass.getMethods();
int i = 0;
while ( i < methods.length ) {
if ( methodName.equals(methods[i].getName()) ) {
if ( methods[i].getParameters().length == 0 ) {
zeroArgMethod = methods[i];
} else if ( methods[i].getParameters().length >= 2 ) {
boolean valid = true;
for(int m=0; m<methods[i].getParameters().length; m++) {
final String type = methods[i].getParameters()[m].getType();
if ( !type.equals(TYPE_BUNDLE_CONTEXT)
&& !type.equals(TYPE_COMPONENT_CONTEXT)
&& !type.equals(TYPE_MAP) ) {
// if this is deactivate, int and integer are possible as well
if ( isActivate || (!type.equals(TYPE_INT) && !type.equals(TYPE_INTEGER)) ) {
valid = false;
}
}
}
if ( valid ) {
if ( found == null ) {
found = methods[i];
} else {
// print warning
iLog.addWarning(this.getMessage("Lifecycle method " + methods[i].getName() + " occurs several times with different matching signature."));
}
}
}
}
i++;
}
if ( found != null ) {
method = found;
} else {
method = zeroArgMethod;
}
}
}
}
// if no method is found, we check for any method with that name to print some warnings!
if ( method == null ) {
final JavaMethod[] methods = javaClass.getMethods();
for(int i=0; i<methods.length; i++) {
if ( methodName.equals(methods[i].getName()) ) {
if ( methods[i].getParameters().length != 1 ) {
iLog.addWarning(this.getMessage("Lifecycle method " + methods[i].getName() + " has wrong number of arguments"));
} else {
iLog.addWarning(this.getMessage("Lifecycle method " + methods[i].getName() + " has wrong argument " + methods[i].getParameters()[0].getType()));
}
}
}
}
}
// method must be protected for version 1.0
if ( method != null && specVersion == Constants.VERSION_1_0) {
// check protected
if (method.isPublic()) {
iLog.addWarning(this.getMessage("Lifecycle method " + method.getName() + " should be declared protected"));
} else if (!method.isProtected()) {
iLog.addWarning(this.getMessage("Lifecycle method " + method.getName() + " has wrong qualifier, public or protected required"));
}
}
}
/**
* Return the configuration policy.
*/
public String getConfigurationPolicy() {
return this.configurationPolicy;
}
/**
* Set the configuration policy.
*/
public void setConfigurationPolicy(final String value) {
this.configurationPolicy = value;
}
}