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}