/*
* Copyright (c) 2013 the original author or authors
*
* Licensed 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.
*/
package io.werval.runtime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import io.werval.api.Config;
import io.werval.api.Error;
import io.werval.api.Errors;
import io.werval.util.UUIDIdentityGenerator;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import static io.werval.runtime.ConfigKeys.APP_ERRORS_RECORD_MAX;
/**
* Application Errors Instance.
*/
public final class ErrorsInstance
implements Errors
{
private static final class ErrorInstance
implements Error
{
private final Long timestamp;
private final String errorId;
private final String requestId;
private final String message;
private final Throwable cause;
private ErrorInstance( long timestamp, String errorId, String requestId, String message, Throwable cause )
{
this.timestamp = timestamp;
this.errorId = errorId;
this.requestId = requestId;
this.message = message;
this.cause = cause;
}
@Override
public long timestamp()
{
return timestamp;
}
@Override
public String errorId()
{
return errorId;
}
@Override
public String requestId()
{
return requestId;
}
@Override
public String message()
{
return message;
}
@Override
public Throwable cause()
{
return cause;
}
@Override
public String toString()
{
return "Error( " + errorId + ", " + requestId + ", " + cause.getClass().getSimpleName() + " )";
}
@Override
public int hashCode()
{
int hash = 3;
hash = 79 * hash + Objects.hashCode( this.errorId );
return hash;
}
@Override
public boolean equals( Object obj )
{
if( this == obj )
{
return true;
}
if( obj == null )
{
return false;
}
if( getClass() != obj.getClass() )
{
return false;
}
final ErrorInstance other = (ErrorInstance) obj;
return Objects.equals( this.errorId, other.errorId );
}
}
private final Config config;
private final Map<String, Error> errors;
private final UUIDIdentityGenerator errorIdentityGenerator;
public ErrorsInstance( Config config )
{
this.config = config;
this.errors = new TreeMap<>( (o1, o2) -> o2.compareTo( o1 ) );
// Left pad incremented error count with zeroes
// Pad size is max recorded error length + 1
this.errorIdentityGenerator = new UUIDIdentityGenerator( config.string( APP_ERRORS_RECORD_MAX ).length() + 1 );
}
@Override
public Iterator<Error> iterator()
{
return errors.values().iterator();
}
@Override
public List<Error> asList()
{
return unmodifiableList( new ArrayList<>( errors.values() ) );
}
@Override
public Error record( String requestId, String message, Throwable cause )
{
String errorId = errorIdentityGenerator.newIdentity();
Error error = new ErrorInstance( System.currentTimeMillis(), errorId, requestId, message, cause );
errors.put( errorId, error );
while( errors.size() > config.intNumber( APP_ERRORS_RECORD_MAX ) )
{
List<String> keys = new ArrayList<>( errors.keySet() );
errors.remove( keys.get( keys.size() - 1 ) );
}
return error;
}
@Override
public int count()
{
return errors.size();
}
@Override
public synchronized void clear()
{
errors.clear();
errorIdentityGenerator.reset();
}
@Override
public Optional<Error> get( String errorId )
{
return Optional.ofNullable( errors.get( errorId ) );
}
@Override
public Optional<Error> last()
{
if( errors.isEmpty() )
{
return Optional.empty();
}
return Optional.of( errors.get( errors.keySet().iterator().next() ) );
}
@Override
public List<Error> ofRequest( String requestId )
{
if( errors.isEmpty() )
{
return emptyList();
}
return unmodifiableList(
errors.values().stream().filter( e -> requestId.equals( e.requestId() ) ).collect( toList() )
);
}
@Override
public Optional<Error> lastOfRequest( String requestIdentity )
{
if( errors.isEmpty() )
{
return Optional.empty();
}
List<Error> ofRequest = ofRequest( requestIdentity );
if( ofRequest.isEmpty() )
{
return Optional.empty();
}
return Optional.of( ofRequest.get( 0 ) );
}
}