removed dependency on classes from repeat package
This commit is contained in:
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for batch completion policies, to enable batch operations to
|
||||
* strategise normal completion conditions. Stateful implementations of batch
|
||||
* iterators should <em>only</em> update state using the update method. If you
|
||||
* need custom behaviour consider extending an existing implementation or using
|
||||
* the composite provided.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface CompletionPolicy {
|
||||
|
||||
/**
|
||||
* Determine whether a batch is complete given the latest result from the
|
||||
* callback. If this method returns true then
|
||||
* {@link #isComplete(RepeatContext)} should also (but not necessarily vice
|
||||
* versa, since the answer here depends on the result).
|
||||
*
|
||||
* @param context the current batch context.
|
||||
* @param result the result of the latest batch item processing.
|
||||
*
|
||||
* @return true if the batch should terminate.
|
||||
*
|
||||
* @see #isComplete(RepeatContext)
|
||||
*/
|
||||
boolean isComplete(RepeatContext context, RepeatStatus result);
|
||||
|
||||
/**
|
||||
* Allow policy to signal completion according to internal state, without
|
||||
* having to wait for the callback to complete.
|
||||
*
|
||||
* @param context the current batch context.
|
||||
*
|
||||
* @return true if the batch should terminate.
|
||||
*/
|
||||
boolean isComplete(RepeatContext context);
|
||||
|
||||
/**
|
||||
* Create a new context for the execution of a batch. N.B. implementations
|
||||
* should <em>not</em> return the parent from this method - they must
|
||||
* create a new context to meet the specific needs of the policy. The best
|
||||
* way to do this might be to override an existing implementation and use
|
||||
* the {@link RepeatContext} to store state in its attributes.
|
||||
*
|
||||
* @param parent the current context if one is already in progress.
|
||||
* @return a context object that can be used by the implementation to store
|
||||
* internal state for a batch.
|
||||
*/
|
||||
RepeatContext start(RepeatContext parent);
|
||||
|
||||
/**
|
||||
* Give implementations the opportunity to update the state of the current
|
||||
* batch. Will be called <em>once</em> per callback, after it has been
|
||||
* launched, but not necessarily after it completes (if the batch is
|
||||
* asynchronous).
|
||||
*
|
||||
* @param context the value returned by start.
|
||||
*/
|
||||
void update(RepeatContext context);
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat;
|
||||
|
||||
|
||||
/**
|
||||
* Callback interface for batch operations. Many simple processes will be able
|
||||
* to use off-the-shelf implementations of this interface, enabling the
|
||||
* application developer to concentrate on business logic.
|
||||
*
|
||||
* @see RepeatOperations
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface RepeatCallback {
|
||||
|
||||
/**
|
||||
* Implementations return true if they can continue processing - e.g. there
|
||||
* is a data source that is not yet exhausted. Exceptions are not necessarily
|
||||
* fatal - processing might continue depending on the Exception type and the
|
||||
* implementation of the caller.
|
||||
*
|
||||
* @param context the current context passed in by the caller.
|
||||
* @return an {@link RepeatStatus} which is continuable if there is (or may
|
||||
* be) more data to process.
|
||||
* @throws Exception if there is a problem with the processing.
|
||||
*/
|
||||
RepeatStatus doInIteration(RepeatContext context) throws Exception;
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat;
|
||||
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
|
||||
/**
|
||||
* Base interface for context which controls the state and completion /
|
||||
* termination of a batch step. A new context is created for each call to the
|
||||
* {@link RepeatOperations}. Within a batch callback code can communicate via
|
||||
* the {@link AttributeAccessor} interface.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @see RepeatOperations#iterate(RepeatCallback)
|
||||
*
|
||||
*/
|
||||
public interface RepeatContext extends AttributeAccessor {
|
||||
|
||||
/**
|
||||
* If batches are nested, then the inner batch will be created with the
|
||||
* outer one as a parent. This is an accessor for the parent if it exists.
|
||||
*
|
||||
* @return the parent context or null if there is none
|
||||
*/
|
||||
RepeatContext getParent();
|
||||
|
||||
/**
|
||||
* Public access to a counter for the number of operations attempted.
|
||||
*
|
||||
* @return the number of batch operations started.
|
||||
*/
|
||||
int getStartedCount();
|
||||
|
||||
/**
|
||||
* Signal to the framework that the current batch should complete normally,
|
||||
* independent of the current {@link CompletionPolicy}.
|
||||
*/
|
||||
void setCompleteOnly();
|
||||
|
||||
/**
|
||||
* Public accessor for the complete flag.
|
||||
*/
|
||||
boolean isCompleteOnly();
|
||||
|
||||
/**
|
||||
* Signal to the framework that the current batch should complete
|
||||
* abnormally, independent of the current {@link CompletionPolicy}.
|
||||
*/
|
||||
void setTerminateOnly();
|
||||
|
||||
/**
|
||||
* Public accessor for the termination flag. If this flag is set then the
|
||||
* complete flag will also be.
|
||||
*/
|
||||
boolean isTerminateOnly();
|
||||
|
||||
/**
|
||||
* Register a callback to be executed on close, associated with the
|
||||
* attribute having the given name. The {@link Runnable} callback should not
|
||||
* throw any exceptions.
|
||||
*
|
||||
* @param name the name of the attribute to associated this callback with.
|
||||
* If this attribute is removed the callback should never be called.
|
||||
* @param callback a {@link Runnable} to execute when the context is closed.
|
||||
*/
|
||||
void registerDestructionCallback(String name, Runnable callback);
|
||||
|
||||
/**
|
||||
* Allow resources to be cleared, especially in destruction callbacks.
|
||||
* Implementations should ensure that any registered destruction callbacks
|
||||
* are executed here, as long as the corresponding attribute is still
|
||||
* available.
|
||||
*/
|
||||
void close();
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
public class RepeatException extends NestedRuntimeException {
|
||||
|
||||
public RepeatException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public RepeatException(String msg, Throwable t) {
|
||||
super(msg, t);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for listeners to the batch process. Implementers can provide
|
||||
* enhance the behaviour of a batch in small cross-cutting modules. The
|
||||
* framework provides callbacks at key points in the processing.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface RepeatListener {
|
||||
/**
|
||||
* Called by the framework before each batch item. Implementers can halt a
|
||||
* batch by setting the complete flag on the context.
|
||||
*
|
||||
* @param context the current batch context.
|
||||
*/
|
||||
void before(RepeatContext context);
|
||||
|
||||
/**
|
||||
* Called by the framework after each item has been processed, unless the
|
||||
* item processing results in an exception. This method is called as soon as
|
||||
* the result is known.
|
||||
*
|
||||
* @param context the current batch context
|
||||
* @param result the result of the callback
|
||||
*/
|
||||
void after(RepeatContext context, RepeatStatus result);
|
||||
|
||||
/**
|
||||
* Called once at the start of a complete batch, before any items are
|
||||
* processed. Implementers can use this method to acquire any resources that
|
||||
* might be needed during processing. Implementers can halt the current
|
||||
* operation by setting the complete flag on the context. To halt all
|
||||
* enclosing batches (the whole job), the would need to use the parent
|
||||
* context (recursively).
|
||||
*
|
||||
* @param context the current batch context
|
||||
*/
|
||||
void open(RepeatContext context);
|
||||
|
||||
/**
|
||||
* Called when a repeat callback fails by throwing an exception. There will
|
||||
* be one call to this method for each exception thrown during a repeat
|
||||
* operation (e.g. a chunk).<br/>
|
||||
*
|
||||
* There is no need to re-throw the exception here - that will be done by
|
||||
* the enclosing framework.
|
||||
*
|
||||
* @param context the current batch context
|
||||
* @param e the error that was encountered in an item callback.
|
||||
*/
|
||||
void onError(RepeatContext context, Throwable e);
|
||||
|
||||
/**
|
||||
* Called once at the end of a complete batch, after normal or abnormal
|
||||
* completion (i.e. even after an exception). Implementers can use this
|
||||
* method to clean up any resources.
|
||||
*
|
||||
* @param context the current batch context.
|
||||
*/
|
||||
void close(RepeatContext context);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat;
|
||||
|
||||
|
||||
/**
|
||||
* The main interface providing access to batch operations. The batch client is
|
||||
* the {@link RepeatCallback}, where a single item or record is processed. The
|
||||
* batch behaviour, boundary conditions, transactions etc, are dealt with by the
|
||||
* {@link RepeatOperations} in such as way that the client does not need to know
|
||||
* about them. The client may have access to framework abstractions, like
|
||||
* template data sources, but these should work the same whether they are in a
|
||||
* batch or not.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface RepeatOperations {
|
||||
|
||||
/**
|
||||
* Execute the callback repeatedly, until a decision can be made to
|
||||
* complete. The decision about how many times to execute or when to
|
||||
* complete, and what to do in the case of an error is delegated to a
|
||||
* {@link CompletionPolicy}.
|
||||
*
|
||||
* @param callback the batch callback.
|
||||
* @return the aggregate of the result of all the callback operations. An
|
||||
* indication of whether the {@link RepeatOperations} can continue
|
||||
* processing if this method is called again.
|
||||
*/
|
||||
RepeatStatus iterate(RepeatCallback callback) throws RepeatException;
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat;
|
||||
|
||||
public enum RepeatStatus {
|
||||
|
||||
/**
|
||||
* Indicates that processing can continue.
|
||||
*/
|
||||
CONTINUABLE(true),
|
||||
/**
|
||||
* Indicates that processing is finished (either successful or unsuccessful)
|
||||
*/
|
||||
FINISHED(false);
|
||||
|
||||
private final boolean continuable;
|
||||
|
||||
private RepeatStatus(boolean continuable) {
|
||||
this.continuable = continuable;
|
||||
}
|
||||
|
||||
public static RepeatStatus continueIf(boolean continuable) {
|
||||
return continuable ? CONTINUABLE : FINISHED;
|
||||
}
|
||||
|
||||
public boolean isContinuable() {
|
||||
return this == CONTINUABLE;
|
||||
}
|
||||
|
||||
public RepeatStatus and(boolean value) {
|
||||
return value && continuable ? CONTINUABLE : FINISHED;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.callback;
|
||||
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatOperations;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
/**
|
||||
* Callback that delegates to another callback, via a {@link RepeatOperations}
|
||||
* instance. Useful when nesting or composing batches in one another, e.g. for
|
||||
* breaking a batch down into chunks.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class NestedRepeatCallback implements RepeatCallback {
|
||||
|
||||
private RepeatOperations template;
|
||||
|
||||
private RepeatCallback callback;
|
||||
|
||||
/**
|
||||
* Constructor setting mandatory fields.
|
||||
*
|
||||
* @param template the {@link RepeatOperations} to use when calling the
|
||||
* delegate callback
|
||||
* @param callback the {@link RepeatCallback} delegate
|
||||
*/
|
||||
public NestedRepeatCallback(RepeatOperations template, RepeatCallback callback) {
|
||||
super();
|
||||
this.template = template;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply calls template.execute(callback). Clients can use this to repeat a
|
||||
* batch process, or to break a process up into smaller chunks (e.g. to
|
||||
* change the transaction boundaries).
|
||||
*
|
||||
* @see org.springframework.repeat.RepeatCallback#doInIteration(RepeatContext)
|
||||
*/
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
return template.iterate(callback);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Infrastructure implementations of repeat callback concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.context;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Helper class for policies that need to count the number of occurrences of
|
||||
* some event (e.g. an exception type in the context) in the scope of a batch.
|
||||
* The value of the counter can be stored between batches in a nested context,
|
||||
* so that the termination decision is based on the aggregate of a number of
|
||||
* sibling batches.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RepeatContextCounter {
|
||||
|
||||
final private String countKey;
|
||||
|
||||
/**
|
||||
* Flag to indicate whether the count is stored at the level of the parent
|
||||
* context, or just local to the current context. Default value is false.
|
||||
*/
|
||||
final private boolean useParent;
|
||||
|
||||
final private RepeatContext context;
|
||||
|
||||
/**
|
||||
* Increment the counter.
|
||||
*
|
||||
* @param delta the amount by which to increment the counter.
|
||||
*/
|
||||
final public void increment(int delta) {
|
||||
AtomicInteger count = getCounter();
|
||||
count.addAndGet(delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment by 1.
|
||||
*/
|
||||
final public void increment() {
|
||||
increment(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor with useParent=false.
|
||||
* @param context the current context.
|
||||
* @param countKey the key to use to store the counter in the context.
|
||||
*/
|
||||
public RepeatContextCounter(RepeatContext context, String countKey) {
|
||||
this(context, countKey, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link RepeatContextCounter}.
|
||||
*
|
||||
* @param context the current context.
|
||||
* @param countKey the key to use to store the counter in the context.
|
||||
* @param useParent true if the counter is to be shared between siblings.
|
||||
* The state will be stored in the parent of the context (if it exists)
|
||||
* instead of the context itself.
|
||||
*/
|
||||
public RepeatContextCounter(RepeatContext context, String countKey, boolean useParent) {
|
||||
|
||||
super();
|
||||
|
||||
Assert.notNull(context, "The context must be provided to initialize a counter");
|
||||
|
||||
this.countKey = countKey;
|
||||
this.useParent = useParent;
|
||||
|
||||
RepeatContext parent = context.getParent();
|
||||
|
||||
if (this.useParent && parent != null) {
|
||||
this.context = parent;
|
||||
}
|
||||
else {
|
||||
this.context = context;
|
||||
}
|
||||
if (!this.context.hasAttribute(countKey)) {
|
||||
this.context.setAttribute(countKey, new AtomicInteger());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current value of the counter
|
||||
*/
|
||||
public int getCount() {
|
||||
return getCounter().intValue();
|
||||
}
|
||||
|
||||
private AtomicInteger getCounter() {
|
||||
return ((AtomicInteger) context.getAttribute(countKey));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
|
||||
public class RepeatContextSupport extends SynchronizedAttributeAccessor implements RepeatContext {
|
||||
|
||||
private RepeatContext parent;
|
||||
|
||||
private int count;
|
||||
|
||||
private volatile boolean completeOnly;
|
||||
|
||||
private volatile boolean terminateOnly;
|
||||
|
||||
private Map<String, Set<Runnable>> callbacks = new HashMap<String, Set<Runnable>>();
|
||||
|
||||
/**
|
||||
* Constructor for {@link RepeatContextSupport}. The parent can be null, but
|
||||
* should be set to the enclosing repeat context if there is one, e.g. if
|
||||
* this context is an inner loop.
|
||||
* @param parent
|
||||
*/
|
||||
public RepeatContextSupport(RepeatContext parent) {
|
||||
super();
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.RepeatContext#isCompleteOnly()
|
||||
*/
|
||||
public boolean isCompleteOnly() {
|
||||
return completeOnly;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.RepeatContext#setCompleteOnly()
|
||||
*/
|
||||
public void setCompleteOnly() {
|
||||
completeOnly = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.RepeatContext#isTerminateOnly()
|
||||
*/
|
||||
public boolean isTerminateOnly() {
|
||||
return terminateOnly;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.RepeatContext#setTerminateOnly()
|
||||
*/
|
||||
public void setTerminateOnly() {
|
||||
terminateOnly = true;
|
||||
setCompleteOnly();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.RepeatContext#getParent()
|
||||
*/
|
||||
public RepeatContext getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by clients to increment the started count.
|
||||
*/
|
||||
public synchronized void increment() {
|
||||
count++;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.RepeatContext#getStartedCount()
|
||||
*/
|
||||
public synchronized int getStartedCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.batch.repeat.RepeatContext#registerDestructionCallback
|
||||
* (java.lang.String, java.lang.Runnable)
|
||||
*/
|
||||
public void registerDestructionCallback(String name, Runnable callback) {
|
||||
synchronized (callbacks) {
|
||||
Set<Runnable> set = callbacks.get(name);
|
||||
if (set == null) {
|
||||
set = new HashSet<Runnable>();
|
||||
callbacks.put(name, set);
|
||||
}
|
||||
set.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.RepeatContext#close()
|
||||
*/
|
||||
public void close() {
|
||||
|
||||
List<RuntimeException> errors = new ArrayList<RuntimeException>();
|
||||
|
||||
Set<Map.Entry<String, Set<Runnable>>> copy;
|
||||
|
||||
synchronized (callbacks) {
|
||||
copy = new HashSet<Map.Entry<String, Set<Runnable>>>(callbacks.entrySet());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Set<Runnable>> entry : copy) {
|
||||
|
||||
for (Runnable callback : entry.getValue()) {
|
||||
/*
|
||||
* Potentially we could check here if there is an attribute with
|
||||
* the given name - if it has been removed, maybe the callback
|
||||
* is invalid. On the other hand it is less surprising for the
|
||||
* callback register if it is always executed.
|
||||
*/
|
||||
if (callback != null) {
|
||||
/*
|
||||
* The documentation of the interface says that these
|
||||
* callbacks must not throw exceptions, but we don't trust
|
||||
* them necessarily...
|
||||
*/
|
||||
try {
|
||||
callback.run();
|
||||
}
|
||||
catch (RuntimeException t) {
|
||||
errors.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw (RuntimeException) errors.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.context;
|
||||
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
import org.springframework.core.AttributeAccessorSupport;
|
||||
|
||||
/**
|
||||
* An {@link AttributeAccessor} that synchronizes on a mutex (not this) before
|
||||
* modifying or accessing the underlying attributes.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SynchronizedAttributeAccessor implements AttributeAccessor {
|
||||
|
||||
/**
|
||||
* All methods are delegated to this support object.
|
||||
*/
|
||||
AttributeAccessorSupport support = new AttributeAccessorSupport() {
|
||||
/**
|
||||
* Generated serial UID.
|
||||
*/
|
||||
private static final long serialVersionUID = -7664290016506582290L;
|
||||
};
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.AttributeAccessor#attributeNames()
|
||||
*/
|
||||
public String[] attributeNames() {
|
||||
synchronized (support) {
|
||||
return support.attributeNames();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
AttributeAccessorSupport that;
|
||||
if (other instanceof SynchronizedAttributeAccessor) {
|
||||
that = ((SynchronizedAttributeAccessor) other).support;
|
||||
}
|
||||
else if (other instanceof AttributeAccessorSupport) {
|
||||
that = (AttributeAccessorSupport) other;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
synchronized (support) {
|
||||
return support.equals(that);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.AttributeAccessor#getAttribute(java.lang.String)
|
||||
*/
|
||||
public Object getAttribute(String name) {
|
||||
synchronized (support) {
|
||||
return support.getAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.AttributeAccessor#hasAttribute(java.lang.String)
|
||||
*/
|
||||
public boolean hasAttribute(String name) {
|
||||
synchronized (support) {
|
||||
return support.hasAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
public int hashCode() {
|
||||
return support.hashCode();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.AttributeAccessor#removeAttribute(java.lang.String)
|
||||
*/
|
||||
public Object removeAttribute(String name) {
|
||||
synchronized (support) {
|
||||
return support.removeAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.AttributeAccessor#setAttribute(java.lang.String,
|
||||
* java.lang.Object)
|
||||
*/
|
||||
public void setAttribute(String name, Object value) {
|
||||
synchronized (support) {
|
||||
support.setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional support for atomic put if absent.
|
||||
* @param name the key for the attribute name
|
||||
* @param value the value of the attribute
|
||||
* @return null if the attribute was not already set, the existing value
|
||||
* otherwise.
|
||||
*/
|
||||
public Object setAttributeIfAbsent(String name, Object value) {
|
||||
synchronized (support) {
|
||||
Object old = getAttribute(name);
|
||||
if (old != null) {
|
||||
return old;
|
||||
}
|
||||
setAttribute(name, value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuffer buffer = new StringBuffer("SynchronizedAttributeAccessor: [");
|
||||
synchronized (support) {
|
||||
String[] names = attributeNames();
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
String name = names[i];
|
||||
buffer.append(names[i]).append("=").append(getAttribute(name));
|
||||
if (i < names.length - 1) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
}
|
||||
buffer.append("]");
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Infrastructure implementations of repeat context concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
|
||||
/**
|
||||
* Composiste {@link ExceptionHandler} that loops though a list of delegates.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class CompositeExceptionHandler implements ExceptionHandler {
|
||||
|
||||
private ExceptionHandler[] handlers = new ExceptionHandler[0];
|
||||
|
||||
public void setHandlers(ExceptionHandler[] handlers) {
|
||||
this.handlers = Arrays.asList(handlers).toArray(new ExceptionHandler[handlers.length]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over the handlers delegating the call to each in turn. The chain
|
||||
* ends if an exception is thrown.
|
||||
*
|
||||
* @see ExceptionHandler#handleException(RepeatContext, Throwable)
|
||||
*/
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
|
||||
for (int i = 0; i < handlers.length; i++) {
|
||||
ExceptionHandler handler = handlers[i];
|
||||
handler.handleException(context, throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ExceptionHandler} - just re-throws the exception it encounters.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultExceptionHandler implements ExceptionHandler {
|
||||
|
||||
/**
|
||||
* Re-throw the throwable.
|
||||
*
|
||||
* @see org.springframework.repeat.exception.ExceptionHandler#handleException(RepeatContext,
|
||||
* Throwable)
|
||||
*/
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import org.springframework.repeat.CompletionPolicy;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
|
||||
/**
|
||||
* Handler to allow strategies for re-throwing exceptions. Normally a
|
||||
* {@link CompletionPolicy} will be used to decide whether to end a batch when
|
||||
* there is no exception, and the {@link ExceptionHandler} is used to signal an
|
||||
* abnormal ending - an abnormal ending would result in an
|
||||
* {@link ExceptionHandler} throwing an exception. The caller will catch and
|
||||
* re-throw it if necessary.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Robert Kasanicky
|
||||
*
|
||||
*/
|
||||
public interface ExceptionHandler {
|
||||
|
||||
/**
|
||||
* Deal with a Throwable during a batch - decide whether it should be
|
||||
* re-thrown in the first place.
|
||||
*
|
||||
* @param context the current {@link RepeatContext}. Can be used to store
|
||||
* state (via attributes), for example to count the number of occurrences of
|
||||
* a particular exception type and implement a threshold policy.
|
||||
* @param throwable an exception.
|
||||
* @throws Throwable implementations are free to re-throw the exception
|
||||
*/
|
||||
void handleException(RepeatContext context, Throwable throwable) throws Throwable;
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.classify.Classifier;
|
||||
import org.springframework.classify.ClassifierSupport;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatException;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ExceptionHandler} based on an {@link Classifier}.
|
||||
* The classifier determines whether to log the exception or rethrow it. The
|
||||
* keys in the classifier must be the same as the static enum in this class.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class LogOrRethrowExceptionHandler implements ExceptionHandler {
|
||||
|
||||
/**
|
||||
* Logging levels for the handler.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public static enum Level {
|
||||
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be
|
||||
* rethrown. If the throwable is not a RuntimeException it is wrapped in
|
||||
* a {@link RepeatException}.
|
||||
*/
|
||||
RETHROW,
|
||||
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be
|
||||
* logged at debug level.
|
||||
*/
|
||||
DEBUG,
|
||||
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be
|
||||
* logged at warn level.
|
||||
*/
|
||||
WARN,
|
||||
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be
|
||||
* logged at error level.
|
||||
*/
|
||||
ERROR
|
||||
|
||||
}
|
||||
|
||||
protected final Log logger = LogFactory.getLog(LogOrRethrowExceptionHandler.class);
|
||||
|
||||
private Classifier<Throwable, Level> exceptionClassifier = new ClassifierSupport<Throwable, Level>(Level.RETHROW);
|
||||
|
||||
/**
|
||||
* Setter for the {@link Classifier} used by this handler. The default is to
|
||||
* map all throwable instances to {@link Level#RETHROW}.
|
||||
*
|
||||
* @param exceptionClassifier the ExceptionClassifier to use
|
||||
*/
|
||||
public void setExceptionClassifier(Classifier<Throwable, Level> exceptionClassifier) {
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify the throwables and decide whether to rethrow based on the
|
||||
* result. The context is not used.
|
||||
*
|
||||
* @throws Throwable
|
||||
*
|
||||
* @see ExceptionHandler#handleException(RepeatContext, Throwable)
|
||||
*/
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
|
||||
|
||||
Level key = exceptionClassifier.classify(throwable);
|
||||
if (Level.ERROR.equals(key)) {
|
||||
logger.error("Exception encountered in batch repeat.", throwable);
|
||||
}
|
||||
else if (Level.WARN.equals(key)) {
|
||||
logger.warn("Exception encountered in batch repeat.", throwable);
|
||||
}
|
||||
else if (Level.DEBUG.equals(key) && logger.isDebugEnabled()) {
|
||||
logger.debug("Exception encountered in batch repeat.", throwable);
|
||||
}
|
||||
else if (Level.RETHROW.equals(key)) {
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.classify.Classifier;
|
||||
import org.springframework.classify.SubclassClassifier;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.context.RepeatContextCounter;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ExceptionHandler} that rethrows when exceptions of a
|
||||
* given type reach a threshold. Requires an {@link Classifier} that maps
|
||||
* exception types to unique keys, and also a map from those keys to threshold
|
||||
* values (Integer type).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RethrowOnThresholdExceptionHandler implements ExceptionHandler {
|
||||
|
||||
protected static final IntegerHolder ZERO = new IntegerHolder(0);
|
||||
|
||||
protected final Log logger = LogFactory.getLog(RethrowOnThresholdExceptionHandler.class);
|
||||
|
||||
private Classifier<? super Throwable, IntegerHolder> exceptionClassifier = new Classifier<Throwable, IntegerHolder>() {
|
||||
public RethrowOnThresholdExceptionHandler.IntegerHolder classify(Throwable classifiable) {
|
||||
return ZERO;
|
||||
}
|
||||
};
|
||||
|
||||
private boolean useParent = false;
|
||||
|
||||
/**
|
||||
* Flag to indicate the the exception counters should be shared between
|
||||
* sibling contexts in a nested batch. Default is false.
|
||||
*
|
||||
* @param useParent true if the parent context should be used to store the
|
||||
* counters.
|
||||
*/
|
||||
public void setUseParent(boolean useParent) {
|
||||
this.useParent = useParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the exception handler. Creates a default exception handler and
|
||||
* threshold that maps all exceptions to a threshold of 0 - all exceptions
|
||||
* are rethrown by default.
|
||||
*/
|
||||
public RethrowOnThresholdExceptionHandler() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* A map from exception classes to a threshold value of type Integer.
|
||||
*
|
||||
* @param thresholds the threshold value map.
|
||||
*/
|
||||
public void setThresholds(Map<Class<? extends Throwable>, Integer> thresholds) {
|
||||
Map<Class<? extends Throwable>, IntegerHolder> typeMap = new HashMap<Class<? extends Throwable>, IntegerHolder>();
|
||||
for (Entry<Class<? extends Throwable>, Integer> entry : thresholds.entrySet()) {
|
||||
typeMap.put(entry.getKey(), new IntegerHolder(entry.getValue()));
|
||||
}
|
||||
exceptionClassifier = new SubclassClassifier<Throwable, IntegerHolder>(typeMap, ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify the throwables and decide whether to re-throw based on the
|
||||
* result. The context is used to accumulate the number of exceptions of the
|
||||
* same type according to the classifier.
|
||||
*
|
||||
* @throws Throwable
|
||||
* @see ExceptionHandler#handleException(RepeatContext, Throwable)
|
||||
*/
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
|
||||
|
||||
IntegerHolder key = exceptionClassifier.classify(throwable);
|
||||
|
||||
RepeatContextCounter counter = getCounter(context, key);
|
||||
counter.increment();
|
||||
int count = counter.getCount();
|
||||
int threshold = key.getValue();
|
||||
if (count > threshold) {
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private RepeatContextCounter getCounter(RepeatContext context, IntegerHolder key) {
|
||||
String attribute = RethrowOnThresholdExceptionHandler.class.getName() + "." + key;
|
||||
// Creates a new counter and stores it in the correct context:
|
||||
return new RepeatContextCounter(context, attribute, useParent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class IntegerHolder {
|
||||
|
||||
private final int value;
|
||||
|
||||
/**
|
||||
* @param value
|
||||
*/
|
||||
public IntegerHolder(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the value.
|
||||
* @return the value
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return ObjectUtils.getIdentityHexString(this) + "." + value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
|
||||
/**
|
||||
* Simple implementation of exception handler which looks for given exception
|
||||
* types. If one of the types is found then a counter is incremented and the
|
||||
* limit is checked to determine if it has been exceeded and the Throwable
|
||||
* should be re-thrown. Also allows to specify list of 'fatal' exceptions that
|
||||
* are never subject to counting, but are immediately re-thrown. The fatal list
|
||||
* has higher priority so the two lists needn't be exclusive.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class SimpleLimitExceptionHandler implements ExceptionHandler, InitializingBean {
|
||||
|
||||
private RethrowOnThresholdExceptionHandler delegate = new RethrowOnThresholdExceptionHandler();
|
||||
|
||||
private Collection<Class<? extends Throwable>> exceptionClasses = Collections
|
||||
.<Class<? extends Throwable>> singleton(Exception.class);
|
||||
|
||||
private Collection<Class<? extends Throwable>> fatalExceptionClasses = Collections
|
||||
.<Class<? extends Throwable>> singleton(Error.class);
|
||||
|
||||
private int limit = 0;
|
||||
|
||||
/**
|
||||
* Apply the provided properties to create a delegate handler.
|
||||
*
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (limit <= 0) {
|
||||
return;
|
||||
}
|
||||
Map<Class<? extends Throwable>, Integer> thresholds = new HashMap<Class<? extends Throwable>, Integer>();
|
||||
for (Class<? extends Throwable> type : exceptionClasses) {
|
||||
thresholds.put(type, limit);
|
||||
}
|
||||
// do the fatalExceptionClasses last so they override the others
|
||||
for (Class<? extends Throwable> type : fatalExceptionClasses) {
|
||||
thresholds.put(type, 0);
|
||||
}
|
||||
delegate.setThresholds(thresholds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to indicate the the exception counters should be shared between
|
||||
* sibling contexts in a nested batch (i.e. inner loop). Default is false.
|
||||
* Set this flag to true if you want to count exceptions for the whole
|
||||
* (outer) loop in a typical container.
|
||||
*
|
||||
* @param useParent true if the parent context should be used to store the
|
||||
* counters.
|
||||
*/
|
||||
public void setUseParent(boolean useParent) {
|
||||
delegate.setUseParent(useParent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor for the {@link SimpleLimitExceptionHandler} to
|
||||
* set the limit.
|
||||
*
|
||||
* @param limit the limit
|
||||
*/
|
||||
public SimpleLimitExceptionHandler(int limit) {
|
||||
this();
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor for the {@link SimpleLimitExceptionHandler}.
|
||||
*/
|
||||
public SimpleLimitExceptionHandler() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rethrows only if the limit is breached for this context on the exception
|
||||
* type specified.
|
||||
*
|
||||
* @see #setExceptionClasses(Collection)
|
||||
* @see #setLimit(int)
|
||||
*
|
||||
* @see org.springframework.repeat.exception.ExceptionHandler#handleException(org.springframework.repeat.RepeatContext,
|
||||
* Throwable)
|
||||
*/
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
|
||||
delegate.handleException(context, throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* The limit on the given exception type within a single context before it
|
||||
* is rethrown.
|
||||
*
|
||||
* @param limit the limit
|
||||
*/
|
||||
public void setLimit(final int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the exception classes that this handler counts. Defaults to
|
||||
* {@link Exception}. If more exceptionClasses are specified handler uses
|
||||
* single counter that is incremented when one of the recognized exception
|
||||
* exceptionClasses is handled.
|
||||
* @param classes exceptionClasses
|
||||
*/
|
||||
public void setExceptionClasses(Collection<Class<? extends Throwable>> classes) {
|
||||
this.exceptionClasses = classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the exception classes that shouldn't be counted, but rethrown
|
||||
* immediately. This list has higher priority than
|
||||
* {@link #setExceptionClasses(Collection)}.
|
||||
*
|
||||
* @param fatalExceptionClasses defaults to {@link Error}
|
||||
*/
|
||||
public void setFatalExceptionClasses(Collection<Class<? extends Throwable>> fatalExceptionClasses) {
|
||||
this.fatalExceptionClasses = fatalExceptionClasses;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Infrastructure implementations of repeat exception handler concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,203 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.interceptor;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.ProxyMethodInvocation;
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatException;
|
||||
import org.springframework.repeat.RepeatOperations;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.support.RepeatTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} that can be used to automatically repeat calls to
|
||||
* a method on a service. The injected {@link RepeatOperations} is used to
|
||||
* control the completion of the loop. Independent of the completion policy in
|
||||
* the {@link RepeatOperations} the loop will repeat until the target method
|
||||
* returns null or false. Be careful when injecting a bespoke
|
||||
* {@link RepeatOperations} that the loop will actually terminate, because the
|
||||
* default policy for a vanilla {@link RepeatTemplate} will never complete if
|
||||
* the return type of the target method is void (the value returned is always
|
||||
* not-null, representing the {@link Void#TYPE}).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class RepeatOperationsInterceptor implements MethodInterceptor {
|
||||
|
||||
private RepeatOperations repeatOperations = new RepeatTemplate();
|
||||
|
||||
/**
|
||||
* Setter for the {@link RepeatOperations}.
|
||||
*
|
||||
* @param batchTempate
|
||||
* @throws IllegalArgumentException if the argument is null.
|
||||
*/
|
||||
public void setRepeatOperations(RepeatOperations batchTempate) {
|
||||
Assert.notNull(batchTempate, "'repeatOperations' cannot be null.");
|
||||
this.repeatOperations = batchTempate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the proceeding method call repeatedly, according to the properties
|
||||
* of the injected {@link RepeatOperations}.
|
||||
*
|
||||
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
|
||||
*/
|
||||
public Object invoke(final MethodInvocation invocation) throws Throwable {
|
||||
|
||||
final ResultHolder result = new ResultHolder();
|
||||
// Cache void return value if intercepted method returns void
|
||||
final boolean voidReturnType = Void.TYPE.equals(invocation.getMethod().getReturnType());
|
||||
if (voidReturnType) {
|
||||
// This will be ignored anyway, but we want it to be non-null for
|
||||
// convenience of checking that there is a result.
|
||||
result.setValue(new Object());
|
||||
}
|
||||
|
||||
try {
|
||||
repeatOperations.iterate(new RepeatCallback() {
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
try {
|
||||
|
||||
MethodInvocation clone = invocation;
|
||||
if (invocation instanceof ProxyMethodInvocation) {
|
||||
clone = ((ProxyMethodInvocation) invocation).invocableClone();
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"MethodInvocation of the wrong type detected - this should not happen with Spring AOP, so please raise an issue if you see this exception");
|
||||
}
|
||||
|
||||
Object value = clone.proceed();
|
||||
if (voidReturnType) {
|
||||
return RepeatStatus.CONTINUABLE;
|
||||
}
|
||||
if (!isComplete(value)) {
|
||||
// Save the last result
|
||||
result.setValue(value);
|
||||
return RepeatStatus.CONTINUABLE;
|
||||
}
|
||||
else {
|
||||
result.setFinalValue(value);
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (e instanceof Exception) {
|
||||
throw (Exception) e;
|
||||
}
|
||||
else {
|
||||
throw new RepeatOperationsInterceptorException("Unexpected error in batch interceptor", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// The repeat exception should be unwrapped by the template
|
||||
throw t;
|
||||
}
|
||||
|
||||
if (result.isReady()) {
|
||||
return result.getValue();
|
||||
}
|
||||
|
||||
// No result means something weird happened
|
||||
throw new IllegalStateException("No result available for attempted repeat call to " + invocation
|
||||
+ ". The invocation was never called, so maybe there is a problem with the completion policy?");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param result
|
||||
* @return
|
||||
*/
|
||||
private boolean isComplete(Object result) {
|
||||
return result == null || (result instanceof Boolean) && !((Boolean) result).booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper exception class to enable nasty errors to be passed out of
|
||||
* the scope of the repeat operations and handled by the caller.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class RepeatOperationsInterceptorException extends RepeatException {
|
||||
/**
|
||||
* @param message
|
||||
* @param e
|
||||
*/
|
||||
public RepeatOperationsInterceptorException(String message, Throwable e) {
|
||||
super(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper object for the result from a method invocation.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class ResultHolder {
|
||||
private Object value = null;
|
||||
|
||||
private boolean ready = false;
|
||||
|
||||
/**
|
||||
* Public setter for the Object.
|
||||
* @param value the value to set
|
||||
*/
|
||||
public void setValue(Object value) {
|
||||
this.ready = true;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value
|
||||
*/
|
||||
public void setFinalValue(Object value) {
|
||||
if (ready) {
|
||||
// Only set the value the last time if the last time was also
|
||||
// the first time
|
||||
return;
|
||||
}
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the Object.
|
||||
* @return the value
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if a value has been set
|
||||
*/
|
||||
public boolean isReady() {
|
||||
return ready;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Infrastructure implementations of repeat aop concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatListener;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class CompositeRepeatListener implements RepeatListener {
|
||||
|
||||
private List<RepeatListener> listeners = new ArrayList<RepeatListener>();
|
||||
|
||||
/**
|
||||
* Public setter for the listeners.
|
||||
*
|
||||
* @param listeners
|
||||
*/
|
||||
public void setListeners(RepeatListener[] listeners) {
|
||||
this.listeners = Arrays.asList(listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register additional listener.
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public void register(RepeatListener listener) {
|
||||
if (!listeners.contains(listener)) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.RepeatListener#after(org.springframework.batch.repeat.RepeatContext, org.springframework.batch.repeat.ExitStatus)
|
||||
*/
|
||||
public void after(RepeatContext context, RepeatStatus result) {
|
||||
for (RepeatListener listener : listeners) {
|
||||
listener.after(context, result);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.RepeatListener#before(org.springframework.batch.repeat.RepeatContext)
|
||||
*/
|
||||
public void before(RepeatContext context) {
|
||||
for (RepeatListener listener : listeners) {
|
||||
listener.before(context);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.RepeatListener#close(org.springframework.batch.repeat.RepeatContext)
|
||||
*/
|
||||
public void close(RepeatContext context) {
|
||||
for (RepeatListener listener : listeners) {
|
||||
listener.close(context);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.RepeatListener#onError(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)
|
||||
*/
|
||||
public void onError(RepeatContext context, Throwable e) {
|
||||
for (RepeatListener listener : listeners) {
|
||||
listener.onError(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.RepeatListener#open(org.springframework.batch.repeat.RepeatContext)
|
||||
*/
|
||||
public void open(RepeatContext context) {
|
||||
for (RepeatListener listener : listeners) {
|
||||
listener.open(context);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.listener;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatListener;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
/**
|
||||
* Empty method implementation of {@link RepeatListener}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RepeatListenerSupport implements RepeatListener {
|
||||
|
||||
public void before(RepeatContext context) {
|
||||
}
|
||||
|
||||
public void after(RepeatContext context, RepeatStatus result) {
|
||||
}
|
||||
|
||||
public void close(RepeatContext context) {
|
||||
}
|
||||
|
||||
public void onError(RepeatContext context, Throwable e) {
|
||||
}
|
||||
|
||||
public void open(RepeatContext context) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Infrastructure implementations of repeat interceptor concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Infrastructure implementations of repeat concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import org.springframework.repeat.CompletionPolicy;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
|
||||
/**
|
||||
* Very simple base class for {@link CompletionPolicy} implementations.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class CompletionPolicySupport implements CompletionPolicy {
|
||||
|
||||
/**
|
||||
* If exit status is not continuable return <code>true</code>, otherwise
|
||||
* delegate to {@link #isComplete(RepeatContext)}.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext,
|
||||
* RepeatStatus)
|
||||
*/
|
||||
public boolean isComplete(RepeatContext context, RepeatStatus result) {
|
||||
if (result != null && !result.isContinuable()) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return isComplete(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always true.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext)
|
||||
*/
|
||||
public boolean isComplete(RepeatContext context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new {@link RepeatContextSupport} and return it.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
|
||||
*/
|
||||
public RepeatContext start(RepeatContext context) {
|
||||
return new RepeatContextSupport(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the context so the counter is up to date. Do nothing else.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#update(org.springframework.repeat.RepeatContext)
|
||||
*/
|
||||
public void update(RepeatContext context) {
|
||||
if (context instanceof RepeatContextSupport) {
|
||||
((RepeatContextSupport) context).increment();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.repeat.CompletionPolicy;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
|
||||
/**
|
||||
* Composite policy that loops through a list of delegate policies and answers
|
||||
* calls by a concensus.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class CompositeCompletionPolicy implements CompletionPolicy {
|
||||
|
||||
CompletionPolicy[] policies = new CompletionPolicy[0];
|
||||
|
||||
/**
|
||||
* Setter for the policies.
|
||||
*
|
||||
* @param policies
|
||||
*/
|
||||
public void setPolicies(CompletionPolicy[] policies) {
|
||||
this.policies = Arrays.asList(policies).toArray(new CompletionPolicy[policies.length]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This policy is complete if any of the composed policies is complete.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext,
|
||||
* RepeatStatus)
|
||||
*/
|
||||
public boolean isComplete(RepeatContext context, RepeatStatus result) {
|
||||
RepeatContext[] contexts = ((CompositeBatchContext) context).contexts;
|
||||
CompletionPolicy[] policies = ((CompositeBatchContext) context).policies;
|
||||
for (int i = 0; i < policies.length; i++) {
|
||||
if (policies[i].isComplete(contexts[i], result)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This policy is complete if any of the composed policies is complete.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext)
|
||||
*/
|
||||
public boolean isComplete(RepeatContext context) {
|
||||
RepeatContext[] contexts = ((CompositeBatchContext) context).contexts;
|
||||
CompletionPolicy[] policies = ((CompositeBatchContext) context).policies;
|
||||
for (int i = 0; i < policies.length; i++) {
|
||||
if (policies[i].isComplete(contexts[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new composite context from all the available policies.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
|
||||
*/
|
||||
public RepeatContext start(RepeatContext context) {
|
||||
List<RepeatContext> list = new ArrayList<RepeatContext>();
|
||||
for (int i = 0; i < policies.length; i++) {
|
||||
list.add(policies[i].start(context));
|
||||
}
|
||||
return new CompositeBatchContext(context, list);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all the composed contexts, and also increment the parent context.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#update(org.springframework.repeat.RepeatContext)
|
||||
*/
|
||||
public void update(RepeatContext context) {
|
||||
RepeatContext[] contexts = ((CompositeBatchContext) context).contexts;
|
||||
CompletionPolicy[] policies = ((CompositeBatchContext) context).policies;
|
||||
for (int i = 0; i < policies.length; i++) {
|
||||
policies[i].update(contexts[i]);
|
||||
}
|
||||
((RepeatContextSupport) context).increment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Composite context that knows about the policies and contexts is was
|
||||
* created with.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
protected class CompositeBatchContext extends RepeatContextSupport {
|
||||
|
||||
private RepeatContext[] contexts;
|
||||
|
||||
// Save a reference to the policies when we were created - gives some
|
||||
// protection against reference changes (e.g. if the number of policies
|
||||
// change).
|
||||
private CompletionPolicy[] policies;
|
||||
|
||||
public CompositeBatchContext(RepeatContext context, List<RepeatContext> contexts) {
|
||||
super(context);
|
||||
this.contexts = contexts.toArray(new RepeatContext[contexts.size()]);
|
||||
this.policies = CompositeCompletionPolicy.this.policies;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.context.RepeatContextCounter;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
|
||||
/**
|
||||
* Abstract base class for policies that need to count the number of occurrences
|
||||
* of some event (e.g. an exception type in the context), and terminate based on
|
||||
* a limit for the counter. The value of the counter can be stored between
|
||||
* batches in a nested context, so that the termination decision is based on the
|
||||
* aggregate of a number of sibling batches.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public abstract class CountingCompletionPolicy extends DefaultResultCompletionPolicy {
|
||||
|
||||
/**
|
||||
* Session key for global counter.
|
||||
*/
|
||||
public static final String COUNT = CountingCompletionPolicy.class.getName() + ".COUNT";
|
||||
|
||||
private boolean useParent = false;
|
||||
|
||||
private int maxCount = 0;
|
||||
|
||||
/**
|
||||
* Flag to indicate whether the count is at the level of the parent context,
|
||||
* or just local to the context. If true then the count is aggregated among
|
||||
* siblings in a nested batch.
|
||||
*
|
||||
* @param useParent whether to use the parent context to cache the total
|
||||
* count. Default value is false.
|
||||
*/
|
||||
public void setUseParent(boolean useParent) {
|
||||
this.useParent = useParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for maximum value of count before termination.
|
||||
*
|
||||
* @param maxCount the maximum number of counts before termination. Default
|
||||
* 0 so termination is immediate.
|
||||
*/
|
||||
public void setMaxCount(int maxCount) {
|
||||
this.maxCount = maxCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses. Obtain the value of the count in the
|
||||
* current context. Subclasses can count the number of attempts or
|
||||
* violations and store the result in their context. This policy base class
|
||||
* will take care of the termination contract and aggregating at the level
|
||||
* of the session if required.
|
||||
*
|
||||
* @param context the current context, specific to the subclass.
|
||||
* @return the value of the counter in the context.
|
||||
*/
|
||||
protected abstract int getCount(RepeatContext context);
|
||||
|
||||
/**
|
||||
* Extension point for subclasses. Inspect the context and update the state
|
||||
* of a counter in whatever way is appropriate. This will be added to the
|
||||
* session-level counter if {@link #setUseParent(boolean)} is true.
|
||||
*
|
||||
* @param context the current context.
|
||||
*
|
||||
* @return the change in the value of the counter (default 0).
|
||||
*/
|
||||
protected int doUpdate(RepeatContext context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.policy.CompletionPolicySupport#isComplete(org.springframework.batch.repeat.BatchContext)
|
||||
*/
|
||||
final public boolean isComplete(RepeatContext context) {
|
||||
int count = ((CountingBatchContext) context).getCounter().getCount();
|
||||
return count >= maxCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.policy.CompletionPolicySupport#start(org.springframework.batch.repeat.BatchContext)
|
||||
*/
|
||||
public RepeatContext start(RepeatContext parent) {
|
||||
return new CountingBatchContext(parent);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.policy.CompletionPolicySupport#update(org.springframework.batch.repeat.BatchContext)
|
||||
*/
|
||||
final public void update(RepeatContext context) {
|
||||
super.update(context);
|
||||
int delta = doUpdate(context);
|
||||
((CountingBatchContext) context).getCounter().increment(delta);
|
||||
}
|
||||
|
||||
protected class CountingBatchContext extends RepeatContextSupport {
|
||||
|
||||
RepeatContextCounter counter;
|
||||
|
||||
public CountingBatchContext(RepeatContext parent) {
|
||||
super(parent);
|
||||
counter = new RepeatContextCounter(this, COUNT, useParent);
|
||||
}
|
||||
|
||||
public RepeatContextCounter getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import org.springframework.repeat.CompletionPolicy;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
/**
|
||||
* Very simple {@link CompletionPolicy} that bases its decision on the result of
|
||||
* a batch operation. If the result is null or not continuable according to the
|
||||
* {@link RepeatStatus} the batch is complete, otherwise not.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultResultCompletionPolicy extends CompletionPolicySupport {
|
||||
|
||||
/**
|
||||
* True if the result is null, or a {@link RepeatStatus} indicating
|
||||
* completion.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext,
|
||||
* RepeatStatus)
|
||||
*/
|
||||
public boolean isComplete(RepeatContext context, RepeatStatus result) {
|
||||
return (result == null || !result.isContinuable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Always false.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext)
|
||||
*/
|
||||
public boolean isComplete(RepeatContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
import org.springframework.repeat.support.RepeatTemplate;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Policy for terminating a batch after a fixed number of operations. Internal
|
||||
* state is maintained and a counter incremented, so successful use of this
|
||||
* policy requires that isComplete() is only called once per batch item. Using
|
||||
* the standard {@link RepeatTemplate} should ensure this contract is kept, but it needs
|
||||
* to be carefully monitored.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleCompletionPolicy extends DefaultResultCompletionPolicy {
|
||||
|
||||
public static final int DEFAULT_CHUNK_SIZE = 5;
|
||||
|
||||
int chunkSize = 0;
|
||||
|
||||
public SimpleCompletionPolicy() {
|
||||
this(DEFAULT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
public SimpleCompletionPolicy(int chunkSize) {
|
||||
super();
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
public void setChunkSize(int chunkSize) {
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the counter.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
|
||||
*/
|
||||
public RepeatContext start(RepeatContext context) {
|
||||
return new SimpleTerminationContext(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate if the chunk size has been reached, or the result is null.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(RepeatContext,
|
||||
* RepeatStatus)
|
||||
* @throws RuntimeException (normally terminating the batch) if the result is
|
||||
* itself an exception.
|
||||
*/
|
||||
public boolean isComplete(RepeatContext context, RepeatStatus result) {
|
||||
return super.isComplete(context, result) || ((SimpleTerminationContext) context).isComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate if the chunk size has been reached.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(RepeatContext)
|
||||
*/
|
||||
public boolean isComplete(RepeatContext context) {
|
||||
return ((SimpleTerminationContext) context).isComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the counter in the context.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#update(RepeatContext)
|
||||
*/
|
||||
public void update(RepeatContext context) {
|
||||
((SimpleTerminationContext) context).update();
|
||||
}
|
||||
|
||||
protected class SimpleTerminationContext extends RepeatContextSupport {
|
||||
|
||||
public SimpleTerminationContext(RepeatContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
increment();
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return getStartedCount() >= chunkSize;
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return ClassUtils.getShortName(SimpleCompletionPolicy.class)+": chunkSize="+chunkSize;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
|
||||
/**
|
||||
* Termination policy that times out after a fixed period. Allows graceful exit
|
||||
* from a batch if the latest result comes in after the timeout expires (i.e.
|
||||
* does not throw a timeout exception).<br/>
|
||||
*
|
||||
* N.B. It may often be the case that the batch governed by this policy will be
|
||||
* transactional, and the transaction might have its own timeout. In this case
|
||||
* the transaction might throw a timeout exception on commit if its timeout
|
||||
* threshold is lower than the termination policy.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class TimeoutTerminationPolicy extends CompletionPolicySupport {
|
||||
|
||||
/**
|
||||
* Default timeout value in millisecs (the value equivalent to 30 seconds).
|
||||
*/
|
||||
public static final long DEFAULT_TIMEOUT = 30000L;
|
||||
|
||||
private long timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public TimeoutTerminationPolicy() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link TimeoutTerminationPolicy} with the specified timeout
|
||||
* value (in milliseconds).
|
||||
*
|
||||
* @param timeout
|
||||
*/
|
||||
public TimeoutTerminationPolicy(long timeout) {
|
||||
super();
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the timeout and complete gracefully if it has expires.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext)
|
||||
*/
|
||||
@Override
|
||||
public boolean isComplete(RepeatContext context) {
|
||||
return ((TimeoutBatchContext) context).isComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the clock on the timeout.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
|
||||
*/
|
||||
@Override
|
||||
public RepeatContext start(RepeatContext context) {
|
||||
return new TimeoutBatchContext(context);
|
||||
}
|
||||
|
||||
protected class TimeoutBatchContext extends RepeatContextSupport {
|
||||
|
||||
private volatile long time = System.currentTimeMillis();
|
||||
|
||||
private final long timeout = TimeoutTerminationPolicy.this.timeout;
|
||||
|
||||
public TimeoutBatchContext(RepeatContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return (System.currentTimeMillis() - time) > timeout;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Infrastructure implementations of repeat policy concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Internal interface for extensions of {@link RepeatTemplate}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface RepeatInternalState {
|
||||
|
||||
/**
|
||||
* Returns a mutable collection of exceptions that have occurred in the
|
||||
* current repeat context. Clients are expected to mutate this collection.
|
||||
*
|
||||
* @return the collection of exceptions being accumulated
|
||||
*/
|
||||
Collection<Throwable> getThrowables();
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class RepeatInternalStateSupport implements RepeatInternalState {
|
||||
|
||||
// Accumulation of failed results.
|
||||
private final Set<Throwable> throwables = new HashSet<Throwable>();
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.repeat.support.BatchInternalState#getThrowables()
|
||||
*/
|
||||
public Collection<Throwable> getThrowables() {
|
||||
return throwables;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatOperations;
|
||||
|
||||
/**
|
||||
* Global variable support for repeat clients. Normally it is not necessary for
|
||||
* clients to be aware of the surrounding environment because a
|
||||
* {@link RepeatCallback} can always use the context it is passed by the
|
||||
* enclosing {@link RepeatOperations}. But occasionally it might be helpful to
|
||||
* have lower level access to the ongoing {@link RepeatContext} so we provide a
|
||||
* global accessor here. The mutator methods ({@link #clear()} and
|
||||
* {@link #register(RepeatContext)} should not be used except internally by
|
||||
* {@link RepeatOperations} implementations.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public final class RepeatSynchronizationManager {
|
||||
|
||||
private static final ThreadLocal<RepeatContext> contextHolder = new ThreadLocal<RepeatContext>();
|
||||
|
||||
private RepeatSynchronizationManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the current context. A context is shared by all items in the
|
||||
* batch, so this method is intended to return the same context object
|
||||
* independent of whether the callback is running synchronously or
|
||||
* asynchronously with the surrounding {@link RepeatOperations}.
|
||||
*
|
||||
* @return the current {@link RepeatContext} or null if there is none (if we
|
||||
* are not in a batch).
|
||||
*/
|
||||
public static RepeatContext getContext() {
|
||||
return contextHolder.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to set the current repeat operation to complete if it
|
||||
* exists.
|
||||
*/
|
||||
public static void setCompleteOnly() {
|
||||
RepeatContext context = getContext();
|
||||
if (context != null) {
|
||||
context.setCompleteOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for registering a context - should only be used by
|
||||
* {@link RepeatOperations} implementations to ensure that
|
||||
* {@link #getContext()} always returns the correct value.
|
||||
*
|
||||
* @param context a new context at the start of a batch.
|
||||
* @return the old value if there was one.
|
||||
*/
|
||||
public static RepeatContext register(RepeatContext context) {
|
||||
RepeatContext oldSession = getContext();
|
||||
RepeatSynchronizationManager.contextHolder.set(context);
|
||||
return oldSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current context at the end of a batch - should only be used by
|
||||
* {@link RepeatOperations} implementations.
|
||||
*
|
||||
* @return the old value if there was one.
|
||||
*/
|
||||
public static RepeatContext clear() {
|
||||
RepeatContext context = getContext();
|
||||
RepeatSynchronizationManager.contextHolder.set(null);
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current session and all ancestors (via parent) to complete.,
|
||||
*/
|
||||
public static void setAncestorsCompleteOnly() {
|
||||
RepeatContext context = getContext();
|
||||
while (context != null) {
|
||||
context.setCompleteOnly();
|
||||
context = context.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,476 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.repeat.CompletionPolicy;
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatException;
|
||||
import org.springframework.repeat.RepeatListener;
|
||||
import org.springframework.repeat.RepeatOperations;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.exception.DefaultExceptionHandler;
|
||||
import org.springframework.repeat.exception.ExceptionHandler;
|
||||
import org.springframework.repeat.policy.DefaultResultCompletionPolicy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple implementation and base class for batch templates implementing
|
||||
* {@link RepeatOperations}. Provides a framework including interceptors and
|
||||
* policies. Subclasses just need to provide a method that gets the next result
|
||||
* and one that waits for all the results to be returned from concurrent
|
||||
* processes or threads.<br/>
|
||||
*
|
||||
* N.B. the template accumulates thrown exceptions during the iteration, and
|
||||
* they are all processed together when the main loop ends (i.e. finished
|
||||
* processing the items). Clients that do not want to stop execution when an
|
||||
* exception is thrown can use a specific {@link CompletionPolicy} that does not
|
||||
* finish when exceptions are received. This is not the default behaviour.<br/>
|
||||
*
|
||||
* Clients that want to take some business action when an exception is thrown by
|
||||
* the {@link RepeatCallback} can consider using a custom {@link RepeatListener}
|
||||
* instead of trying to customise the {@link CompletionPolicy}. This is
|
||||
* generally a friendlier interface to implement, and the
|
||||
* {@link RepeatListener#after(RepeatContext, RepeatStatus)} method is passed in
|
||||
* the result of the callback, which would be an instance of {@link Throwable}
|
||||
* if the business processing had thrown an exception. If the exception is not
|
||||
* to be propagated to the caller, then a non-default {@link CompletionPolicy}
|
||||
* needs to be provided as well, but that could be off the shelf, with the
|
||||
* business action implemented only in the interceptor.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RepeatTemplate implements RepeatOperations {
|
||||
|
||||
protected Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private RepeatListener[] listeners = new RepeatListener[] {};
|
||||
|
||||
private CompletionPolicy completionPolicy = new DefaultResultCompletionPolicy();
|
||||
|
||||
private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
|
||||
|
||||
/**
|
||||
* Set the listeners for this template, registering them for callbacks at
|
||||
* appropriate times in the iteration.
|
||||
*
|
||||
* @param listeners
|
||||
*/
|
||||
public void setListeners(RepeatListener[] listeners) {
|
||||
this.listeners = Arrays.asList(listeners).toArray(new RepeatListener[listeners.length]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an additional listener.
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public void registerListener(RepeatListener listener) {
|
||||
List<RepeatListener> list = new ArrayList<RepeatListener>(Arrays.asList(listeners));
|
||||
list.add(listener);
|
||||
listeners = (RepeatListener[]) list.toArray(new RepeatListener[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for exception handler strategy. The exception handler is called at
|
||||
* the end of a batch, after the {@link CompletionPolicy} has determined
|
||||
* that the batch is complete. By default all exceptions are re-thrown.
|
||||
*
|
||||
* @see ExceptionHandler
|
||||
* @see DefaultExceptionHandler
|
||||
* @see #setCompletionPolicy(CompletionPolicy)
|
||||
*
|
||||
* @param exceptionHandler the {@link ExceptionHandler} to use.
|
||||
*/
|
||||
public void setExceptionHandler(ExceptionHandler exceptionHandler) {
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for policy to decide when the batch is complete. The default is to
|
||||
* complete normally when the callback returns a {@link RepeatStatus} which
|
||||
* is not marked as continuable, and abnormally when the callback throws an
|
||||
* exception (but the decision to re-throw the exception is deferred to the
|
||||
* {@link ExceptionHandler}).
|
||||
*
|
||||
* @see #setExceptionHandler(ExceptionHandler)
|
||||
*
|
||||
* @param terminationPolicy a TerminationPolicy.
|
||||
* @throws IllegalArgumentException if the argument is null
|
||||
*/
|
||||
public void setCompletionPolicy(CompletionPolicy terminationPolicy) {
|
||||
Assert.notNull(terminationPolicy);
|
||||
this.completionPolicy = terminationPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the batch callback until the completion policy decides that we
|
||||
* are finished. Wait for the whole batch to finish before returning even if
|
||||
* the task executor is asynchronous.
|
||||
*
|
||||
* @see org.springframework.repeat.RepeatOperations#iterate(org.springframework.repeat.RepeatCallback)
|
||||
*/
|
||||
public RepeatStatus iterate(RepeatCallback callback) {
|
||||
|
||||
RepeatContext outer = RepeatSynchronizationManager.getContext();
|
||||
|
||||
RepeatStatus result = RepeatStatus.CONTINUABLE;
|
||||
try {
|
||||
// This works with an asynchronous TaskExecutor: the
|
||||
// interceptors have to wait for the child processes.
|
||||
result = executeInternal(callback);
|
||||
}
|
||||
finally {
|
||||
RepeatSynchronizationManager.clear();
|
||||
if (outer != null) {
|
||||
RepeatSynchronizationManager.register(outer);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal convenience method to loop over interceptors and batch
|
||||
* callbacks.
|
||||
*
|
||||
* @param callback the callback to process each element of the loop.
|
||||
*
|
||||
* @return the aggregate of {@link ContinuationPolicy#canContinue(Object)}
|
||||
* for all the results from the callback.
|
||||
*
|
||||
*/
|
||||
private RepeatStatus executeInternal(final RepeatCallback callback) {
|
||||
|
||||
// Reset the termination policy if there is one...
|
||||
RepeatContext context = start();
|
||||
|
||||
// Make sure if we are already marked complete before we start then no
|
||||
// processing takes place.
|
||||
boolean running = !isMarkedComplete(context);
|
||||
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
RepeatListener interceptor = listeners[i];
|
||||
interceptor.open(context);
|
||||
running = running && !isMarkedComplete(context);
|
||||
if (!running)
|
||||
break;
|
||||
}
|
||||
|
||||
// Return value, default is to allow continued processing.
|
||||
RepeatStatus result = RepeatStatus.CONTINUABLE;
|
||||
|
||||
RepeatInternalState state = createInternalState(context);
|
||||
// This is the list of exceptions thrown by all active callbacks
|
||||
Collection<Throwable> throwables = state.getThrowables();
|
||||
// Keep a separate list of exceptions we handled that need to be
|
||||
// rethrown
|
||||
Collection<Throwable> deferred = new ArrayList<Throwable>();
|
||||
|
||||
try {
|
||||
|
||||
while (running) {
|
||||
|
||||
/*
|
||||
* Run the before interceptors here, not in the task executor so
|
||||
* that they all happen in the same thread - it's easier for
|
||||
* tracking batch status, amongst other things.
|
||||
*/
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
RepeatListener interceptor = listeners[i];
|
||||
interceptor.before(context);
|
||||
// Allow before interceptors to veto the batch by setting
|
||||
// flag.
|
||||
running = running && !isMarkedComplete(context);
|
||||
}
|
||||
|
||||
// Check that we are still running (should always be true) ...
|
||||
if (running) {
|
||||
|
||||
try {
|
||||
|
||||
result = getNextResult(context, callback, state);
|
||||
executeAfterInterceptors(context, result);
|
||||
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
doHandle(throwable, context, deferred);
|
||||
}
|
||||
|
||||
// N.B. the order may be important here:
|
||||
if (isComplete(context, result) || isMarkedComplete(context) || !deferred.isEmpty()) {
|
||||
running = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result = result.and(waitForResults(state));
|
||||
for (Throwable throwable : throwables) {
|
||||
doHandle(throwable, context, deferred);
|
||||
}
|
||||
|
||||
// Explicitly drop any references to internal state...
|
||||
state = null;
|
||||
|
||||
}
|
||||
/*
|
||||
* No need for explicit catch here - if the business processing threw an
|
||||
* exception it was already handled by the helper methods. An exception
|
||||
* here is necessarily fatal.
|
||||
*/
|
||||
finally {
|
||||
|
||||
try {
|
||||
|
||||
if (!deferred.isEmpty()) {
|
||||
Throwable throwable = (Throwable) deferred.iterator().next();
|
||||
logger.debug("Handling fatal exception explicitly (rethrowing first of " + deferred.size() + "): "
|
||||
+ throwable.getClass().getName() + ": " + throwable.getMessage());
|
||||
rethrow(throwable);
|
||||
}
|
||||
|
||||
}
|
||||
finally {
|
||||
|
||||
try {
|
||||
for (int i = listeners.length; i-- > 0;) {
|
||||
RepeatListener interceptor = listeners[i];
|
||||
interceptor.close(context);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
context.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private void doHandle(Throwable throwable, RepeatContext context, Collection<Throwable> deferred) {
|
||||
// An exception alone is not sufficient grounds for not
|
||||
// continuing
|
||||
Throwable unwrappedThrowable = unwrapIfRethrown(throwable);
|
||||
try {
|
||||
|
||||
for (int i = listeners.length; i-- > 0;) {
|
||||
RepeatListener interceptor = listeners[i];
|
||||
// This is not an error - only log at debug
|
||||
// level.
|
||||
logger.debug("Exception intercepted (" + (i + 1) + " of " + listeners.length + ")", unwrappedThrowable);
|
||||
interceptor.onError(context, unwrappedThrowable);
|
||||
}
|
||||
|
||||
logger.debug("Handling exception: " + throwable.getClass().getName() + ", caused by: "
|
||||
+ unwrappedThrowable.getClass().getName() + ": " + unwrappedThrowable.getMessage());
|
||||
exceptionHandler.handleException(context, unwrappedThrowable);
|
||||
|
||||
}
|
||||
catch (Throwable handled) {
|
||||
deferred.add(handled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-throws the original throwable if it is unchecked, wraps checked
|
||||
* exceptions into {@link RepeatException}.
|
||||
*/
|
||||
private static void rethrow(Throwable throwable) throws RuntimeException {
|
||||
if (throwable instanceof Error) {
|
||||
throw (Error) throwable;
|
||||
}
|
||||
else if (throwable instanceof RuntimeException) {
|
||||
throw (RuntimeException) throwable;
|
||||
}
|
||||
else {
|
||||
throw new RepeatException("Exception in batch process", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps the throwable if it has been wrapped by
|
||||
* {@link #rethrow(Throwable)}.
|
||||
*/
|
||||
private static Throwable unwrapIfRethrown(Throwable throwable) {
|
||||
if (throwable instanceof RepeatException) {
|
||||
return throwable.getCause();
|
||||
}
|
||||
else {
|
||||
return throwable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an internal state object that is used to store data needed
|
||||
* internally in the scope of an iteration. Used by subclasses to manage the
|
||||
* queueing and retrieval of asynchronous results. The default just provides
|
||||
* an accumulation of Throwable instances for processing at the end of the
|
||||
* batch.
|
||||
*
|
||||
* @param context the current {@link RepeatContext}
|
||||
* @return a {@link RepeatInternalState} instance.
|
||||
*
|
||||
* @see RepeatTemplate#waitForResults(RepeatInternalState)
|
||||
*/
|
||||
protected RepeatInternalState createInternalState(RepeatContext context) {
|
||||
return new RepeatInternalStateSupport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next completed result, possibly executing several callbacks until
|
||||
* one finally finishes. Normally a subclass would have to override both
|
||||
* this method and {@link #createInternalState(RepeatContext)} because the
|
||||
* implementation of this method would rely on the details of the internal
|
||||
* state.
|
||||
*
|
||||
* @param context current BatchContext.
|
||||
* @param callback the callback to execute.
|
||||
* @param state maintained by the implementation.
|
||||
* @return a finished result.
|
||||
*
|
||||
* @see #isComplete(RepeatContext)
|
||||
* @see #createInternalState(RepeatContext)
|
||||
*/
|
||||
protected RepeatStatus getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state)
|
||||
throws Throwable {
|
||||
update(context);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Repeat operation about to start at count=" + context.getStartedCount());
|
||||
}
|
||||
return callback.doInIteration(context);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* If necessary, wait for results to come back from remote or concurrent
|
||||
* processes. By default does nothing and returns true.
|
||||
*
|
||||
* @param state the internal state.
|
||||
* @return true if {@link #canContinue(RepeatStatus)} is true for all
|
||||
* results retrieved.
|
||||
*/
|
||||
protected boolean waitForResults(RepeatInternalState state) {
|
||||
// no-op by default
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check return value from batch operation.
|
||||
*
|
||||
* @param value the last callback result.
|
||||
* @return true if the value is {@link RepeatStatus#CONTINUABLE}.
|
||||
*/
|
||||
protected final boolean canContinue(RepeatStatus value) {
|
||||
return ((RepeatStatus) value).isContinuable();
|
||||
}
|
||||
|
||||
private boolean isMarkedComplete(RepeatContext context) {
|
||||
boolean complete = context.isCompleteOnly();
|
||||
if (context.getParent() != null) {
|
||||
complete = complete || isMarkedComplete(context.getParent());
|
||||
}
|
||||
if (complete) {
|
||||
logger.debug("Repeat is complete according to context alone.");
|
||||
}
|
||||
return complete;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to execute after interceptors on a callback result.
|
||||
*
|
||||
* @param context the current batch context.
|
||||
* @param value the result of the callback to process.
|
||||
*/
|
||||
protected void executeAfterInterceptors(final RepeatContext context, RepeatStatus value) {
|
||||
|
||||
// Don't re-throw exceptions here: let the exception handler deal with
|
||||
// that...
|
||||
|
||||
if (value != null && value.isContinuable()) {
|
||||
for (int i = listeners.length; i-- > 0;) {
|
||||
RepeatListener interceptor = listeners[i];
|
||||
interceptor.after(context, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the {@link CompletionPolicy}.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(RepeatContext,
|
||||
* RepeatStatus)
|
||||
*/
|
||||
protected boolean isComplete(RepeatContext context, RepeatStatus result) {
|
||||
boolean complete = completionPolicy.isComplete(context, result);
|
||||
if (complete) {
|
||||
logger.debug("Repeat is complete according to policy and result value.");
|
||||
}
|
||||
return complete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to {@link CompletionPolicy}.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#isComplete(RepeatContext)
|
||||
*/
|
||||
protected boolean isComplete(RepeatContext context) {
|
||||
boolean complete = completionPolicy.isComplete(context);
|
||||
if (complete) {
|
||||
logger.debug("Repeat is complete according to policy alone not including result.");
|
||||
}
|
||||
return complete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the {@link CompletionPolicy}.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
|
||||
*/
|
||||
protected RepeatContext start() {
|
||||
RepeatContext parent = RepeatSynchronizationManager.getContext();
|
||||
RepeatContext context = completionPolicy.start(parent);
|
||||
RepeatSynchronizationManager.register(context);
|
||||
logger.debug("Starting repeat context.");
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the {@link CompletionPolicy}.
|
||||
*
|
||||
* @see org.springframework.repeat.CompletionPolicy#update(RepeatContext)
|
||||
*/
|
||||
protected void update(RepeatContext context) {
|
||||
completionPolicy.update(context);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
/**
|
||||
* Interface for result holder.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
interface ResultHolder {
|
||||
/**
|
||||
* Get the result for client from this holder. Does not block if none is
|
||||
* available yet.
|
||||
*
|
||||
* @return the result, or null if there is none.
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
RepeatStatus getResult();
|
||||
|
||||
/**
|
||||
* Get the error for client from this holder if any. Does not block if
|
||||
* none is available yet.
|
||||
*
|
||||
* @return the error, or null if there is none.
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
Throwable getError();
|
||||
|
||||
/**
|
||||
* Get the context in which the result evaluation is executing.
|
||||
*
|
||||
* @return the context of the result evaluation.
|
||||
*/
|
||||
RepeatContext getContext();
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
/**
|
||||
* An implementation of the {@link ResultQueue} that throttles the number of
|
||||
* expected results, limiting it to a maximum at any given time.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class ResultHolderResultQueue implements ResultQueue<ResultHolder> {
|
||||
|
||||
// Accumulation of result objects as they finish.
|
||||
private final BlockingQueue<ResultHolder> results;
|
||||
|
||||
// Accumulation of dummy objects flagging expected results in the future.
|
||||
private final Semaphore waits;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
private volatile int count = 0;
|
||||
|
||||
/**
|
||||
* @param throttleLimit the maximum number of results that can be expected
|
||||
* at any given time.
|
||||
*/
|
||||
public ResultHolderResultQueue(int throttleLimit) {
|
||||
results = new PriorityBlockingQueue<ResultHolder>(throttleLimit, new ResultHolderComparator());
|
||||
waits = new Semaphore(throttleLimit);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return results.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.support.ResultQueue#isExpecting()
|
||||
*/
|
||||
public boolean isExpecting() {
|
||||
// Base the decision about whether we expect more results on a
|
||||
// counter of the number of expected results actually collected.
|
||||
// Do not synchronize! Otherwise put and expect can deadlock.
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the queue to expect one more result. Blocks until a new result is
|
||||
* available if already expecting too many (as determined by the throttle
|
||||
* limit).
|
||||
*
|
||||
* @see ResultQueue#expect()
|
||||
*/
|
||||
public void expect() throws InterruptedException {
|
||||
waits.acquire();
|
||||
// Don't acquire the lock in a synchronized block - might deadlock
|
||||
synchronized (lock) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
public void put(ResultHolder holder) throws IllegalArgumentException {
|
||||
if (!isExpecting()) {
|
||||
throw new IllegalArgumentException("Not expecting a result. Call expect() before put().");
|
||||
}
|
||||
results.add(holder);
|
||||
// Take from the waits queue now to allow another result to
|
||||
// accumulate. But don't decrement the counter.
|
||||
waits.release();
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next result as soon as it becomes available. <br/>
|
||||
* <br/>
|
||||
* Release result immediately if:
|
||||
* <ul>
|
||||
* <li>There is a result that is continuable.</li>
|
||||
* </ul>
|
||||
* Otherwise block if either:
|
||||
* <ul>
|
||||
* <li>There is no result (as per contract of {@link ResultQueue}).</li>
|
||||
* <li>The number of results is less than the number expected.</li>
|
||||
* </ul>
|
||||
* Error if either:
|
||||
* <ul>
|
||||
* <li>Not expecting.</li>
|
||||
* <li>Interrupted.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see ResultQueue#take()
|
||||
*/
|
||||
public ResultHolder take() throws NoSuchElementException, InterruptedException {
|
||||
if (!isExpecting()) {
|
||||
throw new NoSuchElementException("Not expecting a result. Call expect() before take().");
|
||||
}
|
||||
ResultHolder value;
|
||||
synchronized (lock) {
|
||||
value = results.take();
|
||||
if (isContinuable(value)) {
|
||||
// Decrement the counter only when the result is collected.
|
||||
count--;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
results.put(value);
|
||||
synchronized (lock) {
|
||||
while (count > results.size()) {
|
||||
lock.wait();
|
||||
}
|
||||
value = results.take();
|
||||
count--;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private boolean isContinuable(ResultHolder value) {
|
||||
return value.getResult() != null && value.getResult().isContinuable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares ResultHolders so that one that is continuable ranks lowest.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class ResultHolderComparator implements Comparator<ResultHolder> {
|
||||
public int compare(ResultHolder h1, ResultHolder h2) {
|
||||
RepeatStatus result1 = h1.getResult();
|
||||
RepeatStatus result2 = h2.getResult();
|
||||
if (result1 == null && result2 == null) {
|
||||
return 0;
|
||||
}
|
||||
if (result1 == null) {
|
||||
return -1;
|
||||
}
|
||||
else if (result2 == null) {
|
||||
return 1;
|
||||
}
|
||||
if ((result1.isContinuable() && result2.isContinuable())
|
||||
|| (!result1.isContinuable() && !result2.isContinuable())) {
|
||||
return 0;
|
||||
}
|
||||
if (result1.isContinuable()) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
|
||||
/**
|
||||
* Abstraction for queue of {@link ResultHolder} objects. Acts a bit likeT a
|
||||
* {@link BlockingQueue} with the ability to count the number of items it
|
||||
* expects to ever hold. When clients schedule an item to be added they call
|
||||
* {@link #expect()}, and then collect the result later with {@link #take()}.
|
||||
* Result providers in another thread call {@link #put(Object)} to notify the
|
||||
* expecting client of a new result.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Ben Hale
|
||||
*/
|
||||
interface ResultQueue<T> {
|
||||
|
||||
/**
|
||||
* In a master-slave pattern, the master calls this method paired with
|
||||
* {@link #take()} to manage the flow of items. Normally a task is submitted
|
||||
* for processing in another thread, at which point the master uses this
|
||||
* method to keep track of the number of expected results. It has the
|
||||
* personality of an counter increment, rather than a work queue, which is
|
||||
* usually managed elsewhere, e.g. by a {@link TaskExecutor}.<br/><br/>
|
||||
* Implementations may choose to block here, if they need to limit the
|
||||
* number or rate of tasks being submitted.
|
||||
*
|
||||
* @throws InterruptedException if the call blocks and is then interrupted.
|
||||
*/
|
||||
void expect() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Once it is expecting a result, clients call this method to satisfy the
|
||||
* expectation. In a master-worker pattern, the workers call this method to
|
||||
* deposit the result of a finished task on the queue for collection.
|
||||
*
|
||||
* @param result the result for later collection.
|
||||
*
|
||||
* @throws IllegalArgumentException if the queue is not expecting a new
|
||||
* result
|
||||
*/
|
||||
void put(T result) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Gets the next available result, blocking if there are none yet available.
|
||||
*
|
||||
* @return a result previously deposited
|
||||
*
|
||||
* @throws NoSuchElementException if there is no result expected
|
||||
* @throws InterruptedException if the operation is interrupted while
|
||||
* waiting
|
||||
*/
|
||||
T take() throws NoSuchElementException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Used by master thread to verify that there are results available from
|
||||
* {@link #take()} without possibly having to block and wait.
|
||||
*
|
||||
* @return true if there are no results available
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Check if any results are expected. Usually used by master thread to drain
|
||||
* queue when it is finished.
|
||||
*
|
||||
* @return true if more results are expected, but possibly not yet
|
||||
* available.
|
||||
*/
|
||||
public boolean isExpecting();
|
||||
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatException;
|
||||
import org.springframework.repeat.RepeatOperations;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Provides {@link RepeatOperations} support including interceptors that can be
|
||||
* used to modify or monitor the behaviour at run time.<br/>
|
||||
*
|
||||
* This implementation is sufficient to be used to configure transactional
|
||||
* behaviour for each item by making the {@link RepeatCallback} transactional,
|
||||
* or for the whole batch by making the execute method transactional (but only
|
||||
* then if the task executor is synchronous).<br/>
|
||||
*
|
||||
* This class is thread safe if its collaborators are thread safe (interceptors,
|
||||
* terminationPolicy, callback). Normally this will be the case, but clients
|
||||
* need to be aware that if the task executor is asynchronous, then the other
|
||||
* collaborators should be also. In particular the {@link RepeatCallback} that
|
||||
* is wrapped in the execute method must be thread safe - often it is based on
|
||||
* some form of data source, which itself should be both thread safe and
|
||||
* transactional (multiple threads could be accessing it at any given time, and
|
||||
* each thread would have its own transaction).<br/>
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class TaskExecutorRepeatTemplate extends RepeatTemplate {
|
||||
|
||||
/**
|
||||
* Default limit for maximum number of concurrent unfinished results allowed
|
||||
* by the template.
|
||||
* {@link #getNextResult(RepeatContext, RepeatCallback, RepeatInternalState)}
|
||||
* .
|
||||
*/
|
||||
public static final int DEFAULT_THROTTLE_LIMIT = 4;
|
||||
|
||||
private int throttleLimit = DEFAULT_THROTTLE_LIMIT;
|
||||
|
||||
private TaskExecutor taskExecutor = new SyncTaskExecutor();
|
||||
|
||||
/**
|
||||
* Public setter for the throttle limit. The throttle limit is the largest
|
||||
* number of concurrent tasks that can be executing at one time - if a new
|
||||
* task arrives and the throttle limit is breached we wait for one of the
|
||||
* executing tasks to finish before submitting the new one to the
|
||||
* {@link TaskExecutor}. Default value is {@link #DEFAULT_THROTTLE_LIMIT}.
|
||||
* N.B. when used with a thread pooled {@link TaskExecutor} the thread pool
|
||||
* might prevent the throttle limit actually being reached (so make the core
|
||||
* pool size larger than the throttle limit if possible).
|
||||
*
|
||||
* @param throttleLimit the throttleLimit to set.
|
||||
*/
|
||||
public void setThrottleLimit(int throttleLimit) {
|
||||
this.throttleLimit = throttleLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for task executor to be used to run the individual item callbacks.
|
||||
*
|
||||
* @param taskExecutor a TaskExecutor
|
||||
* @throws IllegalArgumentException if the argument is null
|
||||
*/
|
||||
public void setTaskExecutor(TaskExecutor taskExecutor) {
|
||||
Assert.notNull(taskExecutor);
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the {@link #setTaskExecutor(TaskExecutor)} to generate a result. The
|
||||
* internal state in this case is a queue of unfinished result holders of
|
||||
* type {@link ResultHolder}. The holder with the return value should not be
|
||||
* on the queue when this method exits. The queue is scoped in the calling
|
||||
* method so there is no need to synchronize access.
|
||||
*
|
||||
*/
|
||||
protected RepeatStatus getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state)
|
||||
throws Throwable {
|
||||
|
||||
ExecutingRunnable runnable = null;
|
||||
|
||||
ResultQueue<ResultHolder> queue = ((ResultQueueInternalState) state).getResultQueue();
|
||||
|
||||
do {
|
||||
|
||||
/*
|
||||
* Wrap the callback in a runnable that will add its result to the
|
||||
* queue when it is ready.
|
||||
*/
|
||||
runnable = new ExecutingRunnable(callback, context, queue);
|
||||
|
||||
/**
|
||||
* Tell the runnable that it can expect a result. This could have
|
||||
* been in-lined with the constructor, but it might block, so it's
|
||||
* better to do it here, since we have the option (it's a private
|
||||
* class).
|
||||
*/
|
||||
runnable.expect();
|
||||
|
||||
/*
|
||||
* Start the task possibly concurrently / in the future.
|
||||
*/
|
||||
taskExecutor.execute(runnable);
|
||||
|
||||
/*
|
||||
* Allow termination policy to update its state. This must happen
|
||||
* immediately before or after the call to the task executor.
|
||||
*/
|
||||
update(context);
|
||||
|
||||
/*
|
||||
* Keep going until we get a result that is finished, or early
|
||||
* termination...
|
||||
*/
|
||||
} while (queue.isEmpty() && !isComplete(context));
|
||||
|
||||
/*
|
||||
* N.B. If the queue is empty then take() blocks until a result appears,
|
||||
* and there must be at least one because we just submitted one to the
|
||||
* task executor.
|
||||
*/
|
||||
ResultHolder result = queue.take();
|
||||
if (result.getError() != null) {
|
||||
throw result.getError();
|
||||
}
|
||||
return result.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for all the results to appear on the queue and execute the after
|
||||
* interceptors for each one.
|
||||
*
|
||||
* @see org.springframework.repeat.support.RepeatTemplate#waitForResults(org.springframework.repeat.support.RepeatInternalState)
|
||||
*/
|
||||
protected boolean waitForResults(RepeatInternalState state) {
|
||||
|
||||
ResultQueue<ResultHolder> queue = ((ResultQueueInternalState) state).getResultQueue();
|
||||
|
||||
boolean result = true;
|
||||
|
||||
while (queue.isExpecting()) {
|
||||
|
||||
/*
|
||||
* Careful that no runnables that are not going to finish ever get
|
||||
* onto the queue, else this may block forever.
|
||||
*/
|
||||
ResultHolder future;
|
||||
try {
|
||||
future = (ResultHolder) queue.take();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RepeatException("InterruptedException while waiting for result.");
|
||||
}
|
||||
|
||||
if (future.getError() != null) {
|
||||
state.getThrowables().add(future.getError());
|
||||
}
|
||||
else {
|
||||
RepeatStatus status = future.getResult();
|
||||
result = result && canContinue(status);
|
||||
executeAfterInterceptors(future.getContext(), status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Assert.state(queue.isEmpty(), "Future results queue should be empty at end of batch.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected RepeatInternalState createInternalState(RepeatContext context) {
|
||||
// Queue of pending results:
|
||||
return new ResultQueueInternalState(throttleLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* A runnable that puts its result on a queue when it is done.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private class ExecutingRunnable implements Runnable, ResultHolder {
|
||||
|
||||
private final RepeatCallback callback;
|
||||
|
||||
private final RepeatContext context;
|
||||
|
||||
private final ResultQueue<ResultHolder> queue;
|
||||
|
||||
private volatile RepeatStatus result;
|
||||
|
||||
private volatile Throwable error;
|
||||
|
||||
public ExecutingRunnable(RepeatCallback callback, RepeatContext context, ResultQueue<ResultHolder> queue) {
|
||||
|
||||
super();
|
||||
|
||||
this.callback = callback;
|
||||
this.context = context;
|
||||
this.queue = queue;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the queue to expect a result.
|
||||
*/
|
||||
public void expect() {
|
||||
try {
|
||||
queue.expect();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RepeatException("InterruptedException waiting for to acquire lock on input.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the batch callback, and store the result, or any exception
|
||||
* that is thrown for retrieval later by caller.
|
||||
*
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
boolean clearContext = false;
|
||||
try {
|
||||
if (RepeatSynchronizationManager.getContext() == null) {
|
||||
clearContext = true;
|
||||
RepeatSynchronizationManager.register(context);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Repeat operation about to start at count=" + context.getStartedCount());
|
||||
}
|
||||
|
||||
result = callback.doInIteration(context);
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
error = e;
|
||||
}
|
||||
finally {
|
||||
|
||||
if (clearContext) {
|
||||
RepeatSynchronizationManager.clear();
|
||||
}
|
||||
|
||||
queue.put(this);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result - never blocks because the queue manages waiting for
|
||||
* the task to finish.
|
||||
*/
|
||||
public RepeatStatus getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error - never blocks because the queue manages waiting for
|
||||
* the task to finish.
|
||||
*/
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the context.
|
||||
*/
|
||||
public RepeatContext getContext() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class ResultQueueInternalState extends RepeatInternalStateSupport {
|
||||
|
||||
private final ResultQueue<ResultHolder> results;
|
||||
|
||||
/**
|
||||
* @param throttleLimit the throttle limit for the result queue
|
||||
*/
|
||||
public ResultQueueInternalState(int throttleLimit) {
|
||||
super();
|
||||
this.results = new ResultHolderResultQueue(throttleLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the result queue
|
||||
*/
|
||||
public ResultQueue<ResultHolder> getResultQueue() {
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
/**
|
||||
* An implementation of the {@link ResultQueue} that throttles the number of
|
||||
* expected results, limiting it to a maximum at any given time.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class ThrottleLimitResultQueue<T> implements ResultQueue<T> {
|
||||
|
||||
// Accumulation of result objects as they finish.
|
||||
private final BlockingQueue<T> results;
|
||||
|
||||
// Accumulation of dummy objects flagging expected results in the future.
|
||||
private final Semaphore waits;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
private volatile int count = 0;
|
||||
|
||||
/**
|
||||
* @param throttleLimit the maximum number of results that can be expected
|
||||
* at any given time.
|
||||
*/
|
||||
public ThrottleLimitResultQueue(int throttleLimit) {
|
||||
results = new LinkedBlockingQueue<T>();
|
||||
waits = new Semaphore(throttleLimit);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return results.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.support.ResultQueue#isExpecting()
|
||||
*/
|
||||
public boolean isExpecting() {
|
||||
// Base the decision about whether we expect more results on a
|
||||
// counter of the number of expected results actually collected.
|
||||
// Do not synchronize! Otherwise put and expect can deadlock.
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the queue to expect one more result. Blocks until a new result is
|
||||
* available if already expecting too many (as determined by the throttle
|
||||
* limit).
|
||||
*
|
||||
* @see ResultQueue#expect()
|
||||
*/
|
||||
public void expect() throws InterruptedException {
|
||||
synchronized (lock) {
|
||||
waits.acquire();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
public void put(T holder) throws IllegalArgumentException {
|
||||
if (!isExpecting()) {
|
||||
throw new IllegalArgumentException("Not expecting a result. Call expect() before put().");
|
||||
}
|
||||
// There should be no need to block here, or to use offer()
|
||||
results.add(holder);
|
||||
// Take from the waits queue now to allow another result to
|
||||
// accumulate. But don't decrement the counter.
|
||||
waits.release();
|
||||
}
|
||||
|
||||
public T take() throws NoSuchElementException, InterruptedException {
|
||||
if (!isExpecting()) {
|
||||
throw new NoSuchElementException("Not expecting a result. Call expect() before take().");
|
||||
}
|
||||
T value;
|
||||
synchronized (lock) {
|
||||
value = results.take();
|
||||
// Decrement the counter only when the result is collected.
|
||||
count--;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Infrastructure implementations of repeat support concerns.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.retry.support;
|
||||
|
||||
import org.springframework.repeat.RepeatOperations;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryOperations;
|
||||
@@ -52,7 +51,7 @@ public final class RetrySynchronizationManager {
|
||||
|
||||
/**
|
||||
* Method for registering a context - should only be used by
|
||||
* {@link RetryOperations} implementations to ensure that
|
||||
* {@link org.springframework.repeat.RepeatOperations} implementations to ensure that
|
||||
* {@link #getContext()} always returns the correct value.
|
||||
*
|
||||
* @param context the new context to register
|
||||
|
||||
@@ -23,7 +23,6 @@ import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.repeat.RepeatException;
|
||||
import org.springframework.retry.ExhaustedRetryException;
|
||||
import org.springframework.retry.RecoveryCallback;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
@@ -469,7 +468,7 @@ public class RetryTemplate implements RetryOperations {
|
||||
|
||||
/**
|
||||
* Re-throws the original throwable if it is unchecked, wraps checked
|
||||
* exceptions into {@link RepeatException}.
|
||||
* exceptions into {@link RetryException}.
|
||||
*/
|
||||
private static Exception wrapIfNecessary(Throwable throwable) {
|
||||
if (throwable instanceof Error) {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public abstract class AbstractExceptionTests extends TestCase {
|
||||
|
||||
public void testExceptionString() throws Exception {
|
||||
Exception exception = getException("foo");
|
||||
assertEquals("foo", exception.getMessage());
|
||||
}
|
||||
|
||||
public void testExceptionStringThrowable() throws Exception {
|
||||
Exception exception = getException("foo", new IllegalStateException());
|
||||
assertEquals("foo", exception.getMessage().substring(0, 3));
|
||||
}
|
||||
|
||||
public abstract Exception getException(String msg) throws Exception;
|
||||
|
||||
public abstract Exception getException(String msg, Throwable t) throws Exception;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat;
|
||||
|
||||
import org.springframework.repeat.RepeatException;
|
||||
|
||||
public class RepeatExceptionTests extends AbstractExceptionTests {
|
||||
|
||||
public Exception getException(String msg) throws Exception {
|
||||
return new RepeatException(msg);
|
||||
}
|
||||
|
||||
public Exception getException(String msg, Throwable t) throws Exception {
|
||||
return new RepeatException(msg, t);
|
||||
}
|
||||
|
||||
public void testNothing() throws Exception {
|
||||
// fool coverage tools...
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.callback;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.support.RepeatTemplate;
|
||||
|
||||
public class NestedRepeatCallbackTests extends TestCase {
|
||||
|
||||
int count = 0;
|
||||
|
||||
public void testExecute() throws Exception {
|
||||
NestedRepeatCallback callback = new NestedRepeatCallback(new RepeatTemplate(), new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
count++;
|
||||
return RepeatStatus.continueIf(count <= 1);
|
||||
}
|
||||
});
|
||||
RepeatStatus result = callback.doInIteration(null);
|
||||
assertEquals(2, count);
|
||||
assertFalse(result.isContinuable()); // False because processing has finished
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.context;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
|
||||
public class RepeatContextCounterTests extends TestCase {
|
||||
|
||||
RepeatContext parent = new RepeatContextSupport(null);
|
||||
RepeatContext context = new RepeatContextSupport(parent);
|
||||
|
||||
public void testAttributeCreated() {
|
||||
new RepeatContextCounter(context, "FOO");
|
||||
assertTrue(context.hasAttribute("FOO"));
|
||||
}
|
||||
|
||||
public void testAttributeCreatedWithNullParent() {
|
||||
new RepeatContextCounter(parent, "FOO", true);
|
||||
assertTrue(parent.hasAttribute("FOO"));
|
||||
}
|
||||
|
||||
public void testVanillaIncrement() throws Exception {
|
||||
RepeatContextCounter counter = new RepeatContextCounter(context, "FOO");
|
||||
assertEquals(0, counter.getCount());
|
||||
counter.increment(1);
|
||||
assertEquals(1, counter.getCount());
|
||||
counter.increment(2);
|
||||
assertEquals(3, counter.getCount());
|
||||
}
|
||||
|
||||
public void testAttributeCreatedInParent() throws Exception {
|
||||
new RepeatContextCounter(context, "FOO", true);
|
||||
assertFalse(context.hasAttribute("FOO"));
|
||||
assertTrue(parent.hasAttribute("FOO"));
|
||||
}
|
||||
|
||||
public void testParentIncrement() throws Exception {
|
||||
RepeatContextCounter counter = new RepeatContextCounter(context, "FOO", true);
|
||||
assertEquals(0, counter.getCount());
|
||||
counter.increment(1);
|
||||
// now get new context with same parent
|
||||
counter = new RepeatContextCounter(new RepeatContextSupport(parent), "FOO", true);
|
||||
assertEquals(1, counter.getCount());
|
||||
counter.increment(2);
|
||||
assertEquals(3, counter.getCount());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* @author dsyer
|
||||
*
|
||||
*/
|
||||
public class RepeatContextSupportTests extends TestCase {
|
||||
|
||||
private List<String> list = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.repeat.context.RepeatContextSupport#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
|
||||
*/
|
||||
public void testDestructionCallbackSunnyDay() throws Exception {
|
||||
RepeatContextSupport context = new RepeatContextSupport(null);
|
||||
context.setAttribute("foo", "FOO");
|
||||
context.registerDestructionCallback("foo", new Runnable() {
|
||||
public void run() {
|
||||
list.add("bar");
|
||||
}
|
||||
});
|
||||
context.close();
|
||||
assertEquals(1, list.size());
|
||||
assertEquals("bar", list.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.repeat.context.RepeatContextSupport#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
|
||||
*/
|
||||
public void testDestructionCallbackMissingAttribute() throws Exception {
|
||||
RepeatContextSupport context = new RepeatContextSupport(null);
|
||||
context.registerDestructionCallback("foo", new Runnable() {
|
||||
public void run() {
|
||||
list.add("bar");
|
||||
}
|
||||
});
|
||||
context.close();
|
||||
// No check for the attribute before executing callback
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link org.springframework.repeat.context.RepeatContextSupport#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
|
||||
*/
|
||||
public void testDestructionCallbackWithException() throws Exception {
|
||||
RepeatContextSupport context = new RepeatContextSupport(null);
|
||||
context.setAttribute("foo", "FOO");
|
||||
context.setAttribute("bar", "BAR");
|
||||
context.registerDestructionCallback("bar", new Runnable() {
|
||||
public void run() {
|
||||
list.add("spam");
|
||||
throw new RuntimeException("fail!");
|
||||
}
|
||||
});
|
||||
context.registerDestructionCallback("foo", new Runnable() {
|
||||
public void run() {
|
||||
list.add("bar");
|
||||
throw new RuntimeException("fail!");
|
||||
}
|
||||
});
|
||||
try {
|
||||
context.close();
|
||||
fail("Expected RuntimeException");
|
||||
} catch (RuntimeException e) {
|
||||
// We don't care which one was thrown...
|
||||
assertEquals("fail!", e.getMessage());
|
||||
}
|
||||
// ...but we do care that both were executed:
|
||||
assertEquals(2, list.size());
|
||||
assertTrue(list.contains("bar"));
|
||||
assertTrue(list.contains("spam"));
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.context;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.core.AttributeAccessorSupport;
|
||||
|
||||
public class SynchronizedAttributeAccessorTests extends TestCase {
|
||||
|
||||
SynchronizedAttributeAccessor accessor = new SynchronizedAttributeAccessor();
|
||||
|
||||
public void testHashCode() {
|
||||
SynchronizedAttributeAccessor another = new SynchronizedAttributeAccessor();
|
||||
accessor.setAttribute("foo", "bar");
|
||||
another.setAttribute("foo", "bar");
|
||||
assertEquals(accessor, another);
|
||||
assertEquals("Object.hashCode() contract broken", accessor.hashCode(), another.hashCode());
|
||||
}
|
||||
|
||||
public void testToStringWithNoAttributes() throws Exception {
|
||||
assertNotNull(accessor.toString());
|
||||
}
|
||||
|
||||
public void testToStringWithAttributes() throws Exception {
|
||||
accessor.setAttribute("foo", "bar");
|
||||
accessor.setAttribute("spam", "bucket");
|
||||
assertNotNull(accessor.toString());
|
||||
}
|
||||
|
||||
public void testAttributeNames() {
|
||||
accessor.setAttribute("foo", "bar");
|
||||
accessor.setAttribute("spam", "bucket");
|
||||
List<String> list = Arrays.asList(accessor.attributeNames());
|
||||
assertEquals(2, list.size());
|
||||
assertTrue(list.contains("foo"));
|
||||
}
|
||||
|
||||
public void testEqualsSameType() {
|
||||
SynchronizedAttributeAccessor another = new SynchronizedAttributeAccessor();
|
||||
accessor.setAttribute("foo", "bar");
|
||||
another.setAttribute("foo", "bar");
|
||||
assertEquals(accessor, another);
|
||||
}
|
||||
|
||||
public void testEqualsSelf() {
|
||||
accessor.setAttribute("foo", "bar");
|
||||
assertEquals(accessor, accessor);
|
||||
}
|
||||
|
||||
public void testEqualsWrongType() {
|
||||
accessor.setAttribute("foo", "bar");
|
||||
Map<String, String> another = Collections.singletonMap("foo", "bar");
|
||||
// Accessor and another are instances of unrelated classes, they should
|
||||
// never be equal...
|
||||
assertFalse(accessor.equals(another));
|
||||
}
|
||||
|
||||
public void testEqualsSupport() {
|
||||
AttributeAccessorSupport another = new AttributeAccessorSupport() {
|
||||
};
|
||||
accessor.setAttribute("foo", "bar");
|
||||
another.setAttribute("foo", "bar");
|
||||
assertEquals(accessor, another);
|
||||
}
|
||||
|
||||
public void testGetAttribute() {
|
||||
accessor.setAttribute("foo", "bar");
|
||||
assertEquals("bar", accessor.getAttribute("foo"));
|
||||
}
|
||||
|
||||
public void testSetAttributeIfAbsentWhenAlreadyPresent() {
|
||||
accessor.setAttribute("foo", "bar");
|
||||
assertEquals("bar", accessor.setAttributeIfAbsent("foo", "spam"));
|
||||
}
|
||||
|
||||
public void testSetAttributeIfAbsentWhenNotAlreadyPresent() {
|
||||
assertEquals(null, accessor.setAttributeIfAbsent("foo", "bar"));
|
||||
assertEquals("bar", accessor.getAttribute("foo"));
|
||||
}
|
||||
|
||||
public void testHasAttribute() {
|
||||
accessor.setAttribute("foo", "bar");
|
||||
assertEquals(true, accessor.hasAttribute("foo"));
|
||||
}
|
||||
|
||||
public void testRemoveAttribute() {
|
||||
accessor.setAttribute("foo", "bar");
|
||||
assertEquals("bar", accessor.getAttribute("foo"));
|
||||
accessor.removeAttribute("foo");
|
||||
assertEquals(null, accessor.getAttribute("foo"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.exception.CompositeExceptionHandler;
|
||||
import org.springframework.repeat.exception.ExceptionHandler;
|
||||
|
||||
public class CompositeExceptionHandlerTests extends TestCase {
|
||||
|
||||
private CompositeExceptionHandler handler = new CompositeExceptionHandler();
|
||||
|
||||
public void testNewHandler() throws Throwable {
|
||||
try {
|
||||
handler.handleException(null, new RuntimeException());
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
fail("Unexpected RuntimeException");
|
||||
}
|
||||
}
|
||||
|
||||
public void testDelegation() throws Throwable {
|
||||
final List<String> list = new ArrayList<String>();
|
||||
handler.setHandlers(new ExceptionHandler[] {
|
||||
new ExceptionHandler() {
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
|
||||
list.add("1");
|
||||
}
|
||||
},
|
||||
new ExceptionHandler() {
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
|
||||
list.add("2");
|
||||
}
|
||||
}
|
||||
});
|
||||
handler.handleException(null, new RuntimeException());
|
||||
assertEquals(2, list.size());
|
||||
assertEquals("1", list.get(0));
|
||||
assertEquals("2", list.get(1));
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
|
||||
public class DefaultExceptionHandlerTests extends TestCase {
|
||||
|
||||
private DefaultExceptionHandler handler = new DefaultExceptionHandler();
|
||||
private RepeatContext context = null;
|
||||
|
||||
public void testRuntimeException() throws Throwable {
|
||||
try {
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
fail("Expected RuntimeException");
|
||||
} catch (RuntimeException e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testError() throws Throwable {
|
||||
try {
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
fail("Expected Error");
|
||||
} catch (Error e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.SimpleLayout;
|
||||
import org.apache.log4j.WriterAppender;
|
||||
import org.springframework.classify.ClassifierSupport;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.exception.LogOrRethrowExceptionHandler.Level;
|
||||
|
||||
public class LogOrRethrowExceptionHandlerTests extends TestCase {
|
||||
|
||||
private LogOrRethrowExceptionHandler handler = new LogOrRethrowExceptionHandler();
|
||||
|
||||
private StringWriter writer;
|
||||
|
||||
private RepeatContext context = null;
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
Logger logger = Logger.getLogger(LogOrRethrowExceptionHandler.class);
|
||||
logger.setLevel(org.apache.log4j.Level.DEBUG);
|
||||
writer = new StringWriter();
|
||||
logger.removeAllAppenders();
|
||||
logger.getParent().removeAllAppenders();
|
||||
logger.addAppender(new WriterAppender(new SimpleLayout(), writer));
|
||||
}
|
||||
|
||||
public void testRuntimeException() throws Throwable {
|
||||
try {
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testError() throws Throwable {
|
||||
try {
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
fail("Expected Error");
|
||||
}
|
||||
catch (Error e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testNotRethrownErrorLevel() throws Throwable {
|
||||
handler.setExceptionClassifier(new ClassifierSupport<Throwable,Level>(Level.RETHROW) {
|
||||
public Level classify(Throwable throwable) {
|
||||
return Level.ERROR;
|
||||
}
|
||||
});
|
||||
// No exception...
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
assertNotNull(writer.toString());
|
||||
}
|
||||
|
||||
public void testNotRethrownWarnLevel() throws Throwable {
|
||||
handler.setExceptionClassifier(new ClassifierSupport<Throwable,Level>(Level.RETHROW) {
|
||||
public Level classify(Throwable throwable) {
|
||||
return Level.WARN;
|
||||
}
|
||||
});
|
||||
// No exception...
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
assertNotNull(writer.toString());
|
||||
}
|
||||
|
||||
public void testNotRethrownDebugLevel() throws Throwable {
|
||||
handler.setExceptionClassifier(new ClassifierSupport<Throwable,Level>(Level.RETHROW) {
|
||||
public Level classify(Throwable throwable) {
|
||||
return Level.DEBUG;
|
||||
}
|
||||
});
|
||||
// No exception...
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
assertNotNull(writer.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
|
||||
public class RethrowOnThresholdExceptionHandlerTests {
|
||||
|
||||
private RethrowOnThresholdExceptionHandler handler = new RethrowOnThresholdExceptionHandler();
|
||||
|
||||
private RepeatContext parent = new RepeatContextSupport(null);
|
||||
|
||||
private RepeatContext context = new RepeatContextSupport(parent);
|
||||
|
||||
@Test
|
||||
public void testRuntimeException() throws Throwable {
|
||||
try {
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testError() throws Throwable {
|
||||
try {
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
fail("Expected Error");
|
||||
}
|
||||
catch (Error e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotRethrownWithThreshold() throws Throwable {
|
||||
handler.setThresholds(Collections.<Class<? extends Throwable>, Integer> singletonMap(Exception.class, 1));
|
||||
// No exception...
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
AtomicInteger counter = (AtomicInteger) context.getAttribute(context.attributeNames()[0]);
|
||||
assertNotNull(counter);
|
||||
assertEquals(1, counter.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRethrowOnThreshold() throws Throwable {
|
||||
handler.setThresholds(Collections.<Class<? extends Throwable>, Integer> singletonMap(Exception.class, 2));
|
||||
// No exception...
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
try {
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotUseParent() throws Throwable {
|
||||
handler.setThresholds(Collections.<Class<? extends Throwable>, Integer> singletonMap(Exception.class, 1));
|
||||
// No exception...
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
context = new RepeatContextSupport(parent);
|
||||
try {
|
||||
// No exception again - context is changed...
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
fail("Unexpected Error");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseParent() throws Throwable {
|
||||
handler.setThresholds(Collections.<Class<? extends Throwable>, Integer> singletonMap(Exception.class, 1));
|
||||
handler.setUseParent(true);
|
||||
// No exception...
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
context = new RepeatContextSupport(parent);
|
||||
try {
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
fail("Expected Error");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.exception;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SimpleLimitExceptionHandler}
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class SimpleLimitExceptionHandlerTests {
|
||||
|
||||
// object under test
|
||||
private SimpleLimitExceptionHandler handler = new SimpleLimitExceptionHandler();
|
||||
|
||||
@Before
|
||||
public void initializeHandler() throws Exception {
|
||||
handler.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeWithNullContext() throws Throwable {
|
||||
try {
|
||||
handler.handleException(null, new RuntimeException("foo"));
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeWithNullContextAndNullException() throws Throwable {
|
||||
try {
|
||||
handler.handleException(null, null);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultBehaviour() throws Throwable {
|
||||
Throwable throwable = new RuntimeException("foo");
|
||||
try {
|
||||
handler.handleException(new RepeatContextSupport(null), throwable);
|
||||
fail("Exception was swallowed.");
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertTrue("Exception is rethrown, ignoring the exception limit", true);
|
||||
assertSame(expected, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Other than nominated exception type should be rethrown, ignoring the
|
||||
* exception limit.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testNormalExceptionThrown() throws Throwable {
|
||||
Throwable throwable = new RuntimeException("foo");
|
||||
|
||||
final int MORE_THAN_ZERO = 1;
|
||||
handler.setLimit(MORE_THAN_ZERO);
|
||||
handler.setExceptionClasses(Collections.<Class<? extends Throwable>> singleton(IllegalArgumentException.class));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
try {
|
||||
handler.handleException(new RepeatContextSupport(null), throwable);
|
||||
fail("Exception was swallowed.");
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertTrue("Exception is rethrown, ignoring the exception limit", true);
|
||||
assertSame(expected, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TransactionInvalidException should only be rethrown below the exception
|
||||
* limit.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testLimitedExceptionTypeNotThrown() throws Throwable {
|
||||
final int MORE_THAN_ZERO = 1;
|
||||
handler.setLimit(MORE_THAN_ZERO);
|
||||
handler.setExceptionClasses(Collections.<Class<? extends Throwable>> singleton(RuntimeException.class));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
try {
|
||||
handler.handleException(new RepeatContextSupport(null), new RuntimeException("foo"));
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
fail("Unexpected exception.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TransactionInvalidException should only be rethrown below the exception
|
||||
* limit.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testLimitedExceptionNotThrownFromSiblings() throws Throwable {
|
||||
Throwable throwable = new RuntimeException("foo");
|
||||
|
||||
final int MORE_THAN_ZERO = 1;
|
||||
handler.setLimit(MORE_THAN_ZERO);
|
||||
handler.setExceptionClasses(Collections.<Class<? extends Throwable>> singleton(RuntimeException.class));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
RepeatContextSupport parent = new RepeatContextSupport(null);
|
||||
|
||||
try {
|
||||
RepeatContextSupport context = new RepeatContextSupport(parent);
|
||||
handler.handleException(context, throwable);
|
||||
context = new RepeatContextSupport(parent);
|
||||
handler.handleException(context, throwable);
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
fail("Unexpected exception.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TransactionInvalidException should only be rethrown below the exception
|
||||
* limit.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testLimitedExceptionThrownFromSiblingsWhenUsingParent() throws Throwable {
|
||||
Throwable throwable = new RuntimeException("foo");
|
||||
|
||||
final int MORE_THAN_ZERO = 1;
|
||||
handler.setLimit(MORE_THAN_ZERO);
|
||||
handler.setExceptionClasses(Collections.<Class<? extends Throwable>> singleton(RuntimeException.class));
|
||||
handler.setUseParent(true);
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
RepeatContextSupport parent = new RepeatContextSupport(null);
|
||||
|
||||
try {
|
||||
RepeatContextSupport context = new RepeatContextSupport(parent);
|
||||
handler.handleException(context, throwable);
|
||||
context = new RepeatContextSupport(parent);
|
||||
handler.handleException(context, throwable);
|
||||
fail("Expected exception.");
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertSame(throwable, expected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exceptions are swallowed until the exception limit is exceeded. After the
|
||||
* limit is exceeded exceptions are rethrown
|
||||
*/
|
||||
@Test
|
||||
public void testExceptionNotThrownBelowLimit() throws Throwable {
|
||||
|
||||
final int EXCEPTION_LIMIT = 3;
|
||||
handler.setLimit(EXCEPTION_LIMIT);
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
List<Throwable> throwables = new ArrayList<Throwable>() {
|
||||
{
|
||||
for (int i = 0; i < (EXCEPTION_LIMIT); i++) {
|
||||
add(new RuntimeException("below exception limit"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RepeatContextSupport context = new RepeatContextSupport(null);
|
||||
|
||||
try {
|
||||
for (Throwable throwable : throwables) {
|
||||
|
||||
handler.handleException(context, throwable);
|
||||
assertTrue("exceptions up to limit are swallowed", true);
|
||||
|
||||
}
|
||||
}
|
||||
catch (RuntimeException unexpected) {
|
||||
fail("exception rethrown although exception limit was not exceeded");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* TransactionInvalidExceptions are swallowed until the exception limit is
|
||||
* exceeded. After the limit is exceeded exceptions are rethrown as
|
||||
* BatchCriticalExceptions
|
||||
*/
|
||||
@Test
|
||||
public void testExceptionThrownAboveLimit() throws Throwable {
|
||||
|
||||
final int EXCEPTION_LIMIT = 3;
|
||||
handler.setLimit(EXCEPTION_LIMIT);
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
List<Throwable> throwables = new ArrayList<Throwable>() {
|
||||
{
|
||||
for (int i = 0; i < (EXCEPTION_LIMIT); i++) {
|
||||
add(new RuntimeException("below exception limit"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
throwables.add(new RuntimeException("above exception limit"));
|
||||
|
||||
RepeatContextSupport context = new RepeatContextSupport(null);
|
||||
|
||||
try {
|
||||
for (Throwable throwable : throwables) {
|
||||
|
||||
handler.handleException(context, throwable);
|
||||
assertTrue("exceptions up to limit are swallowed", true);
|
||||
|
||||
}
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertEquals("above exception limit", expected.getMessage());
|
||||
}
|
||||
|
||||
// after reaching the limit, behaviour should be idempotent
|
||||
try {
|
||||
handler.handleException(context, new RuntimeException("foo"));
|
||||
assertTrue("exceptions up to limit are swallowed", true);
|
||||
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertEquals("foo", expected.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.interceptor;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatException;
|
||||
import org.springframework.repeat.RepeatOperations;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.policy.SimpleCompletionPolicy;
|
||||
import org.springframework.repeat.support.RepeatTemplate;
|
||||
|
||||
public class RepeatOperationsInterceptorTests extends TestCase {
|
||||
|
||||
private RepeatOperationsInterceptor interceptor;
|
||||
|
||||
private Service service;
|
||||
|
||||
private ServiceImpl target;
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
interceptor = new RepeatOperationsInterceptor();
|
||||
target = new ServiceImpl();
|
||||
ProxyFactory factory = new ProxyFactory(RepeatOperations.class.getClassLoader());
|
||||
factory.setInterfaces(new Class[] { Service.class });
|
||||
factory.setTarget(target);
|
||||
service = (Service) factory.getProxy();
|
||||
}
|
||||
|
||||
public void testDefaultInterceptorSunnyDay() throws Exception {
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
service.service();
|
||||
assertEquals(3, target.count);
|
||||
}
|
||||
|
||||
public void testCompleteOnFirstInvocation() throws Exception {
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
target.setMaxService(0);
|
||||
service.service();
|
||||
assertEquals(1, target.count);
|
||||
}
|
||||
|
||||
public void testSetTemplate() throws Exception {
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
interceptor.setRepeatOperations(new RepeatOperations() {
|
||||
public RepeatStatus iterate(RepeatCallback callback) {
|
||||
try {
|
||||
Object result = callback.doInIteration(null);
|
||||
calls.add(result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RepeatException("Encountered exception in repeat.", e);
|
||||
}
|
||||
return RepeatStatus.CONTINUABLE;
|
||||
}
|
||||
});
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
service.service();
|
||||
assertEquals(1, calls.size());
|
||||
}
|
||||
|
||||
public void testCallbackNotExecuted() throws Exception {
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
interceptor.setRepeatOperations(new RepeatOperations() {
|
||||
public RepeatStatus iterate(RepeatCallback callback) {
|
||||
calls.add(null);
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
});
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
try {
|
||||
service.service();
|
||||
fail("Expected IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
String message = e.getMessage();
|
||||
assertTrue("Wrong exception message: "+message, message.toLowerCase().indexOf("no result available")>=0);
|
||||
}
|
||||
assertEquals(1, calls.size());
|
||||
}
|
||||
|
||||
public void testVoidServiceSunnyDay() throws Exception {
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
// N.B. the default completion policy results in an infinite loop, so we
|
||||
// need to set the chunk size.
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
interceptor.setRepeatOperations(template);
|
||||
service.alternate();
|
||||
assertEquals(2, target.count);
|
||||
}
|
||||
|
||||
public void testCallbackWithException() throws Exception {
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
try {
|
||||
service.exception();
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Duh", e.getMessage().substring(0, 3));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCallbackWithThrowable() throws Exception {
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
try {
|
||||
service.error();
|
||||
fail("Expected Error");
|
||||
}
|
||||
catch (Error e) {
|
||||
assertEquals("Duh", e.getMessage().substring(0, 3));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCallbackWithBoolean() throws Exception {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
// N.B. the default completion policy results in an infinite loop, so we
|
||||
// need to set the chunk size.
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
interceptor.setRepeatOperations(template);
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
assertTrue(service.isContinuable());
|
||||
assertEquals(2, target.count);
|
||||
}
|
||||
|
||||
public void testCallbackWithBooleanReturningFalseFirstTime() throws Exception {
|
||||
target.setComplete(true);
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
// Complete without repeat when boolean return value is false
|
||||
assertFalse(service.isContinuable());
|
||||
assertEquals(1, target.count);
|
||||
}
|
||||
|
||||
public void testInterceptorChainWithRetry() throws Exception {
|
||||
((Advised) service).addAdvice(interceptor);
|
||||
final List<Object> list = new ArrayList<Object>();
|
||||
((Advised) service).addAdvice(new MethodInterceptor() {
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
list.add("chain");
|
||||
return invocation.proceed();
|
||||
}
|
||||
});
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
interceptor.setRepeatOperations(template);
|
||||
service.service();
|
||||
assertEquals(2, target.count);
|
||||
assertEquals(2, list.size());
|
||||
}
|
||||
|
||||
public void testIllegalMethodInvocationType() throws Throwable {
|
||||
try {
|
||||
interceptor.invoke(new MethodInvocation() {
|
||||
public Method getMethod() {
|
||||
try {
|
||||
return Object.class.getMethod("toString", new Class[0]);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Object[] getArguments() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public AccessibleObject getStaticPart() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object getThis() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object proceed() throws Throwable {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
fail("IllegalStateException expected");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
assertTrue("Exception message should contain MethodInvocation: " + e.getMessage(), e.getMessage().indexOf(
|
||||
"MethodInvocation") >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
private interface Service {
|
||||
Object service() throws Exception;
|
||||
|
||||
void alternate() throws Exception;
|
||||
|
||||
Object exception() throws Exception;
|
||||
|
||||
Object error() throws Exception;
|
||||
|
||||
boolean isContinuable() throws Exception;
|
||||
}
|
||||
|
||||
private static class ServiceImpl implements Service {
|
||||
private int count = 0;
|
||||
|
||||
private boolean complete;
|
||||
|
||||
private int maxService = 2;
|
||||
|
||||
/**
|
||||
* Public setter for the maximum number of times to call service().
|
||||
* @param maxService the maxService to set
|
||||
*/
|
||||
public void setMaxService(int maxService) {
|
||||
this.maxService = maxService;
|
||||
}
|
||||
|
||||
public Object service() throws Exception {
|
||||
count++;
|
||||
if (count <= maxService) {
|
||||
return Integer.valueOf(count);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setComplete(boolean complete) {
|
||||
this.complete = complete;
|
||||
}
|
||||
|
||||
public void alternate() throws Exception {
|
||||
count++;
|
||||
}
|
||||
|
||||
public Object exception() throws Exception {
|
||||
throw new RuntimeException("Duh! Stupid.");
|
||||
}
|
||||
|
||||
public Object error() throws Exception {
|
||||
throw new Error("Duh! Stupid error.");
|
||||
}
|
||||
|
||||
public boolean isContinuable() throws Exception {
|
||||
count++;
|
||||
return !complete;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatListener;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class CompositeRepeatListenerTests extends TestCase {
|
||||
|
||||
private CompositeRepeatListener listener = new CompositeRepeatListener();
|
||||
private RepeatContext context = new RepeatContextSupport(null);
|
||||
|
||||
private List<Object> list = new ArrayList<Object>();
|
||||
|
||||
/**
|
||||
* Test method for {@link CompositeRepeatListener#setListeners(RepeatListener[])}.
|
||||
*/
|
||||
public void testSetListeners() {
|
||||
listener.setListeners(new RepeatListener[] { new RepeatListenerSupport() {
|
||||
public void open(RepeatContext context) {
|
||||
list.add("fail");
|
||||
}
|
||||
}, new RepeatListenerSupport() {
|
||||
public void open(RepeatContext context) {
|
||||
list.add("continue");
|
||||
}
|
||||
} });
|
||||
listener.open(context);
|
||||
assertEquals(2, list.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for
|
||||
* {@link CompositeRepeatListener#register(RepeatListener)}.
|
||||
*/
|
||||
public void testSetListener() {
|
||||
listener.register(new RepeatListenerSupport() {
|
||||
public void before(RepeatContext context) {
|
||||
list.add("fail");
|
||||
}
|
||||
});
|
||||
listener.before(context);
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
|
||||
public void testClose() {
|
||||
listener.register(new RepeatListenerSupport() {
|
||||
public void close(RepeatContext context) {
|
||||
list.add("foo");
|
||||
}
|
||||
});
|
||||
listener.close(context);
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
|
||||
public void testOnError() {
|
||||
listener.register(new RepeatListenerSupport() {
|
||||
public void onError(RepeatContext context, Throwable e) {
|
||||
list.add(e);
|
||||
}
|
||||
});
|
||||
listener.onError(context, new RuntimeException("foo"));
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatListener;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.support.RepeatTemplate;
|
||||
import org.springframework.repeat.support.TaskExecutorRepeatTemplate;
|
||||
|
||||
public class RepeatListenerTests extends TestCase {
|
||||
|
||||
int count = 0;
|
||||
|
||||
public void testBeforeInterceptors() throws Exception {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
template.setListeners(new RepeatListener[] { new RepeatListenerSupport() {
|
||||
public void before(RepeatContext context) {
|
||||
calls.add("1");
|
||||
}
|
||||
}, new RepeatListenerSupport() {
|
||||
public void before(RepeatContext context) {
|
||||
calls.add("2");
|
||||
}
|
||||
} });
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
count++;
|
||||
return RepeatStatus.continueIf(count <= 1);
|
||||
}
|
||||
});
|
||||
// 2 calls: the second time there is no processing
|
||||
// (despite the fact that the callback returned null and batch was
|
||||
// complete). Is this OK?
|
||||
assertEquals(2, count);
|
||||
// ... but the interceptor before() was called:
|
||||
assertEquals("[1, 2, 1, 2]", calls.toString());
|
||||
}
|
||||
|
||||
public void testBeforeInterceptorCanVeto() throws Exception {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
template.registerListener(new RepeatListenerSupport() {
|
||||
public void before(RepeatContext context) {
|
||||
calls.add("1");
|
||||
context.setCompleteOnly();
|
||||
}
|
||||
});
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
count++;
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
});
|
||||
assertEquals(0, count);
|
||||
// ... but the interceptor before() was called:
|
||||
assertEquals("[1]", calls.toString());
|
||||
}
|
||||
|
||||
public void testAfterInterceptors() throws Exception {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
template.setListeners(new RepeatListener[] { new RepeatListenerSupport() {
|
||||
public void after(RepeatContext context, RepeatStatus result) {
|
||||
calls.add("1");
|
||||
}
|
||||
}, new RepeatListenerSupport() {
|
||||
public void after(RepeatContext context, RepeatStatus result) {
|
||||
calls.add("2");
|
||||
}
|
||||
} });
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
count++;
|
||||
return RepeatStatus.continueIf(count <= 1);
|
||||
}
|
||||
});
|
||||
// 2 calls to the callback, and the second one had no processing...
|
||||
assertEquals(2, count);
|
||||
// ... so the interceptor after() is not called:
|
||||
assertEquals("[2, 1]", calls.toString());
|
||||
}
|
||||
|
||||
public void testOpenInterceptors() throws Exception {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
template.setListeners(new RepeatListener[] { new RepeatListenerSupport() {
|
||||
public void open(RepeatContext context) {
|
||||
calls.add("1");
|
||||
}
|
||||
}, new RepeatListenerSupport() {
|
||||
public void open(RepeatContext context) {
|
||||
calls.add("2");
|
||||
context.setCompleteOnly();
|
||||
}
|
||||
} });
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
count++;
|
||||
return RepeatStatus.CONTINUABLE;
|
||||
}
|
||||
});
|
||||
assertEquals(0, count);
|
||||
assertEquals("[1, 2]", calls.toString());
|
||||
}
|
||||
|
||||
public void testSingleOpenInterceptor() throws Exception {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
template.registerListener(new RepeatListenerSupport() {
|
||||
public void open(RepeatContext context) {
|
||||
calls.add("1");
|
||||
}
|
||||
});
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
count++;
|
||||
context.setCompleteOnly();
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
});
|
||||
assertEquals(1, count);
|
||||
assertEquals("[1]", calls.toString());
|
||||
}
|
||||
|
||||
public void testCloseInterceptors() throws Exception {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
template.setListeners(new RepeatListener[] { new RepeatListenerSupport() {
|
||||
public void close(RepeatContext context) {
|
||||
calls.add("1");
|
||||
}
|
||||
}, new RepeatListenerSupport() {
|
||||
public void close(RepeatContext context) {
|
||||
calls.add("2");
|
||||
}
|
||||
} });
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
count++;
|
||||
return RepeatStatus.continueIf(count < 2);
|
||||
}
|
||||
});
|
||||
// Test that more than one call comes in to the callback...
|
||||
assertEquals(2, count);
|
||||
// ... but the interceptor is only called once.
|
||||
assertEquals("[2, 1]", calls.toString());
|
||||
}
|
||||
|
||||
|
||||
public void testOnErrorInterceptors() throws Exception {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
template.setListeners(new RepeatListener[] { new RepeatListenerSupport() {
|
||||
public void onError(RepeatContext context, Throwable t) {
|
||||
calls.add("1");
|
||||
}
|
||||
}, new RepeatListenerSupport() {
|
||||
public void onError(RepeatContext context, Throwable t) {
|
||||
calls.add("2");
|
||||
}
|
||||
} });
|
||||
try {
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
throw new IllegalStateException("Bogus");
|
||||
}
|
||||
});
|
||||
fail("Expected IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
assertEquals(0, count);
|
||||
assertEquals("[2, 1]", calls.toString());
|
||||
}
|
||||
|
||||
public void testOnErrorInterceptorsPrecedence() throws Exception {
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
template.setListeners(new RepeatListener[] { new RepeatListenerSupport() {
|
||||
public void after(RepeatContext context, RepeatStatus result) {
|
||||
calls.add("1");
|
||||
}
|
||||
}, new RepeatListenerSupport() {
|
||||
public void onError(RepeatContext context, Throwable t) {
|
||||
calls.add("2");
|
||||
}
|
||||
} });
|
||||
try {
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
throw new IllegalStateException("Bogus");
|
||||
}
|
||||
});
|
||||
fail("Expected IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
assertEquals(0, count);
|
||||
// The after is not executed, if there is an error...
|
||||
assertEquals("[2]", calls.toString());
|
||||
}
|
||||
|
||||
public void testAsynchronousOnErrorInterceptorsPrecedence() throws Exception {
|
||||
TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate();
|
||||
template.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
final List<Object> calls = new ArrayList<Object>();
|
||||
final List<Object> fails = new ArrayList<Object>();
|
||||
template.setListeners(new RepeatListener[] { new RepeatListenerSupport() {
|
||||
public void after(RepeatContext context, RepeatStatus result) {
|
||||
calls.add("1");
|
||||
}
|
||||
}, new RepeatListenerSupport() {
|
||||
public void onError(RepeatContext context, Throwable t) {
|
||||
calls.add("2");
|
||||
fails.add("2");
|
||||
}
|
||||
} });
|
||||
try {
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
throw new IllegalStateException("Bogus");
|
||||
}
|
||||
});
|
||||
fail("Expected IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// expected
|
||||
assertEquals("Bogus", e.getMessage());
|
||||
}
|
||||
assertEquals(0, count);
|
||||
System.err.println(calls);
|
||||
// The after is not executed on error...
|
||||
assertEquals("2", calls.get(0));
|
||||
assertEquals("2", calls.get(calls.size()-1));
|
||||
assertFalse(calls.contains("1"));
|
||||
assertEquals(fails.size(), calls.size());
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.repeat.CompletionPolicy;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
public class CompositeCompletionPolicyTests extends TestCase {
|
||||
|
||||
public void testEmptyPolicies() throws Exception {
|
||||
CompositeCompletionPolicy policy = new CompositeCompletionPolicy();
|
||||
RepeatContext context = policy.start(null);
|
||||
assertNotNull(context);
|
||||
assertFalse(policy.isComplete(context));
|
||||
}
|
||||
|
||||
public void testTrivialPolicies() throws Exception {
|
||||
CompositeCompletionPolicy policy = new CompositeCompletionPolicy();
|
||||
policy.setPolicies(new CompletionPolicy[] { new MockCompletionPolicySupport(),
|
||||
new MockCompletionPolicySupport() });
|
||||
RepeatContext context = policy.start(null);
|
||||
assertEquals(0, context.getStartedCount());
|
||||
assertFalse(policy.isComplete(context));
|
||||
assertFalse(policy.isComplete(context, null));
|
||||
policy.update(context);
|
||||
assertEquals(1, context.getStartedCount());
|
||||
}
|
||||
|
||||
public void testNonTrivialPolicies() throws Exception {
|
||||
CompositeCompletionPolicy policy = new CompositeCompletionPolicy();
|
||||
policy.setPolicies(new CompletionPolicy[] { new MockCompletionPolicySupport(),
|
||||
new MockCompletionPolicySupport() {
|
||||
public boolean isComplete(RepeatContext context) {
|
||||
return true;
|
||||
}
|
||||
} });
|
||||
RepeatContext context = policy.start(null);
|
||||
assertTrue(policy.isComplete(context));
|
||||
}
|
||||
|
||||
public void testNonTrivialPoliciesWithResult() throws Exception {
|
||||
CompositeCompletionPolicy policy = new CompositeCompletionPolicy();
|
||||
policy.setPolicies(new CompletionPolicy[] { new MockCompletionPolicySupport(),
|
||||
new MockCompletionPolicySupport() {
|
||||
public boolean isComplete(RepeatContext context, RepeatStatus result) {
|
||||
return true;
|
||||
}
|
||||
} });
|
||||
RepeatContext context = policy.start(null);
|
||||
assertTrue(policy.isComplete(context, null));
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
|
||||
public class CountingCompletionPolicyTests extends TestCase {
|
||||
|
||||
public void testDefaultBehaviour() throws Exception {
|
||||
CountingCompletionPolicy policy = new CountingCompletionPolicy() {
|
||||
protected int getCount(RepeatContext context) {
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
RepeatContext context = policy.start(null);
|
||||
assertTrue(policy.isComplete(context));
|
||||
}
|
||||
|
||||
public void testNullResult() throws Exception {
|
||||
CountingCompletionPolicy policy = new CountingCompletionPolicy() {
|
||||
protected int getCount(RepeatContext context) {
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
policy.setMaxCount(10);
|
||||
RepeatContext context = policy.start(null);
|
||||
assertTrue(policy.isComplete(context, null));
|
||||
}
|
||||
|
||||
public void testFinishedResult() throws Exception {
|
||||
CountingCompletionPolicy policy = new CountingCompletionPolicy() {
|
||||
protected int getCount(RepeatContext context) {
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
policy.setMaxCount(10);
|
||||
RepeatContext context = policy.start(null);
|
||||
assertTrue(policy.isComplete(context, RepeatStatus.FINISHED));
|
||||
}
|
||||
|
||||
public void testDefaultBehaviourWithUpdate() throws Exception {
|
||||
CountingCompletionPolicy policy = new CountingCompletionPolicy() {
|
||||
int count = 0;
|
||||
|
||||
protected int getCount(RepeatContext context) {
|
||||
return count;
|
||||
};
|
||||
|
||||
protected int doUpdate(RepeatContext context) {
|
||||
count++;
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
policy.setMaxCount(2);
|
||||
RepeatContext context = policy.start(null);
|
||||
policy.update(context);
|
||||
assertFalse(policy.isComplete(context));
|
||||
policy.update(context);
|
||||
assertTrue(policy.isComplete(context));
|
||||
}
|
||||
|
||||
public void testUpdateNotSavedAcrossSession() throws Exception {
|
||||
CountingCompletionPolicy policy = new CountingCompletionPolicy() {
|
||||
int count = 0;
|
||||
|
||||
protected int getCount(RepeatContext context) {
|
||||
return count;
|
||||
};
|
||||
|
||||
protected int doUpdate(RepeatContext context) {
|
||||
super.doUpdate(context);
|
||||
count++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
public RepeatContext start(RepeatContext context) {
|
||||
count = 0;
|
||||
return super.start(context);
|
||||
}
|
||||
};
|
||||
policy.setMaxCount(2);
|
||||
RepeatContextSupport session = new RepeatContextSupport(null);
|
||||
RepeatContext context = policy.start(session);
|
||||
policy.update(context);
|
||||
assertFalse(policy.isComplete(context));
|
||||
context = policy.start(session);
|
||||
policy.update(context);
|
||||
assertFalse(policy.isComplete(context));
|
||||
}
|
||||
|
||||
public void testUpdateSavedAcrossSession() throws Exception {
|
||||
CountingCompletionPolicy policy = new CountingCompletionPolicy() {
|
||||
int count = 0;
|
||||
|
||||
protected int getCount(RepeatContext context) {
|
||||
return count;
|
||||
};
|
||||
|
||||
protected int doUpdate(RepeatContext context) {
|
||||
super.doUpdate(context);
|
||||
count++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
public RepeatContext start(RepeatContext context) {
|
||||
count = 0;
|
||||
return super.start(context);
|
||||
}
|
||||
};
|
||||
policy.setMaxCount(2);
|
||||
policy.setUseParent(true);
|
||||
RepeatContextSupport session = new RepeatContextSupport(null);
|
||||
RepeatContext context = policy.start(session);
|
||||
policy.update(context);
|
||||
assertFalse(policy.isComplete(context));
|
||||
context = policy.start(session);
|
||||
policy.update(context);
|
||||
assertTrue(policy.isComplete(context));
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
|
||||
public class MockCompletionPolicySupport extends CompletionPolicySupport {
|
||||
|
||||
public boolean isComplete(RepeatContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
public class SimpleCompletionPolicyTests extends TestCase {
|
||||
|
||||
SimpleCompletionPolicy policy = new SimpleCompletionPolicy();
|
||||
|
||||
RepeatContext context;
|
||||
|
||||
RepeatStatus dummy = RepeatStatus.CONTINUABLE;
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
context = policy.start(null);
|
||||
}
|
||||
|
||||
public void testTerminationAfterDefaultSize() throws Exception {
|
||||
for (int i = 0; i < SimpleCompletionPolicy.DEFAULT_CHUNK_SIZE - 1; i++) {
|
||||
policy.update(context);
|
||||
assertFalse(policy.isComplete(context, dummy));
|
||||
}
|
||||
policy.update(context);
|
||||
assertTrue(policy.isComplete(context, dummy));
|
||||
}
|
||||
|
||||
public void testTerminationAfterExplicitChunkSize() throws Exception {
|
||||
int chunkSize = 2;
|
||||
policy.setChunkSize(chunkSize);
|
||||
for (int i = 0; i < chunkSize - 1; i++) {
|
||||
policy.update(context);
|
||||
assertFalse(policy.isComplete(context, dummy));
|
||||
}
|
||||
policy.update(context);
|
||||
assertTrue(policy.isComplete(context, dummy));
|
||||
}
|
||||
|
||||
public void testTerminationAfterNullResult() throws Exception {
|
||||
policy.update(context);
|
||||
assertFalse(policy.isComplete(context, dummy));
|
||||
policy.update(context);
|
||||
assertTrue(policy.isComplete(context, null));
|
||||
}
|
||||
|
||||
public void testReset() throws Exception {
|
||||
policy.setChunkSize(2);
|
||||
policy.update(context);
|
||||
assertFalse(policy.isComplete(context, dummy));
|
||||
policy.update(context);
|
||||
assertTrue(policy.isComplete(context, dummy));
|
||||
context = policy.start(null);
|
||||
policy.update(context);
|
||||
assertFalse(policy.isComplete(context, dummy));
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.policy;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
public class TimeoutCompletionPolicyTests {
|
||||
|
||||
@Test
|
||||
public void testSimpleTimeout() throws Exception {
|
||||
TimeoutTerminationPolicy policy = new TimeoutTerminationPolicy(20L);
|
||||
RepeatContext context = policy.start(null);
|
||||
assertFalse(policy.isComplete(context));
|
||||
Thread.sleep(50L);
|
||||
assertTrue(policy.isComplete(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulResult() throws Exception {
|
||||
TimeoutTerminationPolicy policy = new TimeoutTerminationPolicy();
|
||||
RepeatContext context = policy.start(null);
|
||||
assertFalse(policy.isComplete(context, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonContinuableResult() throws Exception {
|
||||
TimeoutTerminationPolicy policy = new TimeoutTerminationPolicy();
|
||||
RepeatStatus result = RepeatStatus.FINISHED;
|
||||
assertTrue(policy.isComplete(policy.start(null), result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() throws Exception {
|
||||
TimeoutTerminationPolicy policy = new TimeoutTerminationPolicy(20L);
|
||||
RepeatContext context = policy.start(null);
|
||||
assertFalse(policy.isComplete(context));
|
||||
Thread.sleep(50L);
|
||||
assertTrue(policy.isComplete(context));
|
||||
policy.update(context);
|
||||
// update doesn't change completeness
|
||||
assertTrue(policy.isComplete(context));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
|
||||
/**
|
||||
* Base class for simple tests with small trade data set.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractTradeBatchTests {
|
||||
|
||||
public static final int NUMBER_OF_ITEMS = 5;
|
||||
|
||||
protected TradeWriter processor = new TradeWriter();
|
||||
|
||||
protected TradeReader provider;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
provider = new TradeReader();
|
||||
}
|
||||
|
||||
protected static class TradeReader {
|
||||
|
||||
private Trade[] trades = new Trade[] {
|
||||
new Trade("UK21341EAH45", 978, "98.34"),
|
||||
new Trade("UK21341EAH46", 112, "18.12"),
|
||||
new Trade("UK21341EAH47", 245, "12.78"),
|
||||
new Trade("UK21341EAH48", 108, "109.25"),
|
||||
new Trade("UK21341EAH49", 854, "123.39") };
|
||||
|
||||
private int count = 0;
|
||||
|
||||
public Trade read() {
|
||||
if (count < trades.length) {
|
||||
return trades[count++];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class TradeWriter {
|
||||
int count = 0;
|
||||
|
||||
// This has to be synchronized because we are going to test the state
|
||||
// (count) at the end of a concurrent batch run.
|
||||
public synchronized void write(List<? extends Trade> data) {
|
||||
count++;
|
||||
System.out.println("Executing trade '" + data + "'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.callback.NestedRepeatCallback;
|
||||
import org.springframework.repeat.policy.SimpleCompletionPolicy;
|
||||
|
||||
/**
|
||||
* Test various approaches to chunking of a batch. Not really a unit test, but
|
||||
* it should be fast.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ChunkedRepeatTests extends AbstractTradeBatchTests {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
public static class TradeRepeatCallback implements RepeatCallback {
|
||||
|
||||
private final TradeReader provider;
|
||||
private final TradeWriter processor;
|
||||
|
||||
public TradeRepeatCallback(TradeReader provider, TradeWriter processor) {
|
||||
this.provider = provider;
|
||||
this.processor = processor;
|
||||
|
||||
}
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
Trade item = provider.read();
|
||||
if (item == null) {
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
processor.write(Arrays.asList(item));
|
||||
return RepeatStatus.CONTINUABLE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Chunking using a dedicated TerminationPolicy. Transactions would be laid
|
||||
* on at the level of chunkTemplate.execute() or the surrounding callback.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testChunkedBatchWithTerminationPolicy() throws Exception {
|
||||
|
||||
RepeatTemplate repeatTemplate = new RepeatTemplate();
|
||||
final RepeatCallback callback = new TradeRepeatCallback(provider, processor);
|
||||
|
||||
final RepeatTemplate chunkTemplate = new RepeatTemplate();
|
||||
// The policy is resettable so we only have to resolve this dependency
|
||||
// once
|
||||
chunkTemplate.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
|
||||
RepeatStatus result = repeatTemplate.iterate(new NestedRepeatCallback(chunkTemplate, callback) {
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
count++; // for test assertion
|
||||
return super.doInIteration(context);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
assertEquals(NUMBER_OF_ITEMS, processor.count);
|
||||
// The chunk executes 3 times because the last one
|
||||
// returns false. We terminate the main batch when
|
||||
// we encounter a partially empty chunk.
|
||||
assertEquals(3, count);
|
||||
assertFalse(result.isContinuable());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunking with an asynchronous taskExecutor in the chunks. Transactions
|
||||
* have to be at the level of the business callback.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testAsynchronousChunkedBatchWithCompletionPolicy() throws Exception {
|
||||
|
||||
RepeatTemplate repeatTemplate = new RepeatTemplate();
|
||||
final RepeatCallback callback = new TradeRepeatCallback(provider, processor);
|
||||
|
||||
final TaskExecutorRepeatTemplate chunkTemplate = new TaskExecutorRepeatTemplate();
|
||||
// The policy is resettable so we only have to resolve this dependency
|
||||
// once
|
||||
chunkTemplate.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
chunkTemplate.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
|
||||
RepeatStatus result = repeatTemplate.iterate(new NestedRepeatCallback(chunkTemplate, callback) {
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
count++; // for test assertion
|
||||
return super.doInIteration(context);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
assertEquals(NUMBER_OF_ITEMS, processor.count);
|
||||
assertFalse(result.isContinuable());
|
||||
assertTrue("Expected at least 3 chunks but found: "+count, count>=3);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicit chunking of input data. Transactions would be laid on at the
|
||||
* level of template.execute().
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testChunksWithTruncatedItemProvider() throws Exception {
|
||||
|
||||
RepeatTemplate template = new RepeatTemplate();
|
||||
|
||||
// This pattern would work with an asynchronous callback as well
|
||||
// (but non-transactional in that case).
|
||||
|
||||
class Chunker {
|
||||
boolean ready = false;
|
||||
|
||||
int count = 0;
|
||||
|
||||
void set() {
|
||||
ready = true;
|
||||
}
|
||||
|
||||
boolean ready() {
|
||||
return ready;
|
||||
}
|
||||
|
||||
boolean first() {
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
count = 0;
|
||||
ready = false;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
final Chunker chunker = new Chunker();
|
||||
|
||||
while (!chunker.ready()) {
|
||||
|
||||
TradeReader truncated = new TradeReader() {
|
||||
int count = 0;
|
||||
|
||||
public Trade read() {
|
||||
if (count++ < 2)
|
||||
return provider.read();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
chunker.reset();
|
||||
template.iterate(new TradeRepeatCallback(truncated, processor) {
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
RepeatStatus result = super.doInIteration(context);
|
||||
if (!result.isContinuable() && chunker.first()) {
|
||||
chunker.set();
|
||||
}
|
||||
chunker.increment();
|
||||
return result;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
assertEquals(NUMBER_OF_ITEMS, processor.count);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
|
||||
public class RepeatSynchronizationManagerTests extends TestCase {
|
||||
|
||||
private RepeatContext context = new RepeatContextSupport(null);
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
RepeatSynchronizationManager.clear();
|
||||
}
|
||||
|
||||
protected void tearDown() throws Exception {
|
||||
RepeatSynchronizationManager.clear();
|
||||
}
|
||||
|
||||
public void testGetContext() {
|
||||
RepeatSynchronizationManager.register(context);
|
||||
assertEquals(context, RepeatSynchronizationManager.getContext());
|
||||
}
|
||||
|
||||
public void testSetSessionCompleteOnly() {
|
||||
assertNull(RepeatSynchronizationManager.getContext());
|
||||
RepeatSynchronizationManager.register(context);
|
||||
assertFalse(RepeatSynchronizationManager.getContext().isCompleteOnly());
|
||||
RepeatSynchronizationManager.setCompleteOnly();
|
||||
assertTrue(RepeatSynchronizationManager.getContext().isCompleteOnly());
|
||||
}
|
||||
|
||||
public void testSetSessionCompleteOnlyWithParent() {
|
||||
assertNull(RepeatSynchronizationManager.getContext());
|
||||
RepeatContext child = new RepeatContextSupport(context);
|
||||
RepeatSynchronizationManager.register(child);
|
||||
assertFalse(child.isCompleteOnly());
|
||||
RepeatSynchronizationManager.setAncestorsCompleteOnly();
|
||||
assertTrue(child.isCompleteOnly());
|
||||
assertTrue(context.isCompleteOnly());
|
||||
}
|
||||
|
||||
public void testClear() {
|
||||
RepeatSynchronizationManager.register(context);
|
||||
assertEquals(context, RepeatSynchronizationManager.getContext());
|
||||
RepeatSynchronizationManager.clear();
|
||||
assertEquals(null, RepeatSynchronizationManager.getContext());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.springframework.repeat.support;
|
||||
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
|
||||
public class ResultHolderResultQueueTests {
|
||||
|
||||
private ResultHolderResultQueue queue = new ResultHolderResultQueue(10);
|
||||
|
||||
@Test
|
||||
public void testPutTake() throws Exception {
|
||||
queue.expect();
|
||||
assertTrue(queue.isExpecting());
|
||||
assertTrue(queue.isEmpty());
|
||||
queue.put(new TestResultHolder(RepeatStatus.CONTINUABLE));
|
||||
assertFalse(queue.isEmpty());
|
||||
assertTrue(queue.take().getResult().isContinuable());
|
||||
assertFalse(queue.isExpecting());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrdering() throws Exception {
|
||||
queue.expect();
|
||||
queue.expect();
|
||||
queue.put(new TestResultHolder(RepeatStatus.FINISHED));
|
||||
queue.put(new TestResultHolder(RepeatStatus.CONTINUABLE));
|
||||
assertFalse(queue.isEmpty());
|
||||
assertTrue(queue.take().getResult().isContinuable());
|
||||
assertFalse(queue.take().getResult().isContinuable());
|
||||
}
|
||||
|
||||
private static class TestResultHolder implements ResultHolder {
|
||||
|
||||
private RepeatStatus result;
|
||||
|
||||
private Throwable error;
|
||||
|
||||
public TestResultHolder(RepeatStatus result) {
|
||||
super();
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public RepeatContext getContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public RepeatStatus getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,514 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatException;
|
||||
import org.springframework.repeat.RepeatListener;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.callback.NestedRepeatCallback;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
import org.springframework.repeat.exception.ExceptionHandler;
|
||||
import org.springframework.repeat.listener.RepeatListenerSupport;
|
||||
import org.springframework.repeat.policy.CompletionPolicySupport;
|
||||
import org.springframework.repeat.policy.SimpleCompletionPolicy;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
|
||||
|
||||
private RepeatTemplate template = getRepeatTemplate();
|
||||
|
||||
private int count = 0;
|
||||
|
||||
public static class TradeRepeatCallback implements RepeatCallback {
|
||||
|
||||
private final TradeReader provider;
|
||||
private final TradeWriter processor;
|
||||
|
||||
public TradeRepeatCallback(TradeReader provider, TradeWriter processor) {
|
||||
this.provider = provider;
|
||||
this.processor = processor;
|
||||
|
||||
}
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
Trade item = provider.read();
|
||||
if (item == null) {
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
processor.write(Arrays.asList(item));
|
||||
return RepeatStatus.CONTINUABLE;
|
||||
}
|
||||
}
|
||||
|
||||
public RepeatTemplate getRepeatTemplate() {
|
||||
template = new RepeatTemplate();
|
||||
// default stop after more items than exist in dataset
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(8));
|
||||
return template;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute() throws Exception {
|
||||
template.iterate(new TradeRepeatCallback(provider, processor));
|
||||
assertEquals(NUMBER_OF_ITEMS, processor.count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a dedicated TerminationPolicy can terminate the batch.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testEarlyCompletionWithPolicy() throws Exception {
|
||||
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
|
||||
template.iterate(new TradeRepeatCallback(provider, processor));
|
||||
|
||||
assertEquals(2, processor.count);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a dedicated TerminationPolicy can terminate the batch.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testEarlyCompletionWithException() throws Exception {
|
||||
|
||||
try {
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
throw new IllegalStateException("foo!");
|
||||
}
|
||||
});
|
||||
fail("Expected IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
assertEquals("foo!", e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(1, count);
|
||||
assertTrue("Too many attempts: " + count, count <= 10);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the context is closed.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testContextClosedOnNormalCompletion() throws Exception {
|
||||
|
||||
final List<String> list = new ArrayList<String>();
|
||||
|
||||
final RepeatContext context = new RepeatContextSupport(null) {
|
||||
public void close() {
|
||||
super.close();
|
||||
list.add("close");
|
||||
}
|
||||
};
|
||||
template.setCompletionPolicy(new CompletionPolicySupport() {
|
||||
public RepeatContext start(RepeatContext c) {
|
||||
return context;
|
||||
}
|
||||
});
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
return RepeatStatus.continueIf(count < 1);
|
||||
}
|
||||
});
|
||||
|
||||
assertEquals(1, count);
|
||||
assertEquals(1, list.size());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the context is closed.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testContextClosedOnAbnormalCompletion() throws Exception {
|
||||
|
||||
final List<String> list = new ArrayList<String>();
|
||||
|
||||
final RepeatContext context = new RepeatContextSupport(null) {
|
||||
public void close() {
|
||||
super.close();
|
||||
list.add("close");
|
||||
}
|
||||
};
|
||||
template.setCompletionPolicy(new CompletionPolicySupport() {
|
||||
public RepeatContext start(RepeatContext c) {
|
||||
return context;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
throw new RuntimeException("foo");
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
assertEquals("foo", e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(1, count);
|
||||
assertEquals(1, list.size());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the exception handler is called.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testExceptionHandlerCalledOnAbnormalCompletion()
|
||||
throws Exception {
|
||||
|
||||
final List<Throwable> list = new ArrayList<Throwable>();
|
||||
|
||||
template.setExceptionHandler(new ExceptionHandler() {
|
||||
public void handleException(RepeatContext context,
|
||||
Throwable throwable) throws RuntimeException {
|
||||
list.add(throwable);
|
||||
throw (RuntimeException) throwable;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
throw new RuntimeException("foo");
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
assertEquals("foo", e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(1, count);
|
||||
assertEquals(1, list.size());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a the context can be used to signal early completion.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testEarlyCompletionWithContext() throws Exception {
|
||||
|
||||
RepeatStatus result = template.iterate(new TradeRepeatCallback(
|
||||
provider, processor) {
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
RepeatStatus result = super.doInIteration(context);
|
||||
if (processor.count >= 2) {
|
||||
context.setCompleteOnly();
|
||||
// If we return null the batch will terminate anyway
|
||||
// without an exception...
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
// 2 items were processed before completion signalled
|
||||
assertEquals(2, processor.count);
|
||||
|
||||
// Not all items processed
|
||||
assertTrue(result.isContinuable());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a the context can be used to signal early completion.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testEarlyCompletionWithContextTerminated() throws Exception {
|
||||
|
||||
RepeatStatus result = template.iterate(new TradeRepeatCallback(
|
||||
provider, processor) {
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
RepeatStatus result = super.doInIteration(context);
|
||||
if (processor.count >= 2) {
|
||||
context.setTerminateOnly();
|
||||
// If we return null the batch will terminate anyway
|
||||
// without an exception...
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
// 2 items were processed before completion signalled
|
||||
assertEquals(2, processor.count);
|
||||
|
||||
// Not all items processed
|
||||
assertTrue(result.isContinuable());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedSession() throws Exception {
|
||||
RepeatTemplate outer = getRepeatTemplate();
|
||||
RepeatTemplate inner = getRepeatTemplate();
|
||||
outer.iterate(new NestedRepeatCallback(inner, new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
assertNotNull(context);
|
||||
assertNotSame("Nested batch should have new session", context,
|
||||
context.getParent());
|
||||
assertSame(context, RepeatSynchronizationManager.getContext());
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
}) {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
assertSame(context, RepeatSynchronizationManager.getContext());
|
||||
return super.doInIteration(context);
|
||||
}
|
||||
});
|
||||
assertEquals(2, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedSessionTerminatesBeforeIteration() throws Exception {
|
||||
RepeatTemplate outer = getRepeatTemplate();
|
||||
RepeatTemplate inner = getRepeatTemplate();
|
||||
outer.iterate(new NestedRepeatCallback(inner, new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
assertEquals(2, count);
|
||||
fail("Nested batch should not have been executed");
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
}) {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
context.setCompleteOnly();
|
||||
return super.doInIteration(context);
|
||||
}
|
||||
});
|
||||
assertEquals(1, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOuterContextPreserved() throws Exception {
|
||||
RepeatTemplate outer = getRepeatTemplate();
|
||||
outer.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
RepeatTemplate inner = getRepeatTemplate();
|
||||
outer.iterate(new NestedRepeatCallback(inner, new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
assertNotNull(context);
|
||||
assertNotSame("Nested batch should have new session", context,
|
||||
context.getParent());
|
||||
assertSame(context, RepeatSynchronizationManager.getContext());
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
}) {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
assertSame(context, RepeatSynchronizationManager.getContext());
|
||||
super.doInIteration(context);
|
||||
return RepeatStatus.CONTINUABLE;
|
||||
}
|
||||
});
|
||||
assertEquals(4, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a result is returned from the batch.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testResult() throws Exception {
|
||||
RepeatStatus result = template.iterate(new TradeRepeatCallback(
|
||||
provider, processor));
|
||||
assertEquals(NUMBER_OF_ITEMS, processor.count);
|
||||
// We are complete - do not expect to be called again
|
||||
assertFalse(result.isContinuable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionThrownOnLastItem() throws Exception {
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
|
||||
try {
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
if (count < 2) {
|
||||
return RepeatStatus.CONTINUABLE;
|
||||
}
|
||||
throw new RuntimeException("Barf second try count=" + count);
|
||||
}
|
||||
});
|
||||
fail("Expected exception on last item in batch");
|
||||
} catch (Exception e) {
|
||||
// expected
|
||||
assertEquals("Barf second try count=2", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a the session can be used to signal early completion, but an
|
||||
* exception takes precedence.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testEarlyCompletionWithSessionAndException() throws Exception {
|
||||
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(4));
|
||||
|
||||
RepeatStatus result = RepeatStatus.FINISHED;
|
||||
|
||||
try {
|
||||
result = template.iterate(new TradeRepeatCallback(provider,
|
||||
processor) {
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
RepeatStatus result = super.doInIteration(context);
|
||||
if (processor.count >= 2) {
|
||||
context.setCompleteOnly();
|
||||
throw new RuntimeException("Barf second try count="
|
||||
+ processor.count);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
fail("Expected exception on last item in batch");
|
||||
} catch (RuntimeException e) {
|
||||
// expected
|
||||
assertEquals("Barf second try count=2", e.getMessage());
|
||||
}
|
||||
|
||||
// 2 items were processed before completion signalled
|
||||
assertEquals(2, processor.count);
|
||||
|
||||
System.err.println(result);
|
||||
|
||||
// An exception was thrown by the template so result is still false
|
||||
assertFalse(result.isContinuable());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checked exceptions are wrapped into runtime RepeatException.
|
||||
* RepeatException should be unwrapped before before it is passed to
|
||||
* listeners and exception handler.
|
||||
*/
|
||||
@Test
|
||||
public void testExceptionUnwrapping() {
|
||||
|
||||
class TestException extends Exception {
|
||||
TestException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
final TestException exception = new TestException("CRASH!");
|
||||
|
||||
class ExceptionHandlerStub implements ExceptionHandler {
|
||||
boolean called = false;
|
||||
|
||||
public void handleException(RepeatContext context,
|
||||
Throwable throwable) throws Throwable {
|
||||
called = true;
|
||||
assertSame(exception, throwable);
|
||||
throw throwable; // re-throw so that repeat template
|
||||
// terminates iteration
|
||||
}
|
||||
}
|
||||
ExceptionHandlerStub exHandler = new ExceptionHandlerStub();
|
||||
|
||||
class RepeatListenerStub extends RepeatListenerSupport {
|
||||
boolean called = false;
|
||||
|
||||
public void onError(RepeatContext context, Throwable throwable) {
|
||||
called = true;
|
||||
assertSame(exception, throwable);
|
||||
}
|
||||
}
|
||||
RepeatListenerStub listener = new RepeatListenerStub();
|
||||
|
||||
template.setExceptionHandler(exHandler);
|
||||
template.setListeners(new RepeatListener[] { listener });
|
||||
|
||||
try {
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
throw new RepeatException(
|
||||
"typically thrown by nested repeat template",
|
||||
exception);
|
||||
}
|
||||
});
|
||||
fail();
|
||||
} catch (RepeatException expected) {
|
||||
assertSame(exception, expected.getCause());
|
||||
}
|
||||
|
||||
assertTrue(listener.called);
|
||||
assertTrue(exHandler.called);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.callback.NestedRepeatCallback;
|
||||
import org.springframework.repeat.exception.ExceptionHandler;
|
||||
import org.springframework.repeat.policy.SimpleCompletionPolicy;
|
||||
|
||||
public class TaskExecutorRepeatTemplateAsynchronousTests extends
|
||||
AbstractTradeBatchTests {
|
||||
|
||||
RepeatTemplate template = getRepeatTemplate();
|
||||
|
||||
int count = 0;
|
||||
|
||||
// @Override
|
||||
public RepeatTemplate getRepeatTemplate() {
|
||||
TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate();
|
||||
template.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
// Set default completion above number of items in input file
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(8));
|
||||
return template;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEarlyCompletionWithException() throws Exception {
|
||||
|
||||
TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate();
|
||||
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(20));
|
||||
taskExecutor.setConcurrencyLimit(2);
|
||||
template.setTaskExecutor(taskExecutor);
|
||||
try {
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
throw new IllegalStateException("foo!");
|
||||
}
|
||||
});
|
||||
fail("Expected IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
assertEquals("foo!", e.getMessage());
|
||||
}
|
||||
|
||||
assertTrue("Too few attempts: " + count, count >= 1);
|
||||
assertTrue("Too many attempts: " + count, count <= 10);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionHandlerSwallowsException() throws Exception {
|
||||
|
||||
TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate();
|
||||
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(4));
|
||||
taskExecutor.setConcurrencyLimit(2);
|
||||
template.setTaskExecutor(taskExecutor);
|
||||
|
||||
template.setExceptionHandler(new ExceptionHandler() {
|
||||
public void handleException(RepeatContext context,
|
||||
Throwable throwable) throws Throwable {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
template.iterate(new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
throw new IllegalStateException("foo!");
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue("Too few attempts: " + count, count >= 1);
|
||||
assertTrue("Too many attempts: " + count, count <= 10);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedSession() throws Exception {
|
||||
|
||||
RepeatTemplate outer = getRepeatTemplate();
|
||||
RepeatTemplate inner = new RepeatTemplate();
|
||||
|
||||
outer.iterate(new NestedRepeatCallback(inner, new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
assertNotNull(context);
|
||||
assertNotSame("Nested batch should have new session", context,
|
||||
context.getParent());
|
||||
assertSame(context, RepeatSynchronizationManager.getContext());
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
}) {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
count++;
|
||||
assertNotNull(context);
|
||||
assertSame(context, RepeatSynchronizationManager.getContext());
|
||||
return super.doInIteration(context);
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue("Too few attempts: " + count, count >= 1);
|
||||
assertTrue("Too many attempts: " + count, count <= 10);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a batch with a single template that itself has an async task
|
||||
* executor. The result is a batch that runs in multiple threads (up to the
|
||||
* throttle limit of the template).
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testMultiThreadAsynchronousExecution() throws Exception {
|
||||
|
||||
final String threadName = Thread.currentThread().getName();
|
||||
final Set<String> threadNames = new HashSet<String>();
|
||||
|
||||
final RepeatCallback callback = new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
assertNotSame(threadName, Thread.currentThread().getName());
|
||||
threadNames.add(Thread.currentThread().getName());
|
||||
Thread.sleep(100);
|
||||
Trade item = provider.read();
|
||||
if (item != null) {
|
||||
processor.write(Collections.singletonList(item));
|
||||
}
|
||||
return RepeatStatus.continueIf(item != null);
|
||||
}
|
||||
};
|
||||
|
||||
template.iterate(callback);
|
||||
// Shouldn't be necessary to wait:
|
||||
// Thread.sleep(500);
|
||||
assertEquals(NUMBER_OF_ITEMS, processor.count);
|
||||
assertTrue(threadNames.size() > 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrottleLimit() throws Exception {
|
||||
|
||||
int throttleLimit = 600;
|
||||
|
||||
TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate();
|
||||
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
taskExecutor.setConcurrencyLimit(300);
|
||||
template.setTaskExecutor(taskExecutor);
|
||||
template.setThrottleLimit(throttleLimit);
|
||||
|
||||
final String threadName = Thread.currentThread().getName();
|
||||
final Set<String> threadNames = new HashSet<String>();
|
||||
final List<String> items = new ArrayList<String>();
|
||||
|
||||
final RepeatCallback callback = new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
assertNotSame(threadName, Thread.currentThread().getName());
|
||||
Trade item = provider.read();
|
||||
threadNames
|
||||
.add(Thread.currentThread().getName() + " : " + item);
|
||||
items.add("" + item);
|
||||
if (item != null) {
|
||||
processor.write(Collections.singletonList(item));
|
||||
// Do some more I/O
|
||||
for (int i = 0; i < 10; i++) {
|
||||
TradeReader provider = new TradeReader();
|
||||
while (provider.read() != null)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return RepeatStatus.continueIf(item != null);
|
||||
}
|
||||
};
|
||||
|
||||
template.iterate(callback);
|
||||
// Shouldn't be necessary to wait:
|
||||
// Thread.sleep(500);
|
||||
assertEquals(NUMBER_OF_ITEMS, processor.count);
|
||||
assertTrue(threadNames.size() > 1);
|
||||
int frequency = Collections.frequency(items, "null");
|
||||
// System.err.println("Frequency: "+frequency);
|
||||
assertTrue(frequency <= throttleLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an otherwise synchronous batch in a callback to an asynchronous
|
||||
* template.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testSingleThreadAsynchronousExecution() throws Exception {
|
||||
TaskExecutorRepeatTemplate jobTemplate = new TaskExecutorRepeatTemplate();
|
||||
final RepeatTemplate stepTemplate = new RepeatTemplate();
|
||||
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
taskExecutor.setConcurrencyLimit(2);
|
||||
jobTemplate.setTaskExecutor(taskExecutor);
|
||||
|
||||
final String threadName = Thread.currentThread().getName();
|
||||
final Set<String> threadNames = new HashSet<String>();
|
||||
|
||||
final RepeatCallback stepCallback = new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
assertNotSame(threadName, Thread.currentThread().getName());
|
||||
threadNames.add(Thread.currentThread().getName());
|
||||
Thread.sleep(100);
|
||||
Trade item = provider.read();
|
||||
if (item != null) {
|
||||
processor.write(Collections.singletonList(item));
|
||||
}
|
||||
return RepeatStatus.continueIf(item != null);
|
||||
}
|
||||
};
|
||||
RepeatCallback jobCallback = new RepeatCallback() {
|
||||
public RepeatStatus doInIteration(RepeatContext context)
|
||||
throws Exception {
|
||||
stepTemplate.iterate(stepCallback);
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
};
|
||||
|
||||
jobTemplate.iterate(jobCallback);
|
||||
// Shouldn't be necessary to wait:
|
||||
// Thread.sleep(500);
|
||||
assertEquals(NUMBER_OF_ITEMS, processor.count);
|
||||
// Because of the throttling and queueing internally to a TaskExecutor,
|
||||
// more than one thread will be used - the number used is the
|
||||
// concurrency limit in the task executor, plus 1.
|
||||
// System.err.println(threadNames);
|
||||
assertTrue(threadNames.size() >= 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.repeat.RepeatCallback;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.RepeatStatus;
|
||||
import org.springframework.repeat.policy.SimpleCompletionPolicy;
|
||||
|
||||
/**
|
||||
* Simple tests for concurrent behaviour in repeat template, in particular the
|
||||
* barrier at the end of the iteration. N.B. these tests may fail if
|
||||
* insufficient threads are available (e.g. on a single-core machine, or under
|
||||
* load). They shouldn't deadlock though.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class TaskExecutorRepeatTemplateBulkAsynchronousTests {
|
||||
|
||||
static Log logger = LogFactory.getLog(TaskExecutorRepeatTemplateBulkAsynchronousTests.class);
|
||||
|
||||
private int total = 20;
|
||||
|
||||
private int throttleLimit = 8;
|
||||
|
||||
private volatile int early = Integer.MAX_VALUE;
|
||||
|
||||
private TaskExecutorRepeatTemplate template;
|
||||
|
||||
private RepeatCallback callback;
|
||||
|
||||
private List<String> items;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
template = new TaskExecutorRepeatTemplate();
|
||||
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
taskExecutor.setConcurrencyLimit(300);
|
||||
template.setTaskExecutor(taskExecutor);
|
||||
template.setThrottleLimit(throttleLimit);
|
||||
|
||||
items = Collections.synchronizedList(new ArrayList<String>());
|
||||
|
||||
callback = new RepeatCallback() {
|
||||
|
||||
private volatile AtomicInteger count = new AtomicInteger(0);
|
||||
|
||||
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
|
||||
int position = count.incrementAndGet();
|
||||
String item = position <= total ? "" + position : null;
|
||||
items.add("" + item);
|
||||
if (item != null) {
|
||||
beBusy();
|
||||
}
|
||||
/*
|
||||
* In a multi-threaded task, one of the callbacks can call
|
||||
* FINISHED early, while other threads are still working, and
|
||||
* would do more work if the callback was called again. (This
|
||||
* happens for instance if there is a failure and you want to
|
||||
* retry the work.)
|
||||
*/
|
||||
RepeatStatus result = RepeatStatus.continueIf(position != early && item != null);
|
||||
if (!result.isContinuable()) {
|
||||
logger.debug("Returning " + result + " for count=" + position);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrottleLimit() throws Exception {
|
||||
|
||||
template.iterate(callback);
|
||||
int frequency = Collections.frequency(items, "null");
|
||||
// System.err.println(items);
|
||||
// System.err.println("Frequency: " + frequency);
|
||||
assertEquals(total, items.size() - frequency);
|
||||
assertTrue(frequency > 1);
|
||||
assertTrue(frequency <= throttleLimit + 1);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrottleLimitEarlyFinish() throws Exception {
|
||||
|
||||
early = 2;
|
||||
|
||||
template.iterate(callback);
|
||||
int frequency = Collections.frequency(items, "null");
|
||||
// System.err.println("Frequency: " + frequency);
|
||||
// System.err.println("Items: " + items);
|
||||
assertEquals(total, items.size() - frequency);
|
||||
assertTrue(frequency > 1);
|
||||
assertTrue(frequency <= throttleLimit + 1);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrottleLimitEarlyFinishThreadStarvation() throws Exception {
|
||||
|
||||
early = 2;
|
||||
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
// Set the concurrency limit below the throttle limit for possible
|
||||
// starvation condition
|
||||
taskExecutor.setConcurrencyLimit(20);
|
||||
template.setTaskExecutor(taskExecutor);
|
||||
|
||||
template.iterate(callback);
|
||||
int frequency = Collections.frequency(items, "null");
|
||||
// System.err.println("Frequency: " + frequency);
|
||||
// System.err.println("Items: " + items);
|
||||
// Extra tasks will be submitted before the termination is detected
|
||||
assertEquals(total, items.size() - frequency);
|
||||
assertTrue(frequency <= throttleLimit + 1);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrottleLimitEarlyFinishOneThread() throws Exception {
|
||||
|
||||
early = 4;
|
||||
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
taskExecutor.setConcurrencyLimit(1);
|
||||
|
||||
// This is kind of slow with only one thread, so reduce size:
|
||||
throttleLimit = 4;
|
||||
total = 10;
|
||||
|
||||
template.setThrottleLimit(throttleLimit);
|
||||
template.setTaskExecutor(taskExecutor);
|
||||
|
||||
template.iterate(callback);
|
||||
int frequency = Collections.frequency(items, "null");
|
||||
// System.err.println("Frequency: " + frequency);
|
||||
// System.err.println("Items: " + items);
|
||||
assertEquals(total, items.size() - frequency);
|
||||
assertTrue(frequency <= throttleLimit + 1);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrottleLimitWithEarlyCompletion() throws Exception {
|
||||
|
||||
early = 2;
|
||||
template.setCompletionPolicy(new SimpleCompletionPolicy(10));
|
||||
|
||||
template.iterate(callback);
|
||||
int frequency = Collections.frequency(items, "null");
|
||||
assertEquals(10, items.size() - frequency);
|
||||
// System.err.println("Frequency: " + frequency);
|
||||
assertEquals(0, frequency);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Slightly flakey convenience method. If this doesn't do something that
|
||||
* lasts sufficiently long for another worker to be launched while it is
|
||||
* busy, the early completion tests will fail. "Sufficiently long" is the
|
||||
* problem so we try and block until we know someone else is busy?
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private void beBusy() throws Exception {
|
||||
synchronized (this) {
|
||||
wait(100L);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class TaskExecutorRepeatTemplateTests extends SimpleRepeatTemplateTests {
|
||||
|
||||
public RepeatTemplate getRepeatTemplate() {
|
||||
return new TaskExecutorRepeatTemplate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetThrottleLimit() throws Exception {
|
||||
try {
|
||||
new TaskExecutorRepeatTemplate().setThrottleLimit(-1);
|
||||
} catch (Exception e) {
|
||||
// unexpected - no check for illegal values
|
||||
fail("Unexpected Exception setting throttle limit");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ThrottleLimitResultQueueTests {
|
||||
|
||||
private ThrottleLimitResultQueue<String> queue = new ThrottleLimitResultQueue<String>(1);
|
||||
|
||||
@Test
|
||||
public void testPutTake() throws Exception {
|
||||
queue.expect();
|
||||
assertTrue(queue.isExpecting());
|
||||
assertTrue(queue.isEmpty());
|
||||
queue.put("foo");
|
||||
assertFalse(queue.isEmpty());
|
||||
assertEquals("foo", queue.take());
|
||||
assertFalse(queue.isExpecting());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutWithoutExpecting() throws Exception {
|
||||
assertFalse(queue.isExpecting());
|
||||
try {
|
||||
queue.put("foo");
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTakeWithoutExpecting() throws Exception {
|
||||
assertFalse(queue.isExpecting());
|
||||
try {
|
||||
queue.take();
|
||||
fail("Expected NoSuchElementException");
|
||||
} catch (NoSuchElementException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrottleLimit() throws Exception {
|
||||
queue.expect();
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(100L);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
queue.put("foo");
|
||||
}
|
||||
}).start();
|
||||
long t0 = System.currentTimeMillis();
|
||||
queue.expect();
|
||||
long t1 = System.currentTimeMillis();
|
||||
assertEquals("foo", queue.take());
|
||||
assertTrue(queue.isExpecting());
|
||||
assertTrue("Did not block on expect (throttle limit should have been hit): time taken="+(t1-t0), t1-t0>50);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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 org.springframework.repeat.support;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author Rob Harrop
|
||||
*/
|
||||
public class Trade {
|
||||
|
||||
private String isin;
|
||||
|
||||
private long quantity;
|
||||
|
||||
private BigDecimal price;
|
||||
|
||||
Trade(String isin, long quantity, String price) {
|
||||
this.isin = isin;
|
||||
this.quantity = quantity;
|
||||
this.price = new BigDecimal(price);
|
||||
}
|
||||
|
||||
public String getIsin() {
|
||||
return isin;
|
||||
}
|
||||
|
||||
public BigDecimal getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public long getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Trade: [isin=" + isin + ",quantity=" + quantity + ",price=" + price + "]";
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,6 @@ import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.springframework.classify.BinaryExceptionClassifier;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.repeat.RepeatContext;
|
||||
import org.springframework.repeat.context.RepeatContextSupport;
|
||||
import org.springframework.repeat.support.RepeatSynchronizationManager;
|
||||
import org.springframework.retry.ExhaustedRetryException;
|
||||
import org.springframework.retry.RecoveryCallback;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
@@ -83,15 +80,6 @@ public class StatefulRecoveryRetryTests {
|
||||
assertFalse(retryPolicy.canRetry(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverWithParent() throws Exception {
|
||||
RepeatContext parent = new RepeatContextSupport(null);
|
||||
RepeatSynchronizationManager.register(new RepeatContextSupport(parent));
|
||||
testRecover();
|
||||
assertFalse(parent.isCompleteOnly());
|
||||
RepeatSynchronizationManager.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecover() throws Exception {
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(1, Collections
|
||||
|
||||
Reference in New Issue
Block a user