/**
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU General Public License, version 3.0 (GPLv3)
* (the "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU General Public License,
* version 3.0 (GPLv3) along with this library; if not, see
* <http://www.gnu.org/licenses/>.
*
* 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.jumpmind.symmetric.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.ClassUtils;
import org.jumpmind.db.sql.ISqlRowMapper;
import org.jumpmind.db.sql.Row;
import org.jumpmind.extension.IBuiltInExtensionPoint;
import org.jumpmind.extension.IExtensionPoint;
import org.jumpmind.symmetric.ISymmetricEngine;
import org.jumpmind.symmetric.common.ParameterConstants;
import org.jumpmind.symmetric.common.TableConstants;
import org.jumpmind.symmetric.ext.ExtensionPointMetaData;
import org.jumpmind.symmetric.ext.INodeGroupExtensionPoint;
import org.jumpmind.symmetric.ext.ISymmetricEngineAware;
import org.jumpmind.symmetric.io.data.transform.IColumnTransform;
import org.jumpmind.symmetric.model.Extension;
import org.jumpmind.symmetric.service.IExtensionService;
import org.jumpmind.util.SimpleClassCompiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import bsh.EvalError;
import bsh.Interpreter;
/**
* This service registers {@link IExtensionPoint}s defined both by SymmetricDS
* and others found in the {@link ApplicationContext}.
* It also reads the sym_extension table for {@link IExtensionPoint}s defined there.
* <P>
* SymmetricDS reads in any Spring XML file found in the classpath of the
* application that matches the following pattern:
* /META-INF/services/symmetric-*-ext.xml
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class ExtensionService extends AbstractService implements IExtensionService {
private final static Logger log = LoggerFactory.getLogger(ExtensionService.class);
protected ISymmetricEngine engine;
protected Map<Class, Map<String, IExtensionPoint>> staticExtensionsByClassByName = new HashMap<Class, Map<String, IExtensionPoint>>();
protected Map<Class, Map<String, IExtensionPoint>> extensionsByClassByName;
protected List<ExtensionPointMetaData> extensionMetaData;
public ExtensionService(ISymmetricEngine engine) {
super(engine.getParameterService(), engine.getSymmetricDialect());
this.engine = engine;
setSqlMap(new ExtensionServiceSqlMap(symmetricDialect.getPlatform(), createSqlReplacementTokens()));
}
public synchronized void refresh() {
extensionsByClassByName = new HashMap<Class, Map<String, IExtensionPoint>>();
extensionMetaData = new ArrayList<ExtensionPointMetaData>();
for (Class extensionClass : staticExtensionsByClassByName.keySet()) {
Map<String, IExtensionPoint> byNameMap = staticExtensionsByClassByName.get(extensionClass);
for (String name : byNameMap.keySet()) {
IExtensionPoint ext = byNameMap.get(name);
getExtensionsByNameMap(extensionClass).put(name, ext);
addExtensionPointMetaData(ext, name, extensionClass, true);
}
}
String prefix = parameterService.getString(ParameterConstants.RUNTIME_CONFIG_TABLE_PREFIX);
if (platform.getTableFromCache(TableConstants.getTableName(prefix, TableConstants.SYM_EXTENSION), false) != null) {
List<Extension> extensionList = sqlTemplate.query(getSql("selectEnabled"), new ExtensionRowMapper(), parameterService.getNodeGroupId());
log.info("Found {} extension points from the database that will be registered", extensionList.size());
for (Extension extension : extensionList) {
registerExtension(extension);
}
}
}
protected void registerExtension(Extension extension) {
if (extension.getExtensionText() != null) {
if (extension.getExtensionType().equalsIgnoreCase(Extension.EXTENSION_TYPE_JAVA)) {
try {
Object ext = SimpleClassCompiler.getInstance().getCompiledClass(extension.getExtensionText());
registerExtension(extension.getExtensionId(), (IExtensionPoint) ext);
} catch (Exception e) {
log.error("Error while compiling Java extension " + extension.getExtensionId(), e);
}
} else if (extension.getExtensionType().equalsIgnoreCase(Extension.EXTENSION_TYPE_BSH)) {
try {
Interpreter interpreter = new Interpreter();
interpreter.eval(extension.getExtensionText());
Object ext = interpreter.getInterface(Class.forName(extension.getInterfaceName()));
registerExtension(extension.getExtensionId(), (IExtensionPoint) ext);
} catch (EvalError e) {
log.error("Error while parsing BSH extension " + extension.getExtensionId(), e);
} catch (ClassNotFoundException e) {
log.error("Interface class not found for BSH extension " + extension.getExtensionId(), e);
}
} else {
log.error("Skipping extension " + extension.getExtensionId() + ", unknown extension type " + extension.getExtensionType());
}
}
}
protected boolean registerExtension(String name, IExtensionPoint ext) {
if (! (ext instanceof IExtensionPoint)) {
log.error("Missing IExtensionPoint interface for extension " + name);
}
return registerExtension(name, ext, true);
}
protected boolean registerExtension(String name, IExtensionPoint ext, boolean shouldLog) {
boolean installed = false;
if (initializeExtension(ext)) {
for (Class extensionClass : getExtensionClassList(ext)) {
if (ext instanceof IBuiltInExtensionPoint) {
log.debug("Registering built-in extension named '{}' of type '{}'", name, extensionClass.getSimpleName());
} else if (shouldLog) {
log.info("Registering extension named '{}' of type '{}'", name, extensionClass.getSimpleName());
}
installed = true;
addExtensionPointMetaData(ext, name, extensionClass, true);
getExtensionsByNameMap(extensionClass).put(name, ext);
}
if (!installed) {
addExtensionPointMetaData(ext, name, null, false);
}
}
return installed;
}
protected void unRegisterExtension(String name, IExtensionPoint ext) {
for (Class extensionClass : getExtensionClassList(ext)) {
getExtensionsByNameMap(extensionClass).remove(name);
}
Iterator<ExtensionPointMetaData> iterator = extensionMetaData.iterator();
while (iterator.hasNext()) {
ExtensionPointMetaData metaData = iterator.next();
if (metaData.getExtensionPoint() == ext && metaData.getName().equals(name)) {
iterator.remove();
}
}
}
protected boolean initializeExtension(IExtensionPoint ext) {
boolean shouldInstall = false;
if (ext instanceof ISymmetricEngineAware) {
((ISymmetricEngineAware) ext).setSymmetricEngine(engine);
}
if (ext instanceof INodeGroupExtensionPoint) {
String nodeGroupId = parameterService.getNodeGroupId();
INodeGroupExtensionPoint nodeExt = (INodeGroupExtensionPoint) ext;
String[] ids = nodeExt.getNodeGroupIdsToApplyTo();
if (ids != null) {
for (String targetNodeGroupId : ids) {
if (nodeGroupId.equals(targetNodeGroupId)) {
shouldInstall = true;
}
}
} else {
shouldInstall = true;
}
} else {
shouldInstall = true;
}
return shouldInstall;
}
protected List<Class> getExtensionClassList(IExtensionPoint ext) {
List<Class> classList = new ArrayList<Class>();
List<Class> interfaces = ClassUtils.getAllInterfaces(ext.getClass());
for (Class clazz : interfaces) {
if (IExtensionPoint.class.isAssignableFrom(clazz) && ! clazz.getName().equals(IExtensionPoint.class.getName())) {
classList.add(clazz);
}
}
return classList;
}
public synchronized List<ExtensionPointMetaData> getExtensionPointMetaData() {
return new ArrayList<ExtensionPointMetaData>(extensionMetaData);
}
protected void addExtensionPointMetaData(IExtensionPoint extensionPoint, String name,
Class<? extends IExtensionPoint> extensionClass, boolean installed) {
if (!installed || (!extensionClass.equals(IBuiltInExtensionPoint.class) && !extensionClass.equals(IColumnTransform.class))) {
extensionMetaData.add(new ExtensionPointMetaData(extensionPoint, name, extensionClass, installed));
}
}
public synchronized <T extends IExtensionPoint> T getExtensionPoint(Class<T> extensionClass) {
for (T extension : getExtensionPointList(extensionClass)) {
return extension;
}
return null;
}
public synchronized <T extends IExtensionPoint> List<T> getExtensionPointList(Class<T> extensionClass) {
return new ArrayList<T>(getExtensionPointMap(extensionClass).values());
}
public synchronized <T extends IExtensionPoint> Map<String, T> getExtensionPointMap(Class<T> extensionClass) {
return (Map<String, T>) getExtensionsByNameMap(extensionClass);
}
public synchronized void addExtensionPoint(IExtensionPoint extension) {
for (Class extensionClass : getExtensionClassList(extension)) {
getStaticExtensionsByNameMap(extensionClass).put(extension.getClass().getCanonicalName(), extension);
}
registerExtension(extension.getClass().getCanonicalName(), extension, false);
}
public synchronized void addExtensionPoint(String name, IExtensionPoint extension) {
for (Class extensionClass : getExtensionClassList(extension)) {
getStaticExtensionsByNameMap(extensionClass).put(name, extension);
}
registerExtension(name, extension, false);
}
public synchronized void removeExtensionPoint(IExtensionPoint extension) {
for (Class extensionClass : getExtensionClassList(extension)) {
getStaticExtensionsByNameMap(extensionClass).remove(extension.getClass().getCanonicalName());
}
unRegisterExtension(extension.getClass().getCanonicalName(), extension);
}
protected Map<String, IExtensionPoint> getStaticExtensionsByNameMap(Class extensionClass) {
return getExtensionsByNameMap(staticExtensionsByClassByName, extensionClass);
}
protected Map<String, IExtensionPoint> getExtensionsByNameMap(Class extensionClass) {
return getExtensionsByNameMap(extensionsByClassByName, extensionClass);
}
protected Map<String, IExtensionPoint> getExtensionsByNameMap(Map<Class, Map<String, IExtensionPoint>> byClassByNameMap,
Class extensionClass) {
Map<String, IExtensionPoint> byNameMap = byClassByNameMap.get(extensionClass);
if (byNameMap == null) {
byNameMap = new HashMap<String, IExtensionPoint>();
byClassByNameMap.put(extensionClass, byNameMap);
}
return byNameMap;
}
public List<Extension> getExtensions() {
return sqlTemplate.query(getSql("selectAll"), new ExtensionRowMapper());
}
public void saveExtension(Extension extension) {
Object[] args = { extension.getExtensionType(), extension.getInterfaceName(), extension.getNodeGroupId(),
extension.isEnabled() ? 1 : 0, extension.getExtensionOrder(), extension.getExtensionText(), extension.getLastUpdateBy(),
extension.getExtensionId() };
if (sqlTemplate.update(getSql("updateExtensionSql"), args) == 0) {
sqlTemplate.update(getSql("insertExtensionSql"), args);
if (extension.isEnabled()) {
registerExtension(extension);
}
} else {
refresh();
}
}
public void deleteExtension(String extensionId) {
sqlTemplate.update(getSql("deleteExtensionSql"), extensionId);
refresh();
}
class ExtensionRowMapper implements ISqlRowMapper<Extension> {
@Override
public Extension mapRow(Row row) {
Extension extension = new Extension();
extension.setExtensionId(row.getString("extension_id"));
extension.setExtensionType(row.getString("extension_type"));
extension.setInterfaceName(row.getString("interface_name"));
extension.setNodeGroupId(row.getString("node_group_id"));
extension.setEnabled(row.getBoolean("enabled"));
extension.setExtensionOrder(row.getInt("extension_order"));
extension.setExtensionText(row.getString("extension_text"));
extension.setCreateTime(row.getDateTime("create_time"));
extension.setLastUpdateBy(row.getString("last_update_by"));
extension.setLastUpdateTime(row.getDateTime("last_update_time"));
return extension;
}
}
}