/************************************************************************* * Copyright 2009-2014 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.bootstrap; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.SortedSet; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.persistence.PersistenceContext; import org.apache.log4j.Logger; import com.eucalyptus.entities.PersistenceContexts; import com.eucalyptus.records.EventRecord; import com.eucalyptus.records.EventType; import com.eucalyptus.system.Ats; import com.eucalyptus.system.BaseDirectory; import com.eucalyptus.util.Classes; import com.eucalyptus.util.LogUtil; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; /** * TODO: DOCUMENT */ public abstract class ServiceJarDiscovery implements Comparable<ServiceJarDiscovery> { private static Logger LOG = Logger.getLogger( ServiceJarDiscovery.class ); private static SortedSet<ServiceJarDiscovery> discovery = Sets.newTreeSet( ); private static Multimap<Class, String> classList = ArrayListMultimap.create( ); enum JarFilePass { CLASSES { @Override public void process( File f ) throws Exception { final JarFile jar = new JarFile( f ); final Properties props = new Properties( ); final List<JarEntry> jarList = Collections.list( jar.entries( ) ); LOG.trace( "-> Trying to load component info from " + f.getAbsolutePath( ) ); for ( final JarEntry j : jarList ) { try { if ( j.getName( ).matches( ".*\\.class.{0,1}" ) ) { handleClassFile( f, j ); } } catch ( RuntimeException ex ) { LOG.error( ex, ex ); jar.close( ); throw ex; } } jar.close( ); } private void handleClassFile( final File f, final JarEntry j ) throws IOException, RuntimeException { final String classGuess = j.getName( ).replaceAll( "/", "." ).replaceAll( "\\.class.{0,1}", "" ); try { final Class candidate = ClassLoader.getSystemClassLoader( ).loadClass( classGuess ); classList.put( candidate, f.getAbsolutePath( ) ); if ( ServiceJarDiscovery.class.isAssignableFrom( candidate ) && !ServiceJarDiscovery.class.equals( candidate ) && !candidate.isAnonymousClass( ) ) { try { final ServiceJarDiscovery discover = ( ServiceJarDiscovery ) candidate.newInstance( ); discovery.add( discover ); } catch ( final Exception e ) { LOG.fatal( e, e ); throw new RuntimeException( e ); } } else if ( Ats.from( candidate ).has( Bootstrap.Discovery.class ) && Predicate.class.isAssignableFrom( candidate ) ) { try { @SuppressWarnings( { "rawtypes", "unchecked" } ) final ServiceJarDiscovery discover = new ServiceJarDiscovery( ) { final Bootstrap.Discovery annote = Ats.from( candidate ).get( Bootstrap.Discovery.class ); final Predicate<Class> instance = ( Predicate<Class> ) Classes.builder( candidate ).newInstance( ); @Override public boolean processClass( Class discoveryCandidate ) throws Exception { boolean classFiltered = this.annote.value( ).length != 0 ? Iterables.any( Arrays.asList( this.annote.value( ) ), Classes.assignableTo( discoveryCandidate ) ) : true; if ( classFiltered ) { boolean annotationFiltered = this.annote.annotations( ).length != 0 ? Iterables.any( Arrays.asList( this.annote.annotations( ) ), Ats.from( discoveryCandidate ) ) : true; if ( annotationFiltered ) { return this.instance.apply( discoveryCandidate ); } else { return false; } } else { return false; } } @Override public Double getPriority( ) { return this.annote.priority( ); } }; discovery.add( discover ); } catch ( final Exception e ) { LOG.fatal( e, e ); throw new RuntimeException( e ); } } } catch ( final ClassNotFoundException e ) { LOG.debug( e, e ); } } }; JarFilePass( ) {} public abstract void process( final File f ) throws Exception; } private static void doDiscovery( ) { final File libDir = new File( BaseDirectory.LIB.toString( ) ); for ( final File f : libDir.listFiles( ) ) { if ( f.getName( ).startsWith( "eucalyptus" ) && f.getName( ).endsWith( ".jar" ) && !f.getName( ).matches( ".*-ext-.*" ) ) { LOG.debug( "Found eucalyptus component jar: " + f.getName( ) ); try { ServiceJarDiscovery.JarFilePass.CLASSES.process( f ); } catch ( final Throwable e ) { LOG.error( e.getMessage( ) ); continue; } } } ServiceJarDiscovery.runDiscovery( ); } public static void doSingleDiscovery( final ServiceJarDiscovery s ) { final File libDir = new File( BaseDirectory.LIB.toString( ) ); for ( final File f : libDir.listFiles( ) ) { if ( f.getName( ).startsWith( "eucalyptus" ) && f.getName( ).endsWith( ".jar" ) && !f.getName( ).matches( ".*-ext-.*" ) ) { LOG.debug( "Found eucalyptus component jar: " + f.getName( ) ); try { ServiceJarDiscovery.JarFilePass.CLASSES.process( f ); } catch ( final Throwable e ) { LOG.error( e.getMessage( ) ); continue; } } } ServiceJarDiscovery.runDiscovery( s ); } public static void checkUniqueness( final Class c ) { if ( classList.get( c ).size( ) > 1 ) { LOG.fatal( "Duplicate bootstrap class registration: " + c.getName( ) ); for ( final String fileName : classList.get( c ) ) { LOG.fatal( "\n==> Defined in: " + fileName ); } System.exit( 1 );//GRZE: special case, broken installation } } public static void runDiscovery( ) { for ( final ServiceJarDiscovery s : discovery ) { EventRecord.here( ServiceJarDiscovery.class, EventType.BOOTSTRAP_INIT_DISCOVERY, s.getClass( ).getCanonicalName( ) ).trace( ); } for ( final ServiceJarDiscovery s : discovery ) { runDiscovery( s ); } } public static void runDiscovery( final ServiceJarDiscovery s ) { LOG.info( LogUtil.subheader( s.getClass( ).getSimpleName( ) ) ); for ( final Class c : classList.keySet( ) ) { try { s.checkClass( c ); } catch ( final Throwable t ) { LOG.debug( t, t ); } } } private void checkClass( final Class candidate ) { try { if ( this.processClass( candidate ) ) { ServiceJarDiscovery.checkUniqueness( candidate ); EventRecord.here( ServiceJarDiscovery.class, EventType.DISCOVERY_LOADED_ENTRY, this.getClass( ).getSimpleName( ), candidate.getName( ) ).trace( ); } } catch ( final Throwable e ) { if ( e instanceof InstantiationException ) {} else { LOG.trace( e, e ); } } } /** * Process the potential bootstrap-related class. Return false or throw an exception if the class * is rejected. * * @param candidate * @return true if the candidate is accepted. * * @throws Exception */ public abstract boolean processClass( Class candidate ) throws Exception; public Double getDistinctPriority( ) { return this.getPriority( ) + ( .1d / this.getClass( ).hashCode( ) ); } public abstract Double getPriority( ); @Override public int compareTo( final ServiceJarDiscovery that ) { return this.getDistinctPriority( ).compareTo( that.getDistinctPriority( ) ); } public static void processLibraries( ) { final File libDir = new File( BaseDirectory.LIB.toString( ) ); for ( final File f : libDir.listFiles( ) ) { if ( f.getName( ).startsWith( "eucalyptus" ) && f.getName( ).endsWith( ".jar" ) && !f.getName( ).matches( ".*-ext-.*" ) ) { EventRecord.here( ServiceJarDiscovery.class, EventType.BOOTSTRAP_INIT_SERVICE_JAR, f.getName( ) ).info( ); try { ServiceJarDiscovery.JarFilePass.CLASSES.process( f ); } catch ( final Throwable e ) { Bootstrap.LOG.error( e.getMessage( ) ); continue; } } } } public static URLClassLoader makeClassLoader( final File libDir ) { final URLClassLoader loader = new URLClassLoader( Lists.transform( Arrays.asList( libDir.listFiles( ) ), new Function<File, URL>( ) { @Override public URL apply( final File arg0 ) { try { return URI.create( "file://" + arg0.getAbsolutePath( ) ).toURL( ); } catch ( final MalformedURLException e ) { LOG.debug( e, e ); return null; } } } ).toArray( new URL[] {} ) ); return loader; } public static List<String> contextsInDir( final File libDir ) { final ClassLoader oldLoader = Thread.currentThread( ).getContextClassLoader( ); try { Thread.currentThread( ).setContextClassLoader( makeClassLoader( libDir ) ); final Set<String> ctxs = Sets.newHashSet( ); for ( final Class candidate : getClassList( libDir ) ) { if ( PersistenceContexts.isEntityClass( candidate ) ) { if ( Ats.from( candidate ).has( PersistenceContext.class ) ) { ctxs.add( Ats.from( candidate ).get( PersistenceContext.class ).name( ) ); } } } return Lists.newArrayList( ctxs ); } finally { Thread.currentThread( ).setContextClassLoader( oldLoader ); } } public static List<Class> classesInDir( final File libDir ) { final ClassLoader oldLoader = Thread.currentThread( ).getContextClassLoader( ); try { Thread.currentThread( ).setContextClassLoader( makeClassLoader( libDir ) ); return getClassList( libDir ); } finally { Thread.currentThread( ).setContextClassLoader( oldLoader ); } } private static List<Class> getClassList( final File libDir ) { final List<Class> classList = Lists.newArrayList( ); for ( final File f : libDir.listFiles( ) ) { if ( f.getName( ).startsWith( "eucalyptus" ) && f.getName( ).endsWith( ".jar" ) && !f.getName( ).matches( ".*-ext-.*" ) ) { // LOG.trace( "Found eucalyptus component jar: " + f.getName( ) ); try { final JarFile jar = new JarFile( f ); for ( final JarEntry j : Collections.list( jar.entries( ) ) ) { if ( j.getName( ).matches( ".*\\.class.{0,1}" ) ) { final String classGuess = j.getName( ).replaceAll( "/", "." ).replaceAll( "\\.class.{0,1}", "" ); try { final Class candidate = ClassLoader.getSystemClassLoader( ).loadClass( classGuess ); classList.add( candidate ); } catch ( final ClassNotFoundException e ) { // LOG.trace( e, e ); } } } jar.close( ); } catch ( final Throwable e ) { LOG.error( e.getMessage( ) ); continue; } } } return classList; } }