/** * * Copyright * 2009-2015 Jayway Products AB * 2016-2017 Föreningen Sambruk * * Licensed under AGPL, Version 3.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.gnu.org/licenses/agpl.txt * * 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 se.streamsource.streamflow.web.management; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.DynamicMBean; import javax.management.InvalidAttributeValueException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanConstructorInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.ReflectionException; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; import org.qi4j.api.common.QualifiedName; import org.qi4j.api.composite.TransientComposite; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.injection.scope.Uses; import org.qi4j.api.property.Property; import org.qi4j.api.property.StateHolder; import org.qi4j.spi.Qi4jSPI; import org.slf4j.LoggerFactory; /** * Expose a TransientComposite as an MBean. All properties are used as JMX attributes. The rest * of the methods are exposed as operations. */ public class CompositeMBean extends NotificationBroadcasterSupport implements DynamicMBean { private TransientComposite composite; private StateHolder state; private Map<String, QualifiedName> names = new HashMap<String, QualifiedName>(); private MBeanInfo info; private List<Method> operationMethods = new ArrayList<Method>(); private AppenderSkeleton appender; private Class exposedInterface; private ExecutorService executor; public CompositeMBean( @Uses final TransientComposite composite, @Uses final Class exposedInterface, @Uses final ResourceBundle resourceBundle, @Structure Qi4jSPI spi ) { this.exposedInterface = exposedInterface; String description = resourceBundle.getString( "mbean.description" ); // Find state final List<MBeanAttributeInfo> attributes = new ArrayList<MBeanAttributeInfo>(); state = spi.getState( composite ); state.visitProperties( new StateHolder.StateVisitor<RuntimeException>() { public void visitProperty( QualifiedName name, Object value ) { Property property = state.getProperty( name ); MBeanAttributeInfo attribute = new MBeanAttributeInfo( property.qualifiedName().name(), property.type().toString(), resourceBundle.getString( property.qualifiedName().name() + ".description" ), true, !(property.isImmutable() || property.isComputed()), false ); names.put( name.name(), name ); attributes.add( attribute ); } } ); List<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>(); for (Method method : exposedInterface.getMethods()) { if (!method.getReturnType().equals( Property.class )) { List<MBeanParameterInfo> parameters = new ArrayList<MBeanParameterInfo>(); int idx = 1; for (Class<?> parameterType : method.getParameterTypes()) { String name = resourceBundle.getString( method.getName() + ".parameter" + idx + ".name" ); String paramDescription = resourceBundle.getString( method.getName() + ".parameter" + idx + ".description" ); String type = parameterType.getName(); MBeanParameterInfo paramInfo = new MBeanParameterInfo( name, type, paramDescription ); parameters.add( paramInfo ); idx++; } String impactType = resourceBundle.getString( method.getName() + ".impact" ); try { MBeanOperationInfo operation = new MBeanOperationInfo( method.getName(), resourceBundle.getString( method.getName() + ".description" ), parameters.toArray( new MBeanParameterInfo[parameters.size()] ), method.getReturnType().getName(), MBeanOperationInfo.class.getField( impactType ).getInt( null ) ); operationMethods.add( method ); operations.add( operation ); } catch (Exception e) { throw new IllegalArgumentException( "Unknown impact type:" + impactType ); } } } info = new MBeanInfo( exposedInterface.getName(), description, attributes.toArray( new MBeanAttributeInfo[attributes.size()] ), new MBeanConstructorInfo[0], operations.toArray( new MBeanOperationInfo[operations.size()] ), new MBeanNotificationInfo[]{new MBeanNotificationInfo(new String[]{"info","warning","error"}, "Log", "Log")} ); this.composite = composite; appender = new AppenderSkeleton() { long seq = 0; @Override protected void append( LoggingEvent event ) { final Notification notification = new Notification( event.getLevel().toString(), event.getLoggerName(), seq++, event.getTimeStamp(), event.getMessage() == null ? null : event.getMessage().toString() ); ThrowableInformation throwable = event.getThrowableInformation(); if (throwable != null) { StringWriter writer = new StringWriter(); PrintWriter printWriter = new PrintWriter( writer ); throwable.getThrowable().printStackTrace( printWriter ); printWriter.close(); notification.setUserData( writer.toString() ); } sendNotification( notification ); } public void close() { Logger.getLogger( exposedInterface ).removeAppender( this ); } public boolean requiresLayout() { return false; } }; executor = Executors.newSingleThreadExecutor(); } public Object getAttribute( String name ) throws AttributeNotFoundException, MBeanException, ReflectionException { QualifiedName name1 = names.get( name ); if (name1 == null) throw new AttributeNotFoundException( name ); Property prop = state.getProperty( name1 ); return prop.get(); } public void setAttribute( Attribute attribute ) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { QualifiedName qualifiedName = names.get( attribute.getName() ); if (qualifiedName == null) throw new AttributeNotFoundException( attribute.getName() ); Property prop = state.getProperty( qualifiedName ); prop.set( attribute.getValue() ); } public AttributeList getAttributes( String[] names ) { AttributeList list = new AttributeList(); for (String name : names) { try { Object value = getAttribute( name ); list.add( new Attribute( name, value ) ); } catch (AttributeNotFoundException e) { e.printStackTrace(); } catch (MBeanException e) { e.printStackTrace(); } catch (ReflectionException e) { e.printStackTrace(); } } return list; } public AttributeList setAttributes( AttributeList attributeList ) { AttributeList list = new AttributeList(); for (int i = 0; i < list.size(); i++) { Attribute attribute = (Attribute) list.get( i ); try { setAttribute( attribute ); list.add( attribute ); } catch (AttributeNotFoundException e) { e.printStackTrace(); } catch (InvalidAttributeValueException e) { e.printStackTrace(); } catch (MBeanException e) { e.printStackTrace(); } catch (ReflectionException e) { e.printStackTrace(); } } return list; } public synchronized Object invoke( String s, final Object[] arguments, String[] parameterTypes ) throws MBeanException, ReflectionException { final Method method = getOperation( s, parameterTypes ); final Callable<Object> call = new Callable<Object>() { public Object call() throws Exception { try { Logger.getRootLogger().addAppender( appender ); return method.invoke( composite, arguments ); } catch (IllegalAccessException e) { throw new ReflectionException( e ); } catch (InvocationTargetException e) { throw new MBeanException( (Exception) e.getCause() ); } finally { Logger.getRootLogger().removeAppender( appender ); } } }; if (method.getReturnType().equals(Void.TYPE)) { executor.submit(new Callable<Object>() { public Object call() throws Exception { try { return call.call(); } catch (Exception e) { // Log exception Logger.getRootLogger().addAppender( appender ); LoggerFactory.getLogger( exposedInterface ).error( "Could not complete "+method.getName(), e ); Logger.getRootLogger().removeAppender( appender ); throw e; } } }); return method.getName()+" started. See notification log for details"; } else { try { return call.call(); } catch (Exception e) { throw new ReflectionException(e); } } } private Method getOperation( String s, String[] parameterTypes ) throws ReflectionException { nextoperation: for (int idx = 0; idx < info.getOperations().length; idx++) { MBeanOperationInfo mBeanOperationInfo = info.getOperations()[idx]; if (mBeanOperationInfo.getName().equals( s )) { MBeanParameterInfo[] params = mBeanOperationInfo.getSignature(); for (int i = 0; i < parameterTypes.length; i++) { String parameterType = parameterTypes[i]; if (params.length < i || !parameterType.equals( params[i].getType() )) continue nextoperation; } return operationMethods.get( idx ); } } throw new ReflectionException(new NoSuchMethodException(s)); } @Override public MBeanNotificationInfo[] getNotificationInfo() { return info.getNotifications(); } public MBeanInfo getMBeanInfo() { return info; } }