/* * 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.aries.proxy.impl.weaving; import static java.lang.String.format; import java.util.ArrayList; import java.util.Dictionary; import java.util.Hashtable; import java.util.List; import java.util.regex.Pattern; import org.apache.aries.proxy.UnableToProxyException; import org.apache.aries.proxy.weaving.WovenProxy; import org.apache.aries.proxy.weavinghook.ProxyWeavingController; import org.apache.aries.proxy.weavinghook.WeavingHelper; import org.objectweb.asm.ClassReader; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.hooks.weaving.WeavingException; import org.osgi.framework.hooks.weaving.WeavingHook; import org.osgi.framework.hooks.weaving.WovenClass; import org.osgi.framework.wiring.BundleWiring; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class ProxyWeavingHook implements WeavingHook, WeavingHelper { public static final String WEAVING_ENABLED_CLASSES = "org.apache.aries.proxy.weaving.enabled"; public static final String WEAVING_DISABLED_CLASSES = "org.apache.aries.proxy.weaving.disabled"; public static final String WEAVING_ENABLED_CLASSES_DEFAULT = "*"; public static final String WEAVING_DISABLED_CLASSES_DEFAULT = "org.objectweb.asm.*,org.slf4j.*,org.apache.log4j.*,javax.*,ch.qos.logback.*"; private static final Logger LOGGER = LoggerFactory.getLogger(ProxyWeavingHook.class); /** An import of the WovenProxy package */ private static final String IMPORT_A = "org.apache.aries.proxy.weaving"; /** * An import for the InvocationListener class that we will need. * This should automatically wire to the right thing because of the uses clause * on the impl.weaving package */ private static final String IMPORT_B = "org.apache.aries.proxy"; private final List<Pattern> enabled; private final List<Pattern> disabled; @SuppressWarnings("rawtypes") private final ServiceTracker controllers; @SuppressWarnings({ "unchecked", "rawtypes" }) public ProxyWeavingHook(BundleContext context) { String enabledProp = context != null ? context.getProperty(WEAVING_ENABLED_CLASSES) : null; enabled = parseMatchers(enabledProp, WEAVING_ENABLED_CLASSES_DEFAULT); disabled = parseMatchers(context != null ? context.getProperty(WEAVING_DISABLED_CLASSES) : null, WEAVING_DISABLED_CLASSES_DEFAULT); controllers = new ServiceTracker(context, ProxyWeavingController.class.getName(), null); controllers.open(); if (!"none".equals(enabledProp)) { Dictionary<String,String> props = new Hashtable<String,String>(); // SubsystemResource.java also uses this constant. // While it could be turned into a static final constant, note that this // is also a non-standard workaround in the absence of a solution in the spec. // See the associated OSGi spec bug. props.put("osgi.woven.packages", "org.apache.aries.proxy.weaving,org.apache.aries.proxy"); context.registerService("org.osgi.framework.hooks.weaving.WeavingHook", this, props); } } public final void weave(WovenClass wovenClass) { BundleWiring bw = wovenClass.getBundleWiring(); if (bw != null) { Bundle b = bw.getBundle(); if(b.getBundleId() == 0 || b.getSymbolicName().startsWith("org.apache.aries.proxy") || b.getSymbolicName().startsWith("org.apache.aries.util")) { return; } } if (!isEnabled(wovenClass.getClassName()) || isDisabled(wovenClass.getClassName())) { return; } if (shouldWeave(wovenClass)) { byte[] bytes = null; try { bytes = WovenProxyGenerator.getWovenProxy(wovenClass.getBytes(), wovenClass.getBundleWiring().getClassLoader()); } catch (Exception e) { if(e instanceof RuntimeException && e.getCause() instanceof UnableToProxyException){ //This is a weaving failure that should be logged, but the class //can still be loaded LOGGER.trace(String.format("The class %s cannot be woven, it may not be possible for the runtime to proxy this class.", wovenClass.getClassName()), e); } else { throw weavingException(wovenClass, e); } } if(bytes != null && bytes.length != 0) { wovenClass.setBytes(bytes); List<String> imports = wovenClass.getDynamicImports(); imports.add(IMPORT_A); imports.add(IMPORT_B); } } } private List<Pattern> parseMatchers(String matchers, String def) { String[] strings = (matchers != null ? matchers : def).split(","); List<Pattern> patterns = new ArrayList<Pattern>(); for (String str : strings) { str = str.trim(); if (str.length() != 0) { str = str.replaceAll("\\.", "\\\\."); str = str.replaceAll("\\*", ".*"); Pattern p = Pattern.compile(str); patterns.add(p); } } return patterns; } boolean isEnabled(String className) { return matches(enabled, className); } boolean isDisabled(String className) { return matches(disabled, className); } private boolean matches(List<Pattern> patterns, String className) { for (Pattern p : patterns) { if (p.matcher(className).matches()) { return true; } } return false; } public boolean isWoven(Class<?> clazz) { return WovenProxy.class.isAssignableFrom(clazz); } public boolean isSuperClassWoven(WovenClass wovenClass) { ClassReader cReader = new ClassReader(wovenClass.getBytes()); try { Class<?> superClass = Class.forName(cReader.getSuperName().replace('/', '.'), false, wovenClass.getBundleWiring().getClassLoader()); return WovenProxy.class.isAssignableFrom(superClass); } catch (ClassNotFoundException e) { throw weavingException(wovenClass, e); } } private boolean shouldWeave(WovenClass wovenClass) { // assume we weave boolean result = true; Object[] cs = controllers.getServices(); // if we have at least 1 weaving controller if (cs != null && cs.length > 0) { // first of all set to false. result = false; for (Object obj : cs) { ProxyWeavingController c = (ProxyWeavingController) obj; if (c.shouldWeave(wovenClass, this)) { // exit as soon as we get told to weave, otherwise keep going. return true; } } } return result; } private WeavingException weavingException(WovenClass wovenClass, Exception e) { String msg = format("There was a serious error trying to weave the class %s. See the associated exception for more information.", wovenClass.getClassName()); // This is a failure that should stop the class loading! LOGGER.error(msg, e); return new WeavingException(msg, e); } }