001package org.apache.archiva.redback.common.config.acc2; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.archiva.redback.common.config.api.AsyncListener; 023import org.apache.archiva.redback.common.config.api.ConfigRegistry; 024import org.apache.archiva.redback.common.config.api.EventType; 025import org.apache.archiva.redback.common.config.api.RegistryListener; 026import org.apache.commons.configuration2.event.ConfigurationEvent; 027import org.apache.commons.configuration2.event.Event; 028import org.apache.commons.configuration2.event.EventListener; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031import org.springframework.context.ApplicationContext; 032import org.springframework.core.task.TaskExecutor; 033 034import java.util.Iterator; 035import java.util.LinkedHashMap; 036import java.util.Map; 037import java.util.Objects; 038import java.util.WeakHashMap; 039 040/** 041 * This class maps apache commons configuration events into redback configuration events. 042 * 043 * @author Martin Stockhammer <martin_s@apache.org> 044 */ 045 046public class CfgListener implements EventListener 047{ 048 049 ConfigRegistry registry; 050 051 Map<String, ListenerInfo> listeners = new LinkedHashMap<>( ); 052 WeakHashMap<EventInfo, Object> oldValueStore = new WeakHashMap<>( ); 053 054 Logger logger = LoggerFactory.getLogger( CfgListener.class ); 055 056 CfgListener( ConfigRegistry registry ) 057 { 058 this.registry = registry; 059 } 060 061 TaskExecutor defaultExecutor; 062 063 ApplicationContext applicationContext; 064 065 private final class ListenerInfo 066 { 067 final String prefix; 068 final RegistryListener listener; 069 final boolean async; 070 final TaskExecutor executor; 071 072 public ListenerInfo( String prefix, RegistryListener listener ) 073 { 074 this.prefix = prefix; 075 this.listener = listener; 076 Class<? extends RegistryListener> clazz = listener.getClass( ); 077 boolean async = clazz.isAnnotationPresent( AsyncListener.class ); 078 try 079 { 080 AsyncListener classAnnotation = clazz.getAnnotation( AsyncListener.class ); 081 AsyncListener methodAnnotation = clazz.getMethod( "handleConfigurationChangeEvent", ConfigRegistry.class, EventType.class, String.class, Object.class, Object.class ).getAnnotation( AsyncListener.class ); 082 this.async = methodAnnotation != null || classAnnotation != null; 083 String executorString = methodAnnotation != null ? methodAnnotation.value( ) : ( classAnnotation != null ? classAnnotation.value( ) : null ); 084 TaskExecutor newExec; 085 if ( executorString == null ) 086 { 087 newExec = defaultExecutor; 088 } 089 else 090 { 091 newExec = applicationContext.getBean( executorString, TaskExecutor.class ); 092 if ( newExec == null ) 093 { 094 newExec = defaultExecutor; 095 } 096 } 097 this.executor = newExec; 098 } 099 catch ( NoSuchMethodException e ) 100 { 101 throw new RuntimeException( "Fatal error! EventListener methods not found. Maybe you have the wrong version of EventLister in your classpath." ); 102 } 103 } 104 105 } 106 107 108 private final class EventInfo 109 { 110 final org.apache.commons.configuration2.event.EventType type; 111 final String name; 112 final Object value; 113 114 EventInfo( org.apache.commons.configuration2.event.EventType type, String name, Object value ) 115 { 116 this.type = type; 117 this.name = name; 118 this.value = value; 119 } 120 121 @Override 122 public int hashCode( ) 123 { 124 return Objects.hash( type, name, value ); 125 } 126 127 public boolean equals( EventInfo obj ) 128 { 129 return Objects.equals( this.type, obj.type ) && Objects.equals( this.name, obj.name ) && Objects.equals( this.value, obj.value ); 130 } 131 132 } 133 134 /** 135 * This method stores old values in the 136 * @param event 137 */ 138 public void onEvent( org.apache.commons.configuration2.event.ConfigurationEvent event ) 139 { 140 logger.debug( "Event " + event.getClass( ).getName( ) + " Source Class: " + event.getSource( ).getClass( ).getName( ) ); 141 logger.debug( "EventType " + event.getEventType( ) + ", EventProperty: " + event.getPropertyName( ) + ", EventValue: " + event.getPropertyValue( ) ); 142 if ( event.isBeforeUpdate( ) ) 143 { 144 logger.debug( "Event before update" ); 145 Object oldValue = registry.getValue( event.getPropertyName( ) ); 146 oldValueStore.put( new EventInfo( event.getEventType( ), event.getPropertyName( ), event.getPropertyValue( ) ), oldValue ); 147 } 148 else 149 { 150 logger.debug( "Event after update" ); 151 final EventType type = transformEventType( event.getEventType( ) ); 152 final Object oldValue = oldValueStore.remove( new EventInfo( event.getEventType( ), event.getPropertyName( ), event.getPropertyValue( ) ) ); 153 final String propertyName = event.getPropertyName(); 154 final Object newValue = event.getPropertyValue(); 155 listeners.entrySet( ).stream( ).filter( entry -> event.getPropertyName( ).startsWith( entry.getKey( ) ) ).forEach( 156 entry -> 157 callListener( entry.getValue(), type, propertyName, newValue, oldValue ) 158 159 ); 160 } 161 162 } 163 164 private void callListener(ListenerInfo li, EventType type, String propertyName, Object newValue, Object oldValue) { 165 try 166 { 167 if ( li.async ) 168 { 169 li.executor.execute( ( ) -> li.listener.handleConfigurationChangeEvent( registry, type, propertyName, newValue, oldValue ) ); 170 } 171 else 172 { 173 li.listener.handleConfigurationChangeEvent( registry, type, propertyName, newValue, oldValue ); 174 } 175 } catch (Throwable ex) { 176 logger.error( "Listener exception occured: "+ex.getMessage(), ex); 177 // Exception is catched allow to call the other listeners. 178 } 179 } 180 181 private EventType transformEventType( org.apache.commons.configuration2.event.EventType<? extends Event> type ) 182 { 183 184 if ( type.equals( ConfigurationEvent.ADD_PROPERTY ) ) 185 { 186 return EventType.PROPERTY_ADDED; 187 } 188 else if ( type.equals( ConfigurationEvent.CLEAR_PROPERTY ) ) 189 { 190 return EventType.PROPERTY_CLEARED; 191 } 192 else if ( type.equals( ConfigurationEvent.SET_PROPERTY ) ) 193 { 194 return EventType.PROPERTY_SET; 195 } 196 else 197 { 198 return EventType.UNDEFINED; 199 } 200 } 201 202 @Override 203 public void onEvent( Event event ) 204 { 205 if ( event instanceof ConfigurationEvent ) 206 { 207 onEvent( (ConfigurationEvent) event ); 208 } 209 else 210 { 211 logger.debug( "Event " + event.getClass( ).getName( ) + " Source Class: " + event.getSource( ).getClass( ).getName( ) ); 212 logger.debug( "EventType " + event.getEventType( ) ); 213 } 214 } 215 216 public void registerChangeListener( RegistryListener listener, String prefix ) 217 { 218 listeners.put( prefix, new ListenerInfo( prefix, listener ) ); 219 } 220 221 public boolean unregisterChangeListener( RegistryListener listener ) 222 { 223 boolean found = false; 224 Iterator<Map.Entry<String, ListenerInfo>> it = listeners.entrySet( ).iterator( ); 225 while ( it.hasNext( ) ) 226 { 227 Map.Entry<String, ListenerInfo> e = it.next( ); 228 if ( e.getValue( ).listener == listener ) 229 { 230 it.remove( ); 231 found = true; 232 } 233 } 234 return found; 235 } 236 237 public TaskExecutor getDefaultExecutor( ) 238 { 239 return defaultExecutor; 240 } 241 242 public void setDefaultExecutor( TaskExecutor defaultExecutor ) 243 { 244 this.defaultExecutor = defaultExecutor; 245 } 246 247 248 public ApplicationContext getApplicationContext( ) 249 { 250 return applicationContext; 251 } 252 253 public void setApplicationContext( ApplicationContext applicationContext ) 254 { 255 this.applicationContext = applicationContext; 256 } 257}