Improving event API

This commit is contained in:
Martin Schreier 2022-02-13 10:27:32 +01:00
parent 99bd81ac00
commit 0a2dab1677
41 changed files with 942 additions and 132 deletions

View File

@ -76,6 +76,12 @@
<artifactId>jcl-over-slf4j</artifactId> <artifactId>jcl-over-slf4j</artifactId>
</dependency> </dependency>
<!-- Test scope -->
<dependency>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-api</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.archiva</groupId> <groupId>org.apache.archiva</groupId>
<artifactId>archiva-consumer-api</artifactId> <artifactId>archiva-consumer-api</artifactId>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>archiva-base</artifactId>
<groupId>org.apache.archiva</groupId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>Archiva :: Base :: Event API</name>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-api</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.archiva</groupId>
<artifactId>archiva-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,5 +1,4 @@
package org.apache.archiva.event; package org.apache.archiva.event;
/* /*
* Licensed to the Apache Software Foundation (ASF) under one * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file * or more contributor license agreements. See the NOTICE file
@ -10,7 +9,6 @@ package org.apache.archiva.event;
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, * Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an * software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@ -22,24 +20,18 @@ package org.apache.archiva.event;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.*; import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class EventManager implements EventSource /**
* @author Martin Schreier <martin_s@apache.org>
*/
public class AbstractEventManager implements EventSource
{ {
private static final Logger log = LoggerFactory.getLogger( AbstractEventManager.class );
private static final Logger LOG = LoggerFactory.getLogger(EventManager.class); protected final ConcurrentHashMap<EventType<? extends Event>, Set<EventHandler>> handlerMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<EventType<? extends Event>, Set<EventHandler>> handlerMap = new ConcurrentHashMap<>();
private final Object source;
public EventManager(Object source) {
if (source==null) {
throw new IllegalArgumentException("The source may not be null");
}
this.source = source;
}
@Override @Override
public <T extends Event> void registerEventHandler( EventType<T> type, EventHandler<? super T> eventHandler) { public <T extends Event> void registerEventHandler( EventType<T> type, EventHandler<? super T> eventHandler) {
@ -47,16 +39,28 @@ public class EventManager implements EventSource
if (!handlers.contains(eventHandler)) { if (!handlers.contains(eventHandler)) {
handlers.add(eventHandler); handlers.add(eventHandler);
} }
log.debug( "Event handler registered: " + eventHandler.getClass( ) );
} }
@Override @Override
public <T extends Event> void unregisterEventHandler( EventType<T> type, EventHandler<? super T> eventHandler) { public <T extends Event> void unregisterEventHandler( EventType<T> type, EventHandler<? super T> eventHandler) {
if (handlerMap.containsKey(type)) { if (handlerMap.containsKey(type)) {
handlerMap.get(type).remove(eventHandler); handlerMap.get(type).remove(eventHandler);
log.debug( "Event handler unregistered: " + eventHandler.getClass( ) );
} }
} }
public void fireEvent(Event fireEvent) { /**
* Fires the given event for the given source. If the source of the provided event does not match the <code>source</code>
* parameter the event will be chained.
*
* The event will be sent to all registered event handler. Exceptions during handling are not propagated to the
* caller.
*
* @param fireEvent the event to fire
* @param source the source object
*/
public void fireEvent(Event fireEvent, Object source) {
final EventType<? extends Event> type = fireEvent.getType(); final EventType<? extends Event> type = fireEvent.getType();
Event event; Event event;
if (fireEvent.getSource()!=source) { if (fireEvent.getSource()!=source) {
@ -69,9 +73,9 @@ public class EventManager implements EventSource
for (EventHandler handler : handlerMap.get(handlerType)) { for (EventHandler handler : handlerMap.get(handlerType)) {
try { try {
handler.handle(event); handler.handle(event);
} catch (Exception e) { } catch (Throwable e) {
// We catch all errors from handlers // We catch all errors from handlers
LOG.error("An error occured during event handling: {}", e.getMessage(), e); log.error("An error occured during event handling: {}", e.getMessage(), e);
} }
} }
} }

View File

@ -0,0 +1,41 @@
package org.apache.archiva.event;
/*
* 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.
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BasicEventManager extends AbstractEventManager implements EventSource
{
private static final Logger LOG = LoggerFactory.getLogger( BasicEventManager.class);
private final Object source;
public BasicEventManager( Object source) {
if (source==null) {
throw new IllegalArgumentException("The source may not be null");
}
this.source = source;
}
public void fireEvent(Event fireEvent) {
super.fireEvent( fireEvent, source );
}
}

View File

@ -10,7 +10,6 @@ package org.apache.archiva.event;
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, * Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an * software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@ -21,6 +20,9 @@ package org.apache.archiva.event;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.EventObject; import java.util.EventObject;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/** /**
* Base class for events. Events have a type and a source. * Base class for events. Events have a type and a source.
@ -41,6 +43,8 @@ public class Event extends EventObject implements Cloneable {
private final EventType<? extends Event> type; private final EventType<? extends Event> type;
private final LocalDateTime createTime; private final LocalDateTime createTime;
private HashMap<Class<? extends EventContext>, EventContext> contextMap = new HashMap<>( );
public Event(EventType<? extends Event> type, Object originator) { public Event(EventType<? extends Event> type, Object originator) {
super(originator); super(originator);
this.type = type; this.type = type;
@ -52,6 +56,7 @@ public class Event extends EventObject implements Cloneable {
this.previous = previous; this.previous = previous;
this.type = previous.getType(); this.type = previous.getType();
this.createTime = previous.getCreateTime(); this.createTime = previous.getCreateTime();
this.contextMap = previous.contextMap;
} }
/** /**
@ -70,6 +75,38 @@ public class Event extends EventObject implements Cloneable {
return createTime; return createTime;
} }
public <T extends EventContext> T getContext(Class<T> contextClazz) throws IllegalArgumentException {
if (contextMap.containsKey( contextClazz )) {
return (T) contextMap.get( contextClazz );
} else {
T ctx = null;
for ( Map.Entry<Class<? extends EventContext>, EventContext> clazzEntry : contextMap.entrySet()) {
if ( contextClazz.isAssignableFrom( clazzEntry.getKey() ) )
{
ctx = (T) clazzEntry.getValue( );
break;
}
}
if (ctx!=null) {
contextMap.put( contextClazz, ctx );
return ctx;
}
}
throw new IllegalArgumentException( "No matching event context registered for " + contextClazz );
}
public Map<String, String> getContextData() {
return contextMap.entrySet( ).stream( ).flatMap( ctx -> ctx.getValue( ).getData( ).entrySet( ).stream( ) )
.collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
}
public <T extends EventContext> void setContext( Class<T> clazz, T context) {
this.contextMap.put( clazz, context );
}
public <T extends EventContext> void setContext( T context) {
this.contextMap.put( context.getClass(), context );
}
/** /**
* Recreates the event with the given instance as the new source. The * Recreates the event with the given instance as the new source. The
@ -81,6 +118,7 @@ public class Event extends EventObject implements Cloneable {
Event newEvent = (Event) this.clone(); Event newEvent = (Event) this.clone();
newEvent.previous = this; newEvent.previous = this;
newEvent.source = newSource; newEvent.source = newSource;
newEvent.contextMap = this.contextMap;
return newEvent; return newEvent;
} }

View File

@ -0,0 +1,48 @@
package org.apache.archiva.event;
/*
* 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.
*/
import java.util.Map;
/**
* Context information about a specific event.
* This is used to provide specific information about the event context by using the generic
* event interface.
* Some event handler may need information about the underlying event but have no access to the
* API classes that represent the event.
*
* Context information is always string based and should not depend on external classes apart from JDK classes.
*
* @author Martin Schreier <martin_s@apache.org>
*/
public interface EventContext
{
/**
* Returns the prefix used for entry keys in the repository data map.
* @return the prefix string for this context
*/
String getPrefix();
/**
* Returns the context data as map of strings. Each entry key is prefixed with
* the unique prefix of this context.
*
* @return the map of key value pairs stored in this context
*/
Map<String,String> getData();
}

View File

@ -0,0 +1,72 @@
package org.apache.archiva.event;
/*
* 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.
*/
import org.apache.archiva.event.context.RepositoryContext;
import org.apache.archiva.event.context.RestContext;
import org.apache.archiva.event.context.UserContext;
/**
* Static helper class that allows to set certain context data
*
* @author Martin Schreier <martin_s@apache.org>
*/
public class EventContextBuilder
{
Event evt;
public static void setUserContext(Event evt, String user, String remoteAddress) {
evt.setContext( UserContext.class, new UserContext( user, remoteAddress ) );
}
public static void setRestcontext(Event evt, String service, String path, String operation, int resultCode, String... parameters ) {
evt.setContext( RestContext.class, new RestContext( service, path, operation, resultCode, parameters ) );
}
public static void setRepositoryContext(Event evt, String id, String type, String flavour ) {
evt.setContext( RepositoryContext.class, new RepositoryContext( id, type, flavour ) );
}
private EventContextBuilder( Event evt) {
this.evt = evt;
}
public static EventContextBuilder withEvent( Event evt )
{
return new EventContextBuilder( evt );
}
public EventContextBuilder withUser( String user, String remoteAddress) {
setUserContext( this.evt, user, remoteAddress );
return this;
}
public EventContextBuilder witRest( String service, String path, String operation, int resultCode, String... parameters) {
setRestcontext( this.evt, service, path, operation, resultCode, parameters );
return this;
}
public EventContextBuilder withRepository(String id, String type, String flavour) {
setRepositoryContext( this.evt, id, type, flavour );
return this;
}
public Event apply() {
return this.evt;
}
}

View File

@ -10,7 +10,6 @@ package org.apache.archiva.event;
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, * Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an * software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@ -82,6 +81,18 @@ public class EventType<T extends Event> implements Serializable {
return superType; return superType;
} }
public String getPath() {
List<String> path = new ArrayList<>( );
EventType eventType = this;
while(eventType!=ROOT)
{
path.add( eventType.name( ) );
eventType = this.getSuperType( );
}
Collections.reverse( path );
return String.join( "/", path );
}
private void register(EventType<? extends T> subType) { private void register(EventType<? extends T> subType) {
if (subTypes == null) { if (subTypes == null) {
subTypes = new WeakHashMap<>(); subTypes = new WeakHashMap<>();

View File

@ -0,0 +1,90 @@
package org.apache.archiva.event.context;
/*
* 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.
*/
import org.apache.archiva.event.EventContext;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* This context provides repository data.
*
* @author Martin Schreier <martin_s@apache.org>
*/
public class RepositoryContext implements EventContext, Serializable
{
private static final long serialVersionUID = -4172663291198878307L;
private static final String PREFIX = "repository";
private final String id;
private final String type;
private final String flavour;
public RepositoryContext( String id, String type, String flavour )
{
this.id = id;
this.type = type;
this.flavour = flavour;
}
/**
* Returns the repository id
* @return the repository id
*/
public String getId( )
{
return id;
}
/**
* Returns the repository type (e.g. MAVEN)
* @return the string representation of the repository type
*/
public String getType( )
{
return type;
}
/**
* Returns the repository flavour (e.g. Remote, Managed, Group)
* @return
*/
public String getFlavour( )
{
return flavour;
}
@Override
public Map<String, String> getData( )
{
Map<String, String> values = new HashMap<>( );
values.put( PREFIX+".id", id );
values.put( PREFIX+".type", type );
values.put( PREFIX+".flavour", flavour );
return values;
}
@Override
public String getPrefix( )
{
return PREFIX;
}
}

View File

@ -0,0 +1,96 @@
package org.apache.archiva.event.context;
/*
* 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.
*/
import org.apache.archiva.event.EventContext;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Provides information about a REST call.
*
* @author Martin Schreier <martin_s@apache.org>
*/
public class RestContext implements EventContext, Serializable
{
private static final long serialVersionUID = -4109505194250928317L;
public static final String PREFIX = "rest";
private final String service;
private final String path;
private final String operation;
private final List<String> parameters;
private final int resultCode;
public RestContext( String service, String path, String operation, int resultCode, String... parameters )
{
this.service = service;
this.path = path;
this.operation = operation;
this.resultCode = resultCode;
this.parameters = Arrays.asList( parameters );
}
public String getService( )
{
return service;
}
public String getPath( )
{
return path;
}
public String getOperation( )
{
return operation;
}
public List<String> getParameters( )
{
return parameters;
}
public int getResultCode( )
{
return resultCode;
}
@Override
public Map<String, String> getData( )
{
Map<String, String> values = new HashMap<>( );
values.put( PREFIX+".service", service );
values.put( PREFIX+".path", path );
values.put( PREFIX+".operation", operation );
values.put( PREFIX+".parameter", String.join( ",", parameters ) );
return values;
}
@Override
public String getPrefix( )
{
return PREFIX;
}
}

View File

@ -0,0 +1,71 @@
package org.apache.archiva.event.context;
/*
* 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.
*/
import org.apache.archiva.event.EventContext;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* This context provides user information.
*
* @author Martin Schreier <martin_s@apache.org>
*/
public class UserContext implements EventContext, Serializable
{
private static final long serialVersionUID = -3499164111736559781L;
private static final String PREFIX = "user";
private final String userId;
private final String remoteAddress;
public UserContext( String user, String remoteAddress )
{
this.userId = user == null ? "" : user;
this.remoteAddress = remoteAddress == null ? "" : remoteAddress;
}
public String getUserId( )
{
return userId;
}
public String getRemoteAddress( )
{
return remoteAddress;
}
@Override
public Map<String, String> getData( )
{
Map<String, String> values = new HashMap<>( );
values.put( PREFIX+".user_id", userId );
values.put( PREFIX+".remote_address", remoteAddress );
return values;
}
@Override
public String getPrefix( )
{
return PREFIX;
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.
*/
/**
* This module provides an event mechanism for all archiva subsystems.
*
* The events are hierarchical organized. That means each subsystem has its own event manager that collects events
* and processes and forwards them to the parent event manager (normally the central event manager).
* Each event manager clones the event and stores the origin event in the chain before forwarding them to the parent manager.
*
* Event Types are also hierarchical. There is one special type {@link org.apache.archiva.event.EventType#ROOT} that is the
* root type and has no parent type. All other types must be descendants of the ROOT type.
*
* Event types may have certain methods to access context information. But context information can also be accessed in a
* subsystem independent way using the event context data. Event contexts provide access to data without using the
* subsystem API and classes.
* Event types may be used for filtering events.
*
* @since 3.0
* @author Martin Schreier <martin_s@apache.org>
*/
package org.apache.archiva.event;

View File

@ -10,7 +10,6 @@ package org.apache.archiva.event;
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, * Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an * software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@ -29,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.*;
/** /**
* @author Martin Stockhammer <martin_s@apache.org> * @author Martin Stockhammer <martin_s@apache.org>
*/ */
public class EventManagerTest public class BasicEventManagerTest
{ {
private class TestHandler implements EventHandler<Event> { private class TestHandler implements EventHandler<Event> {
@ -53,7 +52,7 @@ public class EventManagerTest
@Test @Test
public void registerEventHandler( ) public void registerEventHandler( )
{ {
EventManager eventManager = new EventManager( this ); BasicEventManager eventManager = new BasicEventManager( this );
TestHandler handler1 = new TestHandler( ); TestHandler handler1 = new TestHandler( );
TestHandler handler2 = new TestHandler( ); TestHandler handler2 = new TestHandler( );
TestHandler handler3 = new TestHandler( ); TestHandler handler3 = new TestHandler( );
@ -92,7 +91,7 @@ public class EventManagerTest
@Test @Test
public void unregisterEventHandler( ) public void unregisterEventHandler( )
{ {
EventManager eventManager = new EventManager( this ); BasicEventManager eventManager = new BasicEventManager( this );
TestHandler handler1 = new TestHandler( ); TestHandler handler1 = new TestHandler( );
TestHandler handler2 = new TestHandler( ); TestHandler handler2 = new TestHandler( );
TestHandler handler3 = new TestHandler( ); TestHandler handler3 = new TestHandler( );
@ -124,7 +123,7 @@ public class EventManagerTest
public void fireEvent( ) public void fireEvent( )
{ {
Object other = new Object( ); Object other = new Object( );
EventManager eventManager = new EventManager( this ); BasicEventManager eventManager = new BasicEventManager( this );
assertThrows( NullPointerException.class, ( ) -> eventManager.fireEvent( null ) ); assertThrows( NullPointerException.class, ( ) -> eventManager.fireEvent( null ) );
Event event = new Event( EventType.ROOT, other ); Event event = new Event( EventType.ROOT, other );
assertEquals( other, event.getSource( ) ); assertEquals( other, event.getSource( ) );

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>archiva-base</artifactId>
<groupId>org.apache.archiva</groupId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-central</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,42 @@
package org.apache.archiva.event.central;
/*
* 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.
*/
import org.apache.archiva.event.AbstractEventManager;
import org.apache.archiva.event.Event;
import org.apache.archiva.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
* Event manager that collects all events from archiva subsystems.
*
* @author Martin Schreier <martin_s@apache.org>
*/
@Service("eventManager#archiva")
public class CentralEventManager extends AbstractEventManager implements EventHandler<Event>
{
private static final Logger log = LoggerFactory.getLogger( CentralEventManager.class );
@Override
public void handle( Event event )
{
log.info( "Event: type={}, sourceClass={}, source={}", event.getType( ), event.getSource().getClass(), event.getSource() );
}
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<!--
~ 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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
default-lazy-init="true">
<context:annotation-config />
<context:component-scan base-package="org.apache.archiva.event.central"/>
</beans>

View File

@ -33,6 +33,10 @@
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.apache.archiva.configuration</groupId> <groupId>org.apache.archiva.configuration</groupId>
<artifactId>archiva-configuration-provider</artifactId> <artifactId>archiva-configuration-provider</artifactId>

View File

@ -34,6 +34,10 @@
<dependencies> <dependencies>
<dependency>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.apache.archiva</groupId> <groupId>org.apache.archiva</groupId>
<artifactId>archiva-common</artifactId> <artifactId>archiva-common</artifactId>

View File

@ -277,11 +277,11 @@ public interface RepositoryHandler<R extends Repository, C extends AbstractRepos
* Returns the repository variant, this handler manages. * Returns the repository variant, this handler manages.
* @return the concrete variant class * @return the concrete variant class
*/ */
Class<R> getVariant(); Class<R> getFlavour();
/** /**
* Returns the repository configuration variant, this handler manages. * Returns the repository configuration variant, this handler manages.
* @return the concrete configuration variant class * @return the concrete configuration variant class
*/ */
Class<C> getConfigurationVariant(); Class<C> getConfigurationFlavour();
} }

View File

@ -20,8 +20,12 @@ package org.apache.archiva.repository.event;
*/ */
import org.apache.archiva.event.Event; import org.apache.archiva.event.Event;
import org.apache.archiva.event.EventContextBuilder;
import org.apache.archiva.event.EventType; import org.apache.archiva.event.EventType;
import org.apache.archiva.repository.ManagedRepository;
import org.apache.archiva.repository.RemoteRepository;
import org.apache.archiva.repository.Repository; import org.apache.archiva.repository.Repository;
import org.apache.archiva.repository.RepositoryGroup;
/** /**
* A repository event is specific to a repository and holds a reference to the repository that * A repository event is specific to a repository and holds a reference to the repository that
@ -39,6 +43,12 @@ public class RepositoryEvent extends Event
public RepositoryEvent(EventType<? extends RepositoryEvent> type, Object origin, Repository repository) { public RepositoryEvent(EventType<? extends RepositoryEvent> type, Object origin, Repository repository) {
super(type, origin); super(type, origin);
this.repository = repository; this.repository = repository;
EventContextBuilder builder = EventContextBuilder.withEvent( this );
if (repository!=null)
{
builder.withRepository( repository.getId( ), repository.getType( ).name( ), getFlavour( repository ) );
}
builder.apply( );
} }
public Repository getRepository() { public Repository getRepository() {
@ -49,4 +59,16 @@ public class RepositoryEvent extends Event
public EventType<? extends RepositoryEvent> getType() { public EventType<? extends RepositoryEvent> getType() {
return (EventType<? extends RepositoryEvent>) super.getType(); return (EventType<? extends RepositoryEvent>) super.getType();
} }
private String getFlavour(Repository repository) {
if (repository instanceof RemoteRepository ) {
return RemoteRepository.class.getName( );
} else if (repository instanceof ManagedRepository ) {
return ManagedRepository.class.getName( );
} else if ( repository instanceof RepositoryGroup ) {
return RepositoryGroup.class.getName( );
} else {
return "UNKNOWN";
}
}
} }

View File

@ -69,6 +69,10 @@
<groupId>org.apache.archiva</groupId> <groupId>org.apache.archiva</groupId>
<artifactId>archiva-common</artifactId> <artifactId>archiva-common</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-central</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>

View File

@ -24,7 +24,7 @@ import com.cronutils.model.definition.CronDefinition;
import com.cronutils.model.definition.CronDefinitionBuilder; import com.cronutils.model.definition.CronDefinitionBuilder;
import org.apache.archiva.event.Event; import org.apache.archiva.event.Event;
import org.apache.archiva.event.EventHandler; import org.apache.archiva.event.EventHandler;
import org.apache.archiva.event.EventManager; import org.apache.archiva.event.BasicEventManager;
import org.apache.archiva.event.EventType; import org.apache.archiva.event.EventType;
import org.apache.archiva.indexer.ArchivaIndexingContext; import org.apache.archiva.indexer.ArchivaIndexingContext;
import org.apache.archiva.repository.EditableRepository; import org.apache.archiva.repository.EditableRepository;
@ -86,7 +86,7 @@ public abstract class AbstractRepository implements EditableRepository, EventHan
public static final CronDefinition CRON_DEFINITION = CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ); public static final CronDefinition CRON_DEFINITION = CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ);
private RepositoryState state; private RepositoryState state;
private final EventManager eventManager; private final BasicEventManager eventManager;
Map<Class<? extends RepositoryFeature<?>>, RepositoryFeature<?>> featureMap = new HashMap<>( ); Map<Class<? extends RepositoryFeature<?>>, RepositoryFeature<?>> featureMap = new HashMap<>( );
@ -100,7 +100,7 @@ public abstract class AbstractRepository implements EditableRepository, EventHan
this.storage = repositoryStorage; this.storage = repositoryStorage;
this.location = repositoryStorage.getLocation(); this.location = repositoryStorage.getLocation();
this.openStatus.compareAndSet(false, true); this.openStatus.compareAndSet(false, true);
this.eventManager = new EventManager(this); this.eventManager = new BasicEventManager(this);
} }
public AbstractRepository(Locale primaryLocale, RepositoryType type, String id, String name, RepositoryStorage repositoryStorage) { public AbstractRepository(Locale primaryLocale, RepositoryType type, String id, String name, RepositoryStorage repositoryStorage) {
@ -111,7 +111,7 @@ public abstract class AbstractRepository implements EditableRepository, EventHan
this.storage = repositoryStorage; this.storage = repositoryStorage;
this.location = repositoryStorage.getLocation(); this.location = repositoryStorage.getLocation();
this.openStatus.compareAndSet(false, true); this.openStatus.compareAndSet(false, true);
this.eventManager = new EventManager(this); this.eventManager = new BasicEventManager(this);
} }
protected void setPrimaryLocale(Locale locale) { protected void setPrimaryLocale(Locale locale) {

View File

@ -22,7 +22,7 @@ import org.apache.archiva.configuration.model.AbstractRepositoryConfiguration;
import org.apache.archiva.configuration.model.Configuration; import org.apache.archiva.configuration.model.Configuration;
import org.apache.archiva.configuration.provider.IndeterminateConfigurationException; import org.apache.archiva.configuration.provider.IndeterminateConfigurationException;
import org.apache.archiva.event.Event; import org.apache.archiva.event.Event;
import org.apache.archiva.event.EventManager; import org.apache.archiva.event.BasicEventManager;
import org.apache.archiva.event.EventType; import org.apache.archiva.event.EventType;
import org.apache.archiva.repository.EditableRepository; import org.apache.archiva.repository.EditableRepository;
import org.apache.archiva.repository.Repository; import org.apache.archiva.repository.Repository;
@ -61,14 +61,14 @@ public abstract class AbstractRepositoryHandler<R extends Repository, C extends
private CombinedValidator<R> combinedValidator; private CombinedValidator<R> combinedValidator;
private final Class<R> repositoryClazz; private final Class<R> repositoryClazz;
private final Class<C> configurationClazz; private final Class<C> configurationClazz;
private final EventManager eventManager; private final BasicEventManager eventManager;
private final Map<String, R> repositoryMap = new HashMap<>( ); private final Map<String, R> repositoryMap = new HashMap<>( );
private final ConfigurationHandler configurationHandler; private final ConfigurationHandler configurationHandler;
public AbstractRepositoryHandler(Class<R> repositoryClazz, Class<C> configurationClazz, ConfigurationHandler configurationHandler) { public AbstractRepositoryHandler(Class<R> repositoryClazz, Class<C> configurationClazz, ConfigurationHandler configurationHandler) {
this.repositoryClazz = repositoryClazz; this.repositoryClazz = repositoryClazz;
this.configurationClazz = configurationClazz; this.configurationClazz = configurationClazz;
this.eventManager = new EventManager( this ); this.eventManager = new BasicEventManager( this );
this.configurationHandler = configurationHandler; this.configurationHandler = configurationHandler;
} }
@ -142,13 +142,13 @@ public abstract class AbstractRepositoryHandler<R extends Repository, C extends
} }
@Override @Override
public Class<R> getVariant( ) public Class<R> getFlavour( )
{ {
return this.repositoryClazz; return this.repositoryClazz;
} }
@Override @Override
public Class<C> getConfigurationVariant( ) public Class<C> getConfigurationFlavour( )
{ {
return this.configurationClazz; return this.configurationClazz;
} }

View File

@ -31,8 +31,10 @@ import org.apache.archiva.configuration.model.RemoteRepositoryConfiguration;
import org.apache.archiva.configuration.model.RepositoryGroupConfiguration; import org.apache.archiva.configuration.model.RepositoryGroupConfiguration;
import org.apache.archiva.event.Event; import org.apache.archiva.event.Event;
import org.apache.archiva.event.EventHandler; import org.apache.archiva.event.EventHandler;
import org.apache.archiva.event.EventManager; import org.apache.archiva.event.BasicEventManager;
import org.apache.archiva.event.EventSource;
import org.apache.archiva.event.EventType; import org.apache.archiva.event.EventType;
import org.apache.archiva.event.central.CentralEventManager;
import org.apache.archiva.indexer.ArchivaIndexManager; import org.apache.archiva.indexer.ArchivaIndexManager;
import org.apache.archiva.indexer.ArchivaIndexingContext; import org.apache.archiva.indexer.ArchivaIndexingContext;
import org.apache.archiva.indexer.IndexCreationFailedException; import org.apache.archiva.indexer.IndexCreationFailedException;
@ -65,6 +67,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -111,9 +114,13 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
@Inject @Inject
List<RepositoryValidator<? extends Repository>> repositoryValidatorList; List<RepositoryValidator<? extends Repository>> repositoryValidatorList;
@Inject
@Named("eventManager#archiva")
CentralEventManager centralEventManager;
private boolean ignoreIndexing = false; private boolean ignoreIndexing = false;
private final EventManager eventManager; private final BasicEventManager eventManager;
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock( ); private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock( );
@ -133,7 +140,7 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
public ArchivaRepositoryRegistry( ConfigurationHandler configurationHandler, List<RepositoryValidator<? extends Repository>> validatorList ) public ArchivaRepositoryRegistry( ConfigurationHandler configurationHandler, List<RepositoryValidator<? extends Repository>> validatorList )
{ {
this.eventManager = new EventManager( this ); this.eventManager = new BasicEventManager( this );
this.configurationHandler = configurationHandler; this.configurationHandler = configurationHandler;
this.validators = initValidatorList( validatorList ); this.validators = initValidatorList( validatorList );
} }
@ -172,6 +179,7 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
provider.addRepositoryEventHandler( this ); provider.addRepositoryEventHandler( this );
} }
this.configurationHandler.addListener( this ); this.configurationHandler.addListener( this );
registerEventHandler( EventType.ROOT, centralEventManager );
} }
finally finally
{ {
@ -1162,15 +1170,15 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
@Override @Override
public void registerHandler( RepositoryHandler<?, ?> handler ) public void registerHandler( RepositoryHandler<?, ?> handler )
{ {
if ( handler.getVariant( ).isAssignableFrom( RepositoryGroup.class ) ) if ( handler.getFlavour( ).isAssignableFrom( RepositoryGroup.class ) )
{ {
registerGroupHandler( (RepositoryHandler<RepositoryGroup, RepositoryGroupConfiguration>) handler ); registerGroupHandler( (RepositoryHandler<RepositoryGroup, RepositoryGroupConfiguration>) handler );
} }
else if ( handler.getVariant( ).isAssignableFrom( ManagedRepository.class ) ) else if ( handler.getFlavour( ).isAssignableFrom( ManagedRepository.class ) )
{ {
registerManagedRepositoryHandler( (RepositoryHandler<ManagedRepository, ManagedRepositoryConfiguration>) handler ); registerManagedRepositoryHandler( (RepositoryHandler<ManagedRepository, ManagedRepositoryConfiguration>) handler );
} }
else if ( handler.getVariant().isAssignableFrom( RemoteRepository.class )) { else if ( handler.getFlavour().isAssignableFrom( RemoteRepository.class )) {
registerRemoteRepositoryHandler( (RepositoryHandler<RemoteRepository, RemoteRepositoryConfiguration>) handler ); registerRemoteRepositoryHandler( (RepositoryHandler<RemoteRepository, RemoteRepositoryConfiguration>) handler );
} }
} }

View File

@ -47,6 +47,8 @@ import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.inject.Named; import javax.inject.Named;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;

View File

@ -148,7 +148,7 @@ class RepositoryGroupHandlerTest
private RepositoryGroupHandler createHandler( ) private RepositoryGroupHandler createHandler( )
{ {
Mockito.when( managedRepositoryHandler.getVariant( ) ).thenReturn( ManagedRepository.class ); Mockito.when( managedRepositoryHandler.getFlavour( ) ).thenReturn( ManagedRepository.class );
final ManagedRepository internalRepo; final ManagedRepository internalRepo;
try try
{ {
@ -161,7 +161,7 @@ class RepositoryGroupHandlerTest
Mockito.when( managedRepositoryHandler.get( ArgumentMatchers.eq("internal") ) ).thenReturn( internalRepo ); Mockito.when( managedRepositoryHandler.get( ArgumentMatchers.eq("internal") ) ).thenReturn( internalRepo );
repositoryRegistry.registerHandler( managedRepositoryHandler ); repositoryRegistry.registerHandler( managedRepositoryHandler );
Mockito.when( remoteRepositoryHandler.getVariant( ) ).thenReturn( RemoteRepository.class ); Mockito.when( remoteRepositoryHandler.getFlavour( ) ).thenReturn( RemoteRepository.class );
final RemoteRepository centralRepo; final RemoteRepository centralRepo;
try try
{ {

View File

@ -33,6 +33,7 @@
<site.staging.base>${project.parent.basedir}</site.staging.base> <site.staging.base>${project.parent.basedir}</site.staging.base>
</properties> </properties>
<modules> <modules>
<module>archiva-event-api</module>
<module>archiva-test-utils</module> <module>archiva-test-utils</module>
<module>archiva-common</module> <module>archiva-common</module>
<module>archiva-mock</module> <module>archiva-mock</module>
@ -53,5 +54,6 @@
<module>archiva-security-common</module> <module>archiva-security-common</module>
<module>archiva-storage-api</module> <module>archiva-storage-api</module>
<module>archiva-storage-fs</module> <module>archiva-storage-fs</module>
<module>archiva-event-central</module>
</modules> </modules>
</project> </project>

View File

@ -32,6 +32,10 @@
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.apache.archiva</groupId> <groupId>org.apache.archiva</groupId>
<artifactId>archiva-repository-api</artifactId> <artifactId>archiva-repository-api</artifactId>

View File

@ -118,6 +118,16 @@ public class MavenRepositoryProvider implements RepositoryProvider {
return repo; return repo;
} }
private Path getBaseDir(String location) {
String lPathStr = location == null ? "" : location;
Path lPath = Paths.get( lPathStr );
if (lPath.isAbsolute()) {
return lPath.getParent( );
} else {
return archivaConfiguration.getRepositoryBaseDir( ).resolve( lPath );
}
}
@Override @Override
public MavenRemoteRepository createRemoteInstance(String id, String name) { public MavenRemoteRepository createRemoteInstance(String id, String name) {
return createRemoteInstance(id, name, archivaConfiguration.getRemoteRepositoryBaseDir()); return createRemoteInstance(id, name, archivaConfiguration.getRemoteRepositoryBaseDir());
@ -194,7 +204,7 @@ public class MavenRepositoryProvider implements RepositoryProvider {
@Override @Override
public ManagedRepository createManagedInstance(ManagedRepositoryConfiguration cfg) throws RepositoryException { public ManagedRepository createManagedInstance(ManagedRepositoryConfiguration cfg) throws RepositoryException {
MavenManagedRepository repo = createManagedInstance(cfg.getId(), cfg.getName(), Paths.get(cfg.getLocation()).getParent()); MavenManagedRepository repo = createManagedInstance( cfg.getId( ), cfg.getName( ), getBaseDir( cfg.getLocation( ) ) );
updateManagedInstance(repo, cfg); updateManagedInstance(repo, cfg);
return repo; return repo;
} }

View File

@ -73,37 +73,6 @@ public class MavenManagedRepository extends Repository
super.setType( RepositoryType.MAVEN.name( ) ); super.setType( RepositoryType.MAVEN.name( ) );
} }
protected static void update(MavenManagedRepository repo, ManagedRepository beanRepo) {
repo.setDescription( beanRepo.getDescription() );
repo.setId( beanRepo.getId() );
repo.setIndex( true );
repo.setLayout( beanRepo.getLayout() );
repo.setBlocksRedeployments( beanRepo.blocksRedeployments() );
repo.setReleaseSchemes( beanRepo.getActiveReleaseSchemes().stream().map( Objects::toString).collect( Collectors.toList()) );
repo.setLocation( beanRepo.getLocation().toString() );
repo.setName( beanRepo.getName());
repo.setScanned( beanRepo.isScanned() );
repo.setSchedulingDefinition( beanRepo.getSchedulingDefinition() );
ArtifactCleanupFeature artifactCleanupFeature = beanRepo.getFeature( ArtifactCleanupFeature.class );
repo.setDeleteSnapshotsOfRelease( artifactCleanupFeature.isDeleteReleasedSnapshots());
repo.setRetentionCount( artifactCleanupFeature.getRetentionCount());
repo.setRetentionPeriod( artifactCleanupFeature.getRetentionPeriod() );
IndexCreationFeature icf = beanRepo.getFeature( IndexCreationFeature.class );
repo.setIndex( icf.hasIndex( ) );
repo.setIndexPath( icf.getIndexPath( ).getPath( ) );
repo.setPackedIndexPath( icf.getPackedIndexPath( ).getPath( ) );
repo.setSkipPackedIndexCreation( icf.isSkipPackedIndexCreation() );
StagingRepositoryFeature srf = beanRepo.getFeature( StagingRepositoryFeature.class );
repo.setHasStagingRepository( srf.isStageRepoNeeded( ) );
repo.setStagingRepository( srf.getStagingRepository()!=null?srf.getStagingRepository().getId():"" );
}
public static MavenManagedRepository of( ManagedRepository beanRepo ) {
MavenManagedRepository repo = new MavenManagedRepository( );
update( repo, beanRepo );
return repo;
}
@Schema(name="blocks_redeployments",description = "True, if redeployments to this repository are not allowed") @Schema(name="blocks_redeployments",description = "True, if redeployments to this repository are not allowed")
public boolean isBlocksRedeployments( ) public boolean isBlocksRedeployments( )
{ {

View File

@ -17,6 +17,7 @@ package org.apache.archiva.rest.api.v2.model;
* under the License. * under the License.
*/ */
import io.swagger.v3.oas.annotations.media.Schema;
import org.apache.archiva.repository.ManagedRepository; import org.apache.archiva.repository.ManagedRepository;
import java.io.Serializable; import java.io.Serializable;
@ -24,17 +25,13 @@ import java.io.Serializable;
/** /**
* @author Martin Stockhammer <martin_s@apache.org> * @author Martin Stockhammer <martin_s@apache.org>
*/ */
@Schema(name="MavenManagedRepositoryUpdate",description = "Data object for updating maven managed repositories")
public class MavenManagedRepositoryUpdate extends MavenManagedRepository implements Serializable public class MavenManagedRepositoryUpdate extends MavenManagedRepository implements Serializable
{ {
private static final long serialVersionUID = -9181643343284109862L; private static final long serialVersionUID = -9181643343284109862L;
private boolean resetStats = false; private boolean resetStats = false;
public static MavenManagedRepositoryUpdate of( ManagedRepository repository ) { @Schema(name="reset_stats",description = "True, if statistics should be reset after update")
MavenManagedRepositoryUpdate repo = new MavenManagedRepositoryUpdate( );
update( repo, repository );
return repo;
}
public boolean isResetStats( ) public boolean isResetStats( )
{ {
return resetStats; return resetStats;

View File

@ -64,13 +64,21 @@ public class MavenRepositoryMapper extends RestServiceMapper<MavenManagedReposit
if (source.getLayout()!=null) if (source.getLayout()!=null)
target.setLayout( source.getLayout() ); target.setLayout( source.getLayout() );
if (source.getLocation()!=null) if (source.getLocation()!=null)
{
target.setLocation( source.getLocation( ) ); target.setLocation( source.getLocation( ) );
} else {
if (target.getLocation()==null) {
target.setLocation( "" );
}
}
if (source.getPackedIndexPath()!=null) if (source.getPackedIndexPath()!=null)
target.setPackedIndexDir( source.getPackedIndexPath() ); target.setPackedIndexDir( source.getPackedIndexPath() );
if (source.getSchedulingDefinition()!=null) if (source.getSchedulingDefinition()!=null)
target.setRefreshCronExpression( source.getSchedulingDefinition() ); target.setRefreshCronExpression( source.getSchedulingDefinition() );
target.setReleases( source.getReleaseSchemes( ).contains( ReleaseScheme.RELEASE.name() ) ); target.setReleases( source.getReleaseSchemes( ).contains( ReleaseScheme.RELEASE.name() ) );
target.setRetentionCount( source.getRetentionCount() ); target.setRetentionCount( source.getRetentionCount() );
if (source.getRetentionPeriod()!=null)
target.setRetentionPeriod( source.getRetentionPeriod().getDays() ); target.setRetentionPeriod( source.getRetentionPeriod().getDays() );
target.setScanned( source.isScanned() ); target.setScanned( source.isScanned() );
target.setSkipPackedIndexCreation( source.isSkipPackedIndexCreation() ); target.setSkipPackedIndexCreation( source.isSkipPackedIndexCreation() );

View File

@ -122,4 +122,10 @@ public interface ErrorKeys
* When the operation needs authentication, but not authenticated user was found in the request context. * When the operation needs authentication, but not authenticated user was found in the request context.
*/ */
String NOT_AUTHENTICATED = PREFIX + "user.not_authenticated"; String NOT_AUTHENTICATED = PREFIX + "user.not_authenticated";
/**
* Repository add action failed
*/
String REPOSITORY_ADD_FAILED = PREFIX + "add.failed";
} }

View File

@ -140,6 +140,51 @@ class MavenRepositoryMapperTest
assertFalse( result.isSkipPackedIndexCreation( ) ); assertFalse( result.isSkipPackedIndexCreation( ) );
} }
@Test
void updateWithNullValues( )
{
MavenRepositoryMapper mapper = new MavenRepositoryMapper( );
MavenManagedRepository repo = new MavenManagedRepository( );
ManagedRepositoryConfiguration result = new ManagedRepositoryConfiguration( );
repo.setId( "repo01" );
repo.setName( "Repo 01" );
repo.setDescription( "This is repo 01" );
repo.setLocation( null );
repo.setHasStagingRepository( true );
repo.setSchedulingDefinition( "0,1,2 * * * *" );
repo.setPackedIndexPath( null );
repo.setIndexPath( null );
repo.setIndex( true );
repo.setDeleteSnapshotsOfRelease( false );
repo.setBlocksRedeployments( false );
repo.setReleaseSchemes( Arrays.asList( ReleaseScheme.RELEASE.name(), ReleaseScheme.SNAPSHOT.name() ) );
repo.setCharacteristic( Repository.CHARACTERISTIC_MANAGED );
repo.setScanned( true );
repo.setRetentionPeriod( null );
repo.setRetentionCount( 15 );
repo.setSkipPackedIndexCreation( false );
repo.setStagingRepository( null );
mapper.update( repo, result );
assertNotNull( result );
assertEquals( "repo01", result.getId( ) );
assertEquals( "Repo 01", result.getName( ) );
assertEquals( "This is repo 01", result.getDescription( ) );
assertNotNull( result.getLocation( ) );
assertTrue( result.isStageRepoNeeded( ) );
assertEquals( "0,1,2 * * * *", result.getRefreshCronExpression( ) );
assertEquals( "", result.getIndexDir( ) );
assertEquals( "", result.getPackedIndexDir( ) );
assertFalse( result.isDeleteReleasedSnapshots( ) );
assertFalse( result.isBlockRedeployments( ) );
assertTrue( result.isSnapshots( ) );
assertTrue( result.isReleases( ) );
assertTrue( result.isScanned( ) );
assertEquals( 100, result.getRetentionPeriod( ) );
assertEquals( 15, result.getRetentionCount( ) );
assertFalse( result.isSkipPackedIndexCreation( ) );
}
@Test @Test
void reverseMap( ) throws IOException, URISyntaxException, UnsupportedURIException void reverseMap( ) throws IOException, URISyntaxException, UnsupportedURIException
{ {

View File

@ -48,6 +48,10 @@
<groupId>org.apache.archiva</groupId> <groupId>org.apache.archiva</groupId>
<artifactId>archiva-storage-api</artifactId> <artifactId>archiva-storage-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.apache.archiva</groupId> <groupId>org.apache.archiva</groupId>
<artifactId>archiva-repository-admin-api</artifactId> <artifactId>archiva-repository-admin-api</artifactId>

View File

@ -0,0 +1,51 @@
package org.apache.archiva.rest.v2.svc;
/*
* 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.
*/
import org.apache.archiva.admin.model.AuditInformation;
import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
import org.apache.archiva.redback.users.User;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;
/**
* @author Martin Schreier <martin_s@apache.org>
*/
public class AbstractService
{
@Context
private HttpServletRequest httpServletRequest;
protected AuditInformation getAuditInformation( )
{
RedbackRequestInformation redbackRequestInformation = RedbackAuthenticationThreadLocal.get( );
User user;
String remoteAddr;
if (redbackRequestInformation==null) {
user = null;
remoteAddr = httpServletRequest.getRemoteAddr( );
} else
{
user = redbackRequestInformation.getUser( );
remoteAddr = redbackRequestInformation.getRemoteAddr( );
}
return new AuditInformation( user, remoteAddr );
}
}

View File

@ -20,13 +20,12 @@ package org.apache.archiva.rest.v2.svc.maven;
import org.apache.archiva.admin.model.AuditInformation; import org.apache.archiva.admin.model.AuditInformation;
import org.apache.archiva.admin.model.RepositoryAdminException; import org.apache.archiva.admin.model.RepositoryAdminException;
import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin; import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
import org.apache.archiva.common.MultiModelMapper;
import org.apache.archiva.components.rest.model.PagedResult; import org.apache.archiva.components.rest.model.PagedResult;
import org.apache.archiva.components.rest.util.QueryHelper; import org.apache.archiva.components.rest.util.QueryHelper;
import org.apache.archiva.configuration.model.ManagedRepositoryConfiguration; import org.apache.archiva.configuration.model.ManagedRepositoryConfiguration;
import org.apache.archiva.redback.authentication.AuthenticationResult; import org.apache.archiva.redback.authentication.AuthenticationResult;
import org.apache.archiva.redback.authorization.AuthorizationException; import org.apache.archiva.redback.authorization.AuthorizationException;
import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
import org.apache.archiva.redback.system.DefaultSecuritySession; import org.apache.archiva.redback.system.DefaultSecuritySession;
import org.apache.archiva.redback.system.SecuritySession; import org.apache.archiva.redback.system.SecuritySession;
import org.apache.archiva.redback.system.SecuritySystem; import org.apache.archiva.redback.system.SecuritySystem;
@ -36,6 +35,7 @@ import org.apache.archiva.redback.users.UserNotFoundException;
import org.apache.archiva.repository.ManagedRepository; import org.apache.archiva.repository.ManagedRepository;
import org.apache.archiva.repository.ReleaseScheme; import org.apache.archiva.repository.ReleaseScheme;
import org.apache.archiva.repository.Repository; import org.apache.archiva.repository.Repository;
import org.apache.archiva.repository.RepositoryException;
import org.apache.archiva.repository.RepositoryRegistry; import org.apache.archiva.repository.RepositoryRegistry;
import org.apache.archiva.repository.RepositoryType; import org.apache.archiva.repository.RepositoryType;
import org.apache.archiva.repository.content.ContentItem; import org.apache.archiva.repository.content.ContentItem;
@ -44,10 +44,12 @@ import org.apache.archiva.repository.storage.fs.FsStorageUtil;
import org.apache.archiva.rest.api.v2.model.FileInfo; import org.apache.archiva.rest.api.v2.model.FileInfo;
import org.apache.archiva.rest.api.v2.model.MavenManagedRepository; import org.apache.archiva.rest.api.v2.model.MavenManagedRepository;
import org.apache.archiva.rest.api.v2.model.MavenManagedRepositoryUpdate; import org.apache.archiva.rest.api.v2.model.MavenManagedRepositoryUpdate;
import org.apache.archiva.rest.api.v2.model.map.ServiceMapperFactory;
import org.apache.archiva.rest.api.v2.svc.ArchivaRestServiceException; import org.apache.archiva.rest.api.v2.svc.ArchivaRestServiceException;
import org.apache.archiva.rest.api.v2.svc.ErrorKeys; import org.apache.archiva.rest.api.v2.svc.ErrorKeys;
import org.apache.archiva.rest.api.v2.svc.ErrorMessage; import org.apache.archiva.rest.api.v2.svc.ErrorMessage;
import org.apache.archiva.rest.api.v2.svc.maven.MavenManagedRepositoryService; import org.apache.archiva.rest.api.v2.svc.maven.MavenManagedRepositoryService;
import org.apache.archiva.rest.v2.svc.AbstractService;
import org.apache.archiva.security.common.ArchivaRoleConstants; import org.apache.archiva.security.common.ArchivaRoleConstants;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -65,14 +67,14 @@ import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.archiva.security.common.ArchivaRoleConstants.OPERATION_READ_REPOSITORY;
import static org.apache.archiva.security.common.ArchivaRoleConstants.OPERATION_ADD_ARTIFACT; import static org.apache.archiva.security.common.ArchivaRoleConstants.OPERATION_ADD_ARTIFACT;
import static org.apache.archiva.security.common.ArchivaRoleConstants.OPERATION_READ_REPOSITORY;
/** /**
* @author Martin Stockhammer <martin_s@apache.org> * @author Martin Stockhammer <martin_s@apache.org>
*/ */
@Service("v2.managedMavenRepositoryService#rest") @Service("v2.managedMavenRepositoryService#rest")
public class DefaultMavenManagedRepositoryService implements MavenManagedRepositoryService public class DefaultMavenManagedRepositoryService extends AbstractService implements MavenManagedRepositoryService
{ {
@Context @Context
HttpServletResponse httpServletResponse; HttpServletResponse httpServletResponse;
@ -80,6 +82,8 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
@Context @Context
UriInfo uriInfo; UriInfo uriInfo;
private static final Logger log = LoggerFactory.getLogger( DefaultMavenManagedRepositoryService.class ); private static final Logger log = LoggerFactory.getLogger( DefaultMavenManagedRepositoryService.class );
private static final QueryHelper<ManagedRepository> QUERY_HELPER = new QueryHelper<>( new String[]{"id", "name"} ); private static final QueryHelper<ManagedRepository> QUERY_HELPER = new QueryHelper<>( new String[]{"id", "name"} );
static static
@ -96,36 +100,20 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
private final ManagedRepositoryAdmin managedRepositoryAdmin; private final ManagedRepositoryAdmin managedRepositoryAdmin;
private final RepositoryRegistry repositoryRegistry; private final RepositoryRegistry repositoryRegistry;
private final SecuritySystem securitySystem; private final SecuritySystem securitySystem;
private final ServiceMapperFactory serviceMapperFactory;
private final MultiModelMapper<MavenManagedRepository, ManagedRepositoryConfiguration, ManagedRepository> mapper;
public DefaultMavenManagedRepositoryService( SecuritySystem securitySystem, public DefaultMavenManagedRepositoryService( SecuritySystem securitySystem,
RepositoryRegistry repositoryRegistry, RepositoryRegistry repositoryRegistry,
ManagedRepositoryAdmin managedRepositoryAdmin ) ManagedRepositoryAdmin managedRepositoryAdmin,
ServiceMapperFactory serviceMapperFactory ) throws IllegalArgumentException
{ {
this.securitySystem = securitySystem; this.securitySystem = securitySystem;
this.repositoryRegistry = repositoryRegistry; this.repositoryRegistry = repositoryRegistry;
this.managedRepositoryAdmin = managedRepositoryAdmin; this.managedRepositoryAdmin = managedRepositoryAdmin;
} this.serviceMapperFactory = serviceMapperFactory;
this.mapper = serviceMapperFactory.getMapper( MavenManagedRepository.class, ManagedRepositoryConfiguration.class, ManagedRepository.class );
protected AuditInformation getAuditInformation( )
{
RedbackRequestInformation redbackRequestInformation = RedbackAuthenticationThreadLocal.get( );
User user;
String remoteAddr;
if (redbackRequestInformation==null) {
user = null;
remoteAddr = null;
} else
{
user = redbackRequestInformation.getUser( );
remoteAddr = redbackRequestInformation.getRemoteAddr( );
}
return new AuditInformation( user, remoteAddr );
}
public static ManagedRepositoryConfiguration toConfig(MavenManagedRepository repo) {
ManagedRepositoryConfiguration cfg = new ManagedRepositoryConfiguration( );
return cfg;
} }
@Override @Override
@ -140,7 +128,7 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
final Comparator<ManagedRepository> comparator = QUERY_HELPER.getComparator( orderBy, order ); final Comparator<ManagedRepository> comparator = QUERY_HELPER.getComparator( orderBy, order );
int totalCount = Math.toIntExact( repos.stream( ).filter( queryFilter ).count( ) ); int totalCount = Math.toIntExact( repos.stream( ).filter( queryFilter ).count( ) );
return PagedResult.of( totalCount, offset, limit, repos.stream( ).filter( queryFilter ).sorted( comparator ) return PagedResult.of( totalCount, offset, limit, repos.stream( ).filter( queryFilter ).sorted( comparator )
.map( MavenManagedRepository::of ).skip( offset ).limit( limit ).collect( Collectors.toList( ) ) ); .map( mapper::reverseMap ).skip( offset ).limit( limit ).collect( Collectors.toList( ) ) );
} }
catch (ArithmeticException e) { catch (ArithmeticException e) {
log.error( "Invalid number of repositories detected." ); log.error( "Invalid number of repositories detected." );
@ -158,7 +146,7 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
if (repo.getType()!=RepositoryType.MAVEN) { if (repo.getType()!=RepositoryType.MAVEN) {
throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_WRONG_TYPE, repositoryId, repo.getType().name() ), 404 ); throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_WRONG_TYPE, repositoryId, repo.getType().name() ), 404 );
} }
return MavenManagedRepository.of( repo ); return mapper.reverseMap( repo );
} }
@Override @Override
@ -220,13 +208,14 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
} }
try try
{ {
managedRepositoryAdmin.addManagedRepository( convert( managedRepository ), managedRepository.hasStagingRepository(), getAuditInformation() ); repositoryRegistry.putRepository( mapper.map( managedRepository ) );
httpServletResponse.setStatus( 201 ); httpServletResponse.setStatus( 201 );
return MavenManagedRepository.of( repositoryRegistry.getManagedRepository( repoId ) ); return mapper.reverseMap( repositoryRegistry.getManagedRepository( repoId ) );
} }
catch ( RepositoryAdminException e ) catch ( RepositoryException e )
{ {
throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ADMIN_ERROR, e.getMessage( ) ) ); log.error( "Could not create repository: {}", e.getMessage( ), e );
throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ADD_FAILED, repoId ) );
} }
} }
@ -241,7 +230,7 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
if (newRepo==null) { if (newRepo==null) {
throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_UPDATE_FAILED, repositoryId ) ); throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_UPDATE_FAILED, repositoryId ) );
} }
return MavenManagedRepository.of( newRepo ); return mapper.reverseMap( newRepo );
} }
catch ( RepositoryAdminException e ) catch ( RepositoryAdminException e )
{ {

View File

@ -100,7 +100,6 @@ public class NativeMavenManagedRepositoryServiceTest extends AbstractNativeRestS
} }
@Disabled
@Test @Test
@Order( 2 ) @Order( 2 )
void testCreateRepository() { void testCreateRepository() {
@ -111,7 +110,7 @@ public class NativeMavenManagedRepositoryServiceTest extends AbstractNativeRestS
assertNotNull( json ); assertNotNull( json );
assertEquals( "repo001", json.get( "id" ) ); assertEquals( "repo001", json.get( "id" ) );
assertEquals( "Repository 001", json.get( "name" ) ); assertEquals( "Repository 001", json.get( "name" ) );
assertEquals( "maven", json.get( "type" ) ); assertEquals( "MAVEN", json.get( "type" ) );
assertEquals( "This is repository 001", json.get( "description" ) ); assertEquals( "This is repository 001", json.get( "description" ) );
} }

10
pom.xml
View File

@ -236,6 +236,16 @@
</dependency> </dependency>
<dependency>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.archiva.event</groupId>
<artifactId>archiva-event-central</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.archiva.maven</groupId> <groupId>org.apache.archiva.maven</groupId>
<artifactId>archiva-maven-common</artifactId> <artifactId>archiva-maven-common</artifactId>