/**
* Copyright (c) 2008-2011 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://www.sonatype.com/products/nexus/attributions.
*
* This program is free software: you can redistribute it and/or modify it only under the terms of the GNU Affero General
* Public License Version 3 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License Version 3
* for more details.
*
* You should have received a copy of the GNU Affero General Public License Version 3 along with this program. If not, see
* http://www.gnu.org/licenses.
*
* Sonatype Nexus (TM) Open Source Version is available from Sonatype, Inc. Sonatype and Sonatype Nexus are trademarks of
* Sonatype, Inc. Apache Maven is a trademark of the Apache Foundation. M2Eclipse is a trademark of the Eclipse Foundation.
* All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.feeds;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.ExceptionUtils;
import org.sonatype.nexus.artifact.NexusItemInfo;
import org.sonatype.nexus.timeline.NexusTimeline;
import org.sonatype.timeline.TimelineFilter;
import org.sonatype.timeline.TimelineRecord;
import org.sonatype.timeline.TimelineResult;
/**
* A feed recorder that uses DefaultNexus to record feeds.
*
* @author cstamas
*/
@Component( role = FeedRecorder.class )
public class DefaultFeedRecorder
implements FeedRecorder
{
public static final int DEFAULT_PAGE_SIZE = 40;
public static final String REPOSITORY = "r";
public static final String REPOSITORY_PATH = "path";
public static final String REMOTE_URL = "rurl";
public static final String CTX_PREFIX = "ctx.";
public static final String ATR_PREFIX = "atr.";
public static final String ACTION = "action";
public static final String MESSAGE = "message";
public static final String DATE = "date";
public static final String STACK_TRACE = "strace";
/**
* Event type: repository
*/
private static final String REPO_EVENT_TYPE = "REPO_EVENTS";
private static final Set<String> REPO_EVENT_TYPE_SET = new HashSet<String>( 1 );
{
REPO_EVENT_TYPE_SET.add( REPO_EVENT_TYPE );
}
/**
* Event type: system
*/
private static final String SYSTEM_EVENT_TYPE = "SYSTEM";
private static final Set<String> SYSTEM_EVENT_TYPE_SET = new HashSet<String>( 1 );
{
SYSTEM_EVENT_TYPE_SET.add( SYSTEM_EVENT_TYPE );
}
/**
* Event type: authc/authz
*/
private static final String AUTHC_AUTHZ_EVENT_TYPE = "AUTHC_AUTHZ";
private static final Set<String> AUTHC_AUTHZ_EVENT_TYPE_SET = new HashSet<String>( 1 );
{
AUTHC_AUTHZ_EVENT_TYPE_SET.add( AUTHC_AUTHZ_EVENT_TYPE );
}
/**
* Event type: error
*/
private static final String ERROR_WARNING_EVENT_TYPE = "ERROR_WARNING";
private static final Set<String> ERROR_WARNING_EVENT_TYPE_SET = new HashSet<String>( 1 );
{
ERROR_WARNING_EVENT_TYPE_SET.add( ERROR_WARNING_EVENT_TYPE );
}
/**
* The time format used in events.
*/
private static final String EVENT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSSZ";
@Requirement
private Logger logger;
/**
* The timeline for persistent events and feeds.
*/
@Requirement
private NexusTimeline nexusTimeline;
/**
* The Feed filter (will checks for user access )
*/
@Requirement
private FeedArtifactEventFilter feedArtifactEventFilter;
protected Logger getLogger()
{
return logger;
}
protected DateFormat getDateFormat()
{
return new SimpleDateFormat( EVENT_DATE_FORMAT );
}
protected Date getEventDate( final Map<String, String> map )
{
Date eventDate;
try
{
eventDate = getDateFormat().parse( map.get( DATE ) );
}
catch ( ParseException e )
{
getLogger().warn( "Could not format event date!", e );
eventDate = new Date();
}
return eventDate;
}
protected List<NexusArtifactEvent> getAisFromMaps( TimelineResult data )
{
List<NexusArtifactEvent> result = new ArrayList<NexusArtifactEvent>();
for ( TimelineRecord record : data )
{
Map<String, String> map = record.getData();
NexusItemInfo ai = new NexusItemInfo();
ai.setRepositoryId( map.get( REPOSITORY ) );
ai.setPath( map.get( REPOSITORY_PATH ) );
ai.setRemoteUrl( map.get( REMOTE_URL ) );
HashMap<String, String> ctx = new HashMap<String, String>();
HashMap<String, String> atr = new HashMap<String, String>();
for ( String key : map.keySet() )
{
if ( key.startsWith( CTX_PREFIX ) )
{
ctx.put( key.substring( 4 ), map.get( key ) );
}
else if ( key.startsWith( ATR_PREFIX ) )
{
atr.put( key.substring( 4 ), map.get( key ) );
}
}
NexusArtifactEvent nae =
new NexusArtifactEvent( getEventDate( map ), map.get( ACTION ), map.get( MESSAGE ), ai );
// NEXUS-4038: backward compatibility
// Before this fix, nae had NO attributes separately stored, but only ctx map existed with ctx + atr content
// overlayed
// After fix we have two separate maps. To handle well "old" timeline records, when we detect there is no
// atr map (atr map is empty which will never be after fix), we "emulate" and lift all the ctx map into atr
// map instead.
if ( atr.isEmpty() )
{
nae.addItemAttributes( ctx );
}
else
{
nae.addEventContext( ctx );
nae.addItemAttributes( atr );
}
result.add( nae );
}
return this.feedArtifactEventFilter.filterArtifactEventList( result );
}
protected List<SystemEvent> getSesFromMaps( TimelineResult data )
{
List<SystemEvent> result = new ArrayList<SystemEvent>();
for ( TimelineRecord record : data )
{
Map<String, String> map = record.getData();
HashMap<String, Object> ctx = new HashMap<String, Object>();
for ( String key : map.keySet() )
{
if ( key.startsWith( CTX_PREFIX ) )
{
ctx.put( key.substring( 4 ), map.get( key ) );
}
}
SystemEvent se = new SystemEvent( getEventDate( map ), map.get( ACTION ), map.get( MESSAGE ) );
se.addEventContext( ctx );
result.add( se );
}
return result;
}
protected List<AuthcAuthzEvent> getAaesFromMaps( TimelineResult data )
{
List<AuthcAuthzEvent> result = new ArrayList<AuthcAuthzEvent>();
for ( TimelineRecord record : data )
{
Map<String, String> map = record.getData();
HashMap<String, Object> ctx = new HashMap<String, Object>();
for ( String key : map.keySet() )
{
if ( key.startsWith( CTX_PREFIX ) )
{
ctx.put( key.substring( 4 ), map.get( key ) );
}
}
AuthcAuthzEvent evt = new AuthcAuthzEvent( getEventDate( map ), map.get( ACTION ), map.get( MESSAGE ) );
evt.addEventContext( ctx );
result.add( evt );
}
return result;
}
protected List<ErrorWarningEvent> getEwesFromMaps( TimelineResult data )
{
List<ErrorWarningEvent> result = new ArrayList<ErrorWarningEvent>();
for ( TimelineRecord record : data )
{
Map<String, String> map = record.getData();
HashMap<String, Object> ctx = new HashMap<String, Object>();
for ( String key : map.keySet() )
{
if ( key.startsWith( CTX_PREFIX ) )
{
ctx.put( key.substring( 4 ), map.get( key ) );
}
}
ErrorWarningEvent evt =
new ErrorWarningEvent( getEventDate( map ), map.get( ACTION ), map.get( MESSAGE ),
map.get( STACK_TRACE ) );
evt.addEventContext( ctx );
result.add( evt );
}
return result;
}
// ==
protected void releaseResult( TimelineResult result )
{
if ( result != null )
{
result.release();
}
}
public TimelineResult getEvents( Set<String> types, Set<String> subtypes, Integer from, Integer count,
TimelineFilter filter )
{
int cnt = count != null ? count : DEFAULT_PAGE_SIZE;
if ( from != null )
{
return nexusTimeline.retrieve( from, cnt, types, subtypes, filter );
}
else
{
return nexusTimeline.retrieve( 0, cnt, types, subtypes, filter );
}
}
public List<NexusArtifactEvent> getNexusArtifectEvents( Set<String> subtypes, Integer from, Integer count,
TimelineFilter filter )
{
TimelineResult result = null;
try
{
result = getEvents( REPO_EVENT_TYPE_SET, subtypes, from, count, filter );
return getAisFromMaps( result );
}
finally
{
releaseResult( result );
}
}
public List<SystemEvent> getSystemEvents( Set<String> subtypes, Integer from, Integer count, TimelineFilter filter )
{
TimelineResult result = null;
try
{
result = getEvents( SYSTEM_EVENT_TYPE_SET, subtypes, from, count, filter );
return getSesFromMaps( result );
}
finally
{
releaseResult( result );
}
}
public List<AuthcAuthzEvent> getAuthcAuthzEvents( Set<String> subtypes, Integer from, Integer count,
TimelineFilter filter )
{
TimelineResult result = null;
try
{
result = getEvents( AUTHC_AUTHZ_EVENT_TYPE_SET, subtypes, from, count, filter );
return getAaesFromMaps( result );
}
finally
{
releaseResult( result );
}
}
public List<ErrorWarningEvent> getErrorWarningEvents( Set<String> subtypes, Integer from, Integer count,
TimelineFilter filter )
{
TimelineResult result = null;
try
{
result = getEvents( ERROR_WARNING_EVENT_TYPE_SET, subtypes, from, count, filter );
return getEwesFromMaps( result );
}
finally
{
releaseResult( result );
}
}
// ==
public void addSystemEvent( String action, String message )
{
SystemEvent event = new SystemEvent( new Date(), action, message );
addToTimeline( event );
}
private void putContext( final Map<String, String> map, final String prefix, final Map<String, ?> context )
{
for ( String key : context.keySet() )
{
Object value = context.get( key );
if ( value == null )
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "The attribute with key '" + key + "' in event context is NULL!" );
}
value = "";
}
map.put( prefix + key, value.toString() );
}
}
public void addAuthcAuthzEvent( AuthcAuthzEvent evt )
{
Map<String, String> map = new HashMap<String, String>();
putContext( map, CTX_PREFIX, evt.getEventContext() );
map.put( ACTION, evt.getAction() );
map.put( MESSAGE, evt.getMessage() );
map.put( DATE, getDateFormat().format( evt.getEventDate() ) );
addToTimeline( map, AUTHC_AUTHZ_EVENT_TYPE, evt.getAction() );
}
public void addNexusArtifactEvent( NexusArtifactEvent nae )
{
Map<String, String> map = new HashMap<String, String>();
map.put( REPOSITORY, nae.getNexusItemInfo().getRepositoryId() );
map.put( REPOSITORY_PATH, nae.getNexusItemInfo().getPath() );
if ( nae.getNexusItemInfo().getRemoteUrl() != null )
{
map.put( REMOTE_URL, nae.getNexusItemInfo().getRemoteUrl() );
}
putContext( map, CTX_PREFIX, nae.getEventContext() );
putContext( map, ATR_PREFIX, nae.getItemAttributes() );
if ( nae.getMessage() != null )
{
map.put( MESSAGE, nae.getMessage() );
}
map.put( DATE, getDateFormat().format( nae.getEventDate() ) );
map.put( ACTION, nae.getAction() );
addToTimeline( map, REPO_EVENT_TYPE, nae.getAction() );
}
public SystemProcess systemProcessStarted( String action, String message )
{
SystemProcess prc = new SystemProcess( new Date(), action, message, new Date() );
addToTimeline( prc );
getLogger().info( prc.getMessage() );
return prc;
}
public void systemProcessFinished( SystemProcess prc, String finishMessage )
{
prc.finished( finishMessage );
addToTimeline( prc );
getLogger().info( prc.getMessage() );
}
public void systemProcessBroken( SystemProcess prc, Throwable e )
{
prc.broken( e );
addToTimeline( prc );
getLogger().info( prc.getMessage(), e );
}
protected void addToTimeline( SystemEvent se )
{
Map<String, String> map = new HashMap<String, String>();
putContext( map, CTX_PREFIX, se.getEventContext() );
map.put( DATE, getDateFormat().format( se.getEventDate() ) );
map.put( ACTION, se.getAction() );
map.put( MESSAGE, se.getMessage() );
addToTimeline( map, SYSTEM_EVENT_TYPE, se.getAction() );
}
protected void addToTimeline( Map<String, String> map, String t1, String t2 )
{
nexusTimeline.add( System.currentTimeMillis(), t1, t2, map );
}
public void addErrorWarningEvent( String action, String message )
{
addErrorWarningEvent( new ErrorWarningEvent( new Date(), action, message, null ) );
}
public void addErrorWarningEvent( String action, String message, Throwable throwable )
{
String stackTrace = ExceptionUtils.getFullStackTrace( throwable );
addErrorWarningEvent( new ErrorWarningEvent( new Date(), action, message, stackTrace ) );
}
protected void addErrorWarningEvent( ErrorWarningEvent errorEvt )
{
Map<String, String> map = new HashMap<String, String>();
putContext( map, CTX_PREFIX, errorEvt.getEventContext() );
map.put( ACTION, errorEvt.getAction() );
map.put( DATE, getDateFormat().format( errorEvt.getEventDate() ) );
if ( errorEvt.getMessage() != null )
{
map.put( MESSAGE, errorEvt.getMessage() );
}
if ( errorEvt.getStackTrace() != null )
{
map.put( STACK_TRACE, errorEvt.getStackTrace() );
}
addToTimeline( map, ERROR_WARNING_EVENT_TYPE, errorEvt.getAction() );
}
}