Here is my code
The first bean is watching the messages on Topic.TRANSACTION_RAW and split one message into two and send them to Topic.TRANSACTION_INTERNAL
And the second bean is doing group and reducing and materialize it to the state store "StateStore.BALANCE".
The last one is to get the ReadOnlyKeyValueStore to read state from "ReadOnlyKeyValueStore".
#Configuration(proxyBeanMethods = false)
#EnableKafkaStreams
public class MyKafkaStreamsConfiguration {
#Bean
public KStream<String, BankTransaction> alphaBankKStream(StreamsBuilder streamsBuilder) {
JsonSerde<BankTransaction> valueSerde = new JsonSerde<>(BankTransaction.class);
KStream<String, BankTransaction> stream = streamsBuilder.stream(Topic.TRANSACTION_RAW,
Consumed.with(Serdes.String(), valueSerde));
stream.flatMap((k, v) -> {
List<BankTransactionInternal> txInternals = BankTransactionInternal.splitBankTransaction(v);
List<KeyValue<String, BankTransactionInternal>> result = new LinkedList<>();
result.add(KeyValue.pair(v.getFromAccount(), txInternals.get(0)));
result.add(KeyValue.pair(v.getToAccount(), txInternals.get(1)));
return result;
}).filter((k, v) -> !Constants.EXTERNAL_ACCOUNT.equalsIgnoreCase(k))
.to(Topic.TRANSACTION_INTERNAL, Produced.with(Serdes.String(), new JsonSerde<>()));
return stream;
}
#Bean
public KStream<String, BankTransactionInternal> alphaBankInternalKStream(StreamsBuilder streamsBuilder) {
JsonSerde<BankTransactionInternal> valueSerde = new JsonSerde<>(BankTransactionInternal.class);
KStream<String, BankTransactionInternal> stream = streamsBuilder.stream(Topic.TRANSACTION_INTERNAL,
Consumed.with(Serdes.String(), valueSerde));
KGroupedStream<String, Double> groupedByAccount = stream
.map((k,v) -> KeyValue.pair(k, v.getAmount()))
.groupBy((account, amount) -> account, Grouped.with(Serdes.String(), Serdes.Double()));
groupedByAccount.reduce(Double::sum,
Materialized.<String, Double, KeyValueStore<Bytes, byte[]>>as(StateStore.BALANCE)
.withValueSerde(Serdes.Double()));
return stream;
}
#Bean
public ReadOnlyKeyValueStore<String, Double> balanceStateStore(StreamsBuilderFactoryBean defaultKafkaStreamsBuilder) {
if (defaultKafkaStreamsBuilder == null) {
System.out.println("... defaultKafkaStreamsBuilder is null ...");
}
if (defaultKafkaStreamsBuilder.getKafkaStreams() == null) {
System.out.println("... defaultKafkaStreamsBuilder.getKafkaStreams() is null ...");
// this one got printed
}
ReadOnlyKeyValueStore<String, Double> store = defaultKafkaStreamsBuilder.getKafkaStreams().store(
StateStore.BALANCE,
QueryableStoreTypes.keyValueStore());
return store;
}
}
I always got NullPointException on defaultKafkaStreamsBuilder.getKafkaStreams().
Any idea what is wrong here? Thanks!
if (defaultKafkaStreamsBuilder.getKafkaStreams() == null) {
System.out.println("... defaultKafkaStreamsBuilder.getKafkaStreams() is null ...");
// this one got printed
}
This operation is not good to do during bean definition phase.
See its JavaDocs:
/**
* Get a managed by this {#link StreamsBuilderFactoryBean} {#link KafkaStreams} instance.
* #return KafkaStreams managed instance;
* may be null if this {#link StreamsBuilderFactoryBean} hasn't been started.
* #since 1.1.4
*/
public synchronized KafkaStreams getKafkaStreams() {
since you call this method far too early before a lifecycle start phase, you end-up with that error.
You should reconsider your logic in favor of SmartLifecycle.start() in the target service where you'd like to use that ReadOnlyKeyValueStore. So, you autowire over there this StreamsBuilderFactoryBean and call its getKafkaStreams() from the start() implementation.
Related
I have a TableView that holds a Model class, which has a BooleanProperty as follow
#FXML
TableView<Model> tableView;
Model Class :
class Model{
BooleanProperty valid;
public Model()
{
valid = new SimpleBooleanProperty();
}
... getters and setters
}
What i want to acheive is to bind a button disable property with selected item valid Property in the Model class fom the tableView, i know that i can acheive that with listeners, but using a listener needs to set first the initial value properly, since they are not getting fired until there is some change, as an exemple in this case, if there is no selected item from the table and the button is set to be not disable from the start, it will still be like that, until the listener fired, this is why i prefer to use Bindings, since it doesn't care about the initial value. is there any way to do so with Bindings also ?
what i tried :
i tried this :
transferButton.disableProperty().bind(Bindings.when(tableView.getSelectionModel().selectedItemProperty().isNotNull()).then(
tableView.getSelectionModel().getSelectedItem().valideProperty()
).otherwise(false));
but the problem is that i'm getting the following error :
return value of "javafx.scene.control.TableView$TableViewSelectionModel.getSelectedItem()" is null
Even tho i put a condition to the binding : Bindings.when(tableView.getSelectionModel().selectedItemProperty().isNotNull()
You can use a custom binding which implements a listener: for example:
transferButton.disableProperty().bind(new BooleanBinding() {
{
tableView.getSelectionModel().selectedItemProperty().addListener(obs, oldSelection, newSelection) -> {
if (oldSelection != null) unbind(oldSelection.validProperty());
if (newSelection != null) bind(newSelection.validProperty());
invalidate();
});
bind(tableView.getSelectionModel().selectedItemProperty());
}
#Override
protected boolean computeValue() {
Model selection = tableView.getSelectionModel().getSelectedItem();
if (selection == null) return true ;
return ! selection.isValid();
}
});
There is also a selection API in the Bindings API which will work, though it is not robust and will generate spurious warnings when the selection is null:
transferButton.disableProperty().bind(Bindings.selectBoolean(
tableView.getSelectionModel().selectedItemProperty(),
"valid"
)).not());
Here's an approach of a custom select binding which uses functions to provide nested properties (similar to core SelectBinding, just replacing the reflective access to the nested properties by functions providing them)
The basic idea
start with binding to the root
keep the binding chain in the dependencies
update the binding chain on validating (no need to do anything as long as the binding is not valid)
implement state cleanup
Code example (here with a single function only, can be extended for a longer chain, though, by adding more functions and walk the providers)
/**
* Custom binding to a nested property using a Function to provide the nested.
*/
public class XSelectBinding<R, T> extends ObjectBinding<T> {
private ObservableList<ObservableValue<?>> dependencies;
private Function<R, ObservableValue<T>> provider;
public XSelectBinding(ObservableValue<R> root, Function<R, ObservableValue<T>> provider) {
if (root == null) {
throw new NullPointerException("root must not be null");
}
if (provider == null) {
throw new NullPointerException("provider must not be null");
}
dependencies = FXCollections.observableArrayList(root);
this.provider = provider;
bind(root);
}
/**
* Implemented to update dependencies and return the value of the nested property if
* available
*/
#Override
protected T computeValue() {
onValidating();
ObservableValue<?> child = dependencies.size() > 1 ? dependencies.get(1) : null;
return child != null ? (T) child.getValue() : null;
}
/**
* Updates dependencies and bindings on validating.
*/
protected void onValidating() {
// grab the root
ObservableValue<R> root = (ObservableValue<R>) dependencies.get(0);
// cleanup bindings and dependencies
unbindAll();
// rebind starting from root
dependencies.add(root);
ObservableValue<T> nestedProperty = root.getValue() != null ?
provider.apply(root.getValue()) : null;
if (nestedProperty != null) {
dependencies.add(nestedProperty);
}
bind(dependencies.toArray(new ObservableValue<?>[dependencies.size()]));
}
/**
* Unbinds and clears dependencies.
*/
private void unbindAll() {
unbind(dependencies.toArray(new ObservableValue<?>[dependencies.size()]));
dependencies.clear();
}
#Override
public ObservableList<?> getDependencies() {
return FXCollections.unmodifiableObservableList(dependencies);
}
/**
* Implemented to unbind all dependencies and clear references to path providers.
*/
#Override
public void dispose() {
unbindAll();
provider = null;
}
}
To use in the OP's context:
// XSelectBinding
ObjectBinding<Boolean> xSelectBinding = new XSelectBinding<Model, Boolean>(
table.getSelectionModel().selectedItemProperty(),
item -> item.validProperty());
transferButton.disableProperty().bind(BooleanExpression.booleanExpression(xSelectBinding).not());
We have scenario where our application(spring boot, spring-cloud-stream based) listens to multiple Kafka topics (TOPIC_A with 3 partitions, TOPIC_B with 1 partition,TOPIC_C with 10 partitions) i.e. 3 #StreamListener methods.
#StreamListener(TopicASink.INPUT)
public void processTopicA(Message<String> msg) {
logger.info("** recieved message: {} ", msg.getPayload());
// do some processing
}
#StreamListener(TopicBSink.INPUT)
public void processTopicB(Message<String> msg) {
logger.info("** recieved message: {} ", msg.getPayload());
// do some processing
}
#StreamListener(TopicCSink.INPUT)
public void processTopicC(Message<String> msg) {
logger.info("** recieved message: {} ", msg.getPayload());
// do some processing
}
We need customize error handling and retry mechanism hence achieving this by configuring ConcurrentKafkaListenerContainerFactory bean.
#Bean
public ConcurrentKafkaListenerContainerFactory concurrentKafkaListenerContainerFactory(ConsumerFactory<Object,Object> consumerFactory) {
ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConcurrency(2); // we need to customize this per topic based on number of partitions
factory.setConsumerFactory(consumerFactory);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(10));
factory.setRetryTemplate(retryTemplate);
factory.setErrorHandler(new SeekToCurrentErrorHandler(new FixedBackOff(FixedBackOff.DEFAULT_INTERVAL, 10)));
return factory;
}
Problem is now we need some properties of the KafkaListenerContainer's to vary per #StreamListener (i.e. per topic in this case) , say to have a concurrency of 3 for TOPIC_A, 10 for TOPIC_C etc. instead of common concurrency set on the factory or set SeekToCurrentErrorHandler for TOPIC_A,TOPIC_C but not for TOPIC_B (or different ErrorHandler for some topics).
How can this be achieved per container level?
stack trace after trying solution with solution with reflection shared below
o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessagingException: Exception thrown while invoking com.jta.poc.kafkapoc.KafkaStreamPocApplication$MessageProcessor#processInput[1 args]; nested exception is com.jta.poc.kafkapoc.MyNewRetryableException, failedMessage=GenericMessage [payload=byte[35], headers={kafka_timestampType=CREATE_TIME, kafka_receivedTopic=new_input_topic, spanTraceId=e3382bf49eaa5343, spanId=e3382bf49eaa5343, nativeHeaders={spanTraceId=[e3382bf49eaa5343], spanId=[efc90644fc4c7dee], spanSampled=[0], X-B3-TraceId=[e3382bf49eaa5343], X-B3-SpanId=[efc90644fc4c7dee], X-B3-ParentSpanId=[e3382bf49eaa5343], spanParentSpanId=[e3382bf49eaa5343], X-B3-Sampled=[0]}, kafka_offset=26, X-B3-SpanId=e3382bf49eaa5343, kafka_consumer=org.apache.kafka.clients.consumer.KafkaConsumer#2a011bf8, X-B3-Sampled=0, X-B3-TraceId=e3382bf49eaa5343, id=3c86f652-f16e-2f59-1a59-f3d8601849f0, kafka_receivedPartitionId=1, spanSampled=0, kafka_receivedTimestamp=1586250896206, kafka_acknowledgment=Acknowledgment for ConsumerRecord(topic = new_input_topic, partition = 1, offset = 26, CreateTime = 1586250896206, serialized key size = -1, serialized value size = 35, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = [B#68df9f80), contentType=application/json, timestamp=1586274368357}]
at org.springframework.cloud.stream.binding.StreamListenerMessageHandler.handleRequestMessage(StreamListenerMessageHandler.java:63)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:109)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:158)
at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:132)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:105)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:73)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:445)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:394)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:181)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:160)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:47)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:108)
at org.springframework.integration.endpoint.MessageProducerSupport.sendMessage(MessageProducerSupport.java:203)
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter.access$300(KafkaMessageDrivenChannelAdapter.java:70)
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter$IntegrationRecordMessageListener.onMessage(KafkaMessageDrivenChannelAdapter.java:387)
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter$IntegrationRecordMessageListener.onMessage(KafkaMessageDrivenChannelAdapter.java:364)
at org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter.lambda$onMessage$0(RetryingMessageListenerAdapter.java:120)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:211)
at org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter.onMessage(RetryingMessageListenerAdapter.java:114)
at org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter.onMessage(RetryingMessageListenerAdapter.java:40)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:1071)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:1051)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:998)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:866)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:724)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:748)
Caused by: com.jta.poc.kafkapoc.MyNewRetryableException
at com.jta.poc.kafkapoc.KafkaStreamPocApplication$MessageProcessor.consumeMessage(KafkaStreamPocApplication.java:164)
at com.jta.poc.kafkapoc.KafkaStreamPocApplication$MessageProcessor.lambda$processInput$0(KafkaStreamPocApplication.java:107)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:164)
at com.jta.poc.kafkapoc.KafkaStreamPocApplication$MessageProcessor.processInput(KafkaStreamPocApplication.java:105)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:181)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:114)
at org.springframework.cloud.stream.binding.StreamListenerMessageHandler.handleRequestMessage(StreamListenerMessageHandler.java:55)
... 29 more
The container factory is not used in this context.
Add a ListenerContainerCustomizer #Bean.
#Bean
public ListenerContainerCustomizer<AbstractMessageListenerContainer<?, ?>> cust() {
return (container, destination, group) -> { ... };
}
As you can see, you get a reference to the container, the destination name and the group so you can figure out which binding it is being called for.
/**
* If a single bean of this type is in the application context, listener containers
* created by the binder can be further customized after all the properties are set. For
* example, to configure less-common properties.
*
* #param <T> container type
* #author Gary Russell
* #author Oleg Zhurakousky
* #since 2.1
*/
#FunctionalInterface
public interface ListenerContainerCustomizer<T> {
/**
* Configure the container that is being created for the supplied queue name and
* consumer group.
* #param container the container.
* #param destinationName the destination name.
* #param group the consumer group.
*/
void configure(T container, String destinationName, String group);
}
Set the error handler etc., on the container.
EDIT
Here is a hack for 2.0.x, if you don't mind using reflection; but bear in mind there was no support for a BackOff in the STCEH back then.
Also Boot 2.0 is end of life and hasn't been supported since April last year; so you really should upgrade.
#Bean
public SmartLifecycle bindingFixer(BindingService bindingService) {
return new SmartLifecycle() {
#Override
public int getPhase() {
return Integer.MAX_VALUE;
}
#Override
public void stop() {
// no op
}
#Override
public void start() {
#SuppressWarnings("unchecked")
Map<String, Binding> consumers = (Map<String, Binding>) new DirectFieldAccessor(bindingService)
.getPropertyValue("consumerBindings");
SeekToCurrentErrorHandler errorHandler = new SeekToCurrentErrorHandler();
((ConcurrentMessageListenerContainer<?, ?>) new DirectFieldAccessor(consumers.get("input"))
.getPropertyValue("lifecycle.messageListenerContainer")).getContainerProperties()
.setErrorHandler(errorHandler);
}
#Override
public boolean isRunning() {
return false;
}
#Override
public void stop(Runnable callback) {
callback.run();
}
#Override
public boolean isAutoStartup() {
return true;
}
};
New to WebFlux, reactive, and handlers. I've got things "working", but am not understanding why following code is returning "okay" with empty body, vice "not found".
Clarification: The issue-of-concern is in the final return statement of DemoPOJOHandler.getById(). The "short-circuit" code works as expected (i.e., returns "Bad Request" status), but the "switchIfEmpty" path of the final return statement does not appear to get exercised if a DemoPOJORepo.getById(int) returns Mono.empty().
(Note: I've hacked up a list-based "repo" to avoid dealing with database while figuring out handlers and http return types.)
Router implementation ("/v1" is a set of annotation based RESTful endpoints)...
#Configuration
public class DemoPOJORouter {
#Bean
public RouterFunction<ServerResponse> route(DemoPOJOHandler requestHandler) {
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoPOJO"), requestHandler::getAll)
.andRoute(RequestPredicates.GET("/DemoPOJO/{id}"), requestHandler::getById)
.andRoute(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
Handler implementation has been "stripped down" to only the code in question. I have a feeling that much of the style is "still imperative", but I've attempted to put the reactive stuff where it "makes the most sense".
If I supply a bad value on the URI (i.e., "foo"), then I get the http "bad request" returned. But, never seem to get the "not found" that should be generated by "switchIfEmpty" if a validly formatted int value is supplied, but it does not map to an entry in the repo.
#Component
public class DemoPOJOHandler {
public static final String PATH_VAR_ID = "id";
private DemoPOJORepo repo = null;
public Mono<ServerResponse> getById(ServerRequest request) {
Mono<DemoPOJO> monoDemoPOJO = null;
Map<String, String> pathVariables = request.pathVariables();
int id = -1;
checkRepoRef(); // part of the list hack
// short-circuit if request doesn't contain id (should never happen)
if ((pathVariables == null)
|| (!pathVariables.containsKey(PATH_VAR_ID))) {
return ServerResponse.badRequest().build();
}
// short-circuit if bad id value
try {
id = Integer.parseInt(pathVariables.get(PATH_VAR_ID));
} catch(NumberFormatException e) {
return ServerResponse.badRequest().build();
}
// get entity by keyValue
monoDemoPOJO = repo.getById(id);
return monoDemoPOJO
.flatMap(demoPOJO -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.syncBody(demoPOJO)
.switchIfEmpty(ServerResponse.notFound().build()));
}
}
Hack of a list-based repo to avoid dealing with data/APIs while working on handlers and http return types.
// local hack to avoid a database for testing
public class DemoPOJORepo {
private static DemoPOJORepo fpRepo = null;
private static int NUM_ROWS = 100;
private Map<Integer, DemoPOJO> fooPOJOMap;
private DemoPOJORepo() {
initMap();
}
public static DemoPOJORepo getInstance() {
if (fpRepo == null) {
fpRepo = new DemoPOJORepo();
}
return fpRepo;
}
public Mono<DemoPOJO> getById(int id) {
Mono<DemoPOJO> monoDP;
if (fooPOJOMap.containsKey(id)) {
monoDP = Mono.just(fooPOJOMap.get(id));
} else {
monoDP = Mono.empty();
}
return monoDP;
}
private Mono<Void> initMap() {
fooPOJOMap = new TreeMap<Integer, DemoPOJO>();
int offset = -1;
for(int ndx=0; ndx<NUM_ROWS; ndx++) {
offset = ndx + 1;
fooPOJOMap.put(offset, new DemoPOJO(offset, "foo_" + offset, offset+100));
}
return Mono.empty();
}
}
Your brackets are in the wrong place causing the swithIfEmpy to apply to the ServerResponse.ok() publisher not the monoDemoPOJO, replace the return with this and it should work:
return monoDemoPOJO
.flatMap(demoPOJO -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).syncBody(demoPOJO))
.switchIfEmpty(ServerResponse.notFound().build());
As I can see the code is right. The response code is Bad request because you are trying to convert "foo" to Integer, and when it throws an exception you are returning a Bad request response, so I think it works perfectly fine.
If you use an Integer id that is not present in your database then the answer must be a not found response
Say I have an enum
public enum E {A,B,C}
Is it possible to add another value, say D, by AspectJ?
After googling around, it seems that there used to be a way to hack the private static field $VALUES, then call the constructor(String, int) by reflection, but seems not working with 1.7 anymore.
Here are several links:
http://www.javaspecialists.eu/archive/Issue161.html (provided by #WimDeblauwe )
and this: http://www.jroller.com/VelkaVrana/entry/modify_enum_with_reflection
Actually, I recommend you to refactor the source code, maybe adding a collection of valid region IDs to each enumeration value. This should be straightforward enough for subsequent merging if you use Git and not some old-school SCM tool like SVN.
Maybe it would even make sense to use a dynamic data structure altogether instead of an enum if it is clear that in the future the list of commands is dynamic. But that should go into the upstream code base. I am sure the devs will accept a good patch or pull request if prepared cleanly.
Remember: Trying to avoid refactoring is usually a bad smell, a symptom of an illness, not a solution. I prefer solutions to symptomatic workarounds. Clean code rules and software craftsmanship attitude demand that.
Having said the above, now here is what you can do. It should work under JDK 7/8 and I found it on Jérôme Kehrli's blog (please be sure to add the bugfix mentioned in one of the comments below the article).
Enum extender utility:
package de.scrum_master.util;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;
public class DynamicEnumExtender {
private static ReflectionFactory reflectionFactory =
ReflectionFactory.getReflectionFactory();
private static void setFailsafeFieldValue(Field field, Object target, Object value)
throws NoSuchFieldException, IllegalAccessException
{
// let's make the field accessible
field.setAccessible(true);
// next we change the modifier in the Field instance to
// not be final anymore, thus tricking reflection into
// letting us modify the static final field
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
int modifiers = modifiersField.getInt(field);
// blank out the final bit in the modifiers int
modifiers &= ~Modifier.FINAL;
modifiersField.setInt(field, modifiers);
FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
fa.set(target, value);
}
private static void blankField(Class<?> enumClass, String fieldName)
throws NoSuchFieldException, IllegalAccessException
{
for (Field field : Class.class.getDeclaredFields()) {
if (field.getName().contains(fieldName)) {
AccessibleObject.setAccessible(new Field[] { field }, true);
setFailsafeFieldValue(field, enumClass, null);
break;
}
}
}
private static void cleanEnumCache(Class<?> enumClass)
throws NoSuchFieldException, IllegalAccessException
{
blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
blankField(enumClass, "enumConstants"); // IBM JDK
}
private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes)
throws NoSuchMethodException
{
Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
parameterTypes[0] = String.class;
parameterTypes[1] = int.class;
System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
return reflectionFactory.newConstructorAccessor(enumClass .getDeclaredConstructor(parameterTypes));
}
private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues)
throws Exception
{
Object[] parms = new Object[additionalValues.length + 2];
parms[0] = value;
parms[1] = Integer.valueOf(ordinal);
System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
}
/**
* Add an enum instance to the enum class given as argument
*
* #param <T> the type of the enum (implicit)
* #param enumType the class of the enum to be modified
* #param enumName the name of the new enum instance to be added to the class
*/
#SuppressWarnings("unchecked")
public static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName) {
// 0. Sanity checks
if (!Enum.class.isAssignableFrom(enumType))
throw new RuntimeException("class " + enumType + " is not an instance of Enum");
// 1. Lookup "$VALUES" holder in enum class and get previous enum
// instances
Field valuesField = null;
Field[] fields = enumType.getDeclaredFields();
for (Field field : fields) {
if (field.getName().contains("$VALUES")) {
valuesField = field;
break;
}
}
AccessibleObject.setAccessible(new Field[] { valuesField }, true);
try {
// 2. Copy it
T[] previousValues = (T[]) valuesField.get(enumType);
List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
// 3. build new enum
T newValue = (T) makeEnum(
enumType, // The target enum class
enumName, // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED
values.size(), new Class<?>[] {}, // could be used to pass values to the enum constuctor if needed
new Object[] {} // could be used to pass values to the enum constuctor if needed
);
// 4. add new value
values.add(newValue);
// 5. Set new values field
setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));
// 6. Clean enum cache
cleanEnumCache(enumType);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage(), e);
}
}
}
Sample application & enum:
package de.scrum_master.app;
/** In honour of "The Secret of Monkey Island"... ;-) */
public enum Command {
OPEN, CLOSE, PUSH, PULL, WALK_TO, PICK_UP, TALK_TO, GIVE, USE, LOOK_AT, TURN_ON, TURN_OFF
}
package de.scrum_master.app;
public class Server {
public void executeCommand(Command command) {
System.out.println("Executing command " + command);
}
}
package de.scrum_master.app;
public class Client {
private Server server;
public Client(Server server) {
this.server = server;
}
public void issueCommand(String command) {
server.executeCommand(
Command.valueOf(
command.toUpperCase().replace(' ', '_')
)
);
}
public static void main(String[] args) {
Client client = new Client(new Server());
client.issueCommand("use");
client.issueCommand("walk to");
client.issueCommand("undress");
client.issueCommand("sleep");
}
}
Console output with original enum:
Executing command USE
Executing command WALK_TO
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant de.scrum_master.app.Command.UNDRESS
at java.lang.Enum.valueOf(Enum.java:236)
at de.scrum_master.app.Command.valueOf(Command.java:1)
at de.scrum_master.app.Client.issueCommand(Client.java:12)
at de.scrum_master.app.Client.main(Client.java:22)
Now you can either add an aspect with an advice executed after the enum class was loaded or just call this manually in your application before extended enum values are to be used for the first time. Here I am showing how it can be done in an aspect.
Enum extender aspect:
package de.scrum_master.aspect;
import de.scrum_master.app.Command;
import de.scrum_master.util.DynamicEnumExtender;
public aspect CommandExtender {
after() : staticinitialization(Command) {
System.out.println(thisJoinPoint);
DynamicEnumExtender.addEnum(Command.class, "UNDRESS");
DynamicEnumExtender.addEnum(Command.class, "SLEEP");
DynamicEnumExtender.addEnum(Command.class, "WAKE_UP");
DynamicEnumExtender.addEnum(Command.class, "DRESS");
}
}
Console output with extended enum:
staticinitialization(de.scrum_master.app.Command.<clinit>)
Executing command USE
Executing command WALK_TO
Executing command UNDRESS
Executing command SLEEP
Et voilà! ;-)
I use an external library which return some List<?>.
I need to check if each object of this list is an Object of the JDK (String, int, Integer...).
Is this a proper solution?
List<?> list = externalLibrary.search(...);
for(clazz : list) {
if (clazz.getPackage().getName().startsWith("java.lang"))
// do something different
}
Is there a better one?
Depending on your definition of "object of the JDK" -- which could get quite fuzzy around the edges -- no, this isn't going to do it. The java.lang package is only a tiny part of all the classes included in the JDK.
You might check whether each object was loaded by the same ClassLoader that loaded java.lang.String -- i.e.,
if (theObject.getClass().getClassLoader() == "".getClass().getClassLoader()) ...
In general, a different ClassLoader will be used for system classes vs. application classes.
It is probably OK, just you have to check the following packages:
java
javax
com.sun
sun
probably others...
We use the below class to check if the classes belongs to JDK
public class JDKClass {
private static Set<String> CS = new HashSet<String>();
static {
try {
File file = new File(System.getProperty("java.home"),
"lib/classlist");
BufferedReader r = new BufferedReader(new FileReader(file));
String l;
while (true) {
l = r.readLine();
if (l == null) {
break;
} else {
CS.add(l.replace('/', '.'));
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static boolean contains(String o) {
return CS.contains(o) || o.startsWith("java") || o.startsWith("com.sun")
|| o.startsWith("sun") || o.startsWith("oracle")
|| o.startsWith("org.xml") || o.startsWith("com.oracle");
}
private JDKClass() {
}
}
You can use ClassLoader.getSystemResources and then check from what jar is the class loaded (f.g. if it comes from rt.jar).
You will get URL's such as:
jar:file:/C:/Users/user/.m2/repository/org/slf4j/slf4j-log4j12/1.6.1/slf4j-log4j12-1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class
Example code taken from SLF4j:
private static String STATIC_LOGGER_BINDER_PATH =
"org/slf4j/impl/StaticLoggerBinder.class";
private static void singleImplementationSanityCheck() {
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class
.getClassLoader();
Enumeration paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader
.getResources(STATIC_LOGGER_BINDER_PATH);
}
List implementationList = new ArrayList();
while (paths.hasMoreElements()) {
URL path = (URL) paths.nextElement();
implementationList.add(path);
}
....
}
Personally I like class loader base answer. But it will return true also on StringBuilder. If you want to more narrow definition that is only "built-in" types, you can try to evaluate whether this is primitive type (such as int) or wrapper type (such as Integer) or String. You can write something like this:
import java.util.Map;
import java.util.TreeMap;
public class Utils {
private static Map<String, Class<?>> SUBST_MAP = new TreeMap<String, Class<?>>();
private static Map<String, Class<?>> SIMPLE_MAP = new TreeMap<String, Class<?>>();
static {
SUBST_MAP.put(Byte.class.getName(), Byte.TYPE);
SUBST_MAP.put(Short.class.getName(), Short.TYPE);
SUBST_MAP.put(Integer.class.getName(), Integer.TYPE);
SUBST_MAP.put(Long.class.getName(), Long.TYPE);
SUBST_MAP.put(Float.class.getName(), Float.TYPE);
SUBST_MAP.put(Double.class.getName(), Double.TYPE);
SUBST_MAP.put(Boolean.class.getName(), Boolean.TYPE);
SUBST_MAP.put(Character.class.getName(), Character.TYPE);
SIMPLE_MAP.put(String.class.getName(), Boolean.TRUE);
}
/**
* Gets the the class type of the types of the argument.
*
* if substPrimitiveWrapper is true,
* then if there is argument, that represent primitive type wrapper (such as Integer),
* then it will be substituted to primitive type (such as int).
* else no substitution will be done.
*
* #param arg object.
* #param substPrimitiveWrapper - wheteher to do primitive type substitution.
* #retrun class type.
*/
public static Class<?> getClassType(Object arg, boolean substPrimitiveWrapper){
Class<?> classType = null;
String className = null;
Class<?> substClass = null;
if(arg != null ){
//making default classType
classType = arg.getClass();
if(substPrimitiveWrapper){
className = classType.getName();
substClass = (Class<?>)SUBST_MAP.get(className);
if(substClass != null){
classType = substClass;
}
}
}
return classType;
}
/**
* This method consider JDK type any primitive type, wrapper class or String.
*
*
* #param arg object
* #return where arg is JDK type or now.
*/
public static boolean isJDKClass(Object arg){
Class<?> classType = getClassType(arg, true);
boolean isJDKClass = false;
if(classType!=null){
//if(String.class.equals(classType)){
// isJDKClass = true; //this is String, note that String is final
//}
assert classType!=null;
String className = classType.getName();
Boolean isFound = (Boolean)SIMPLE_MAP.get(className);
if(Boolean.TRUE.equals(isFound)){
isJDKClass = true; //this is predefined class
}
boolean isPrimitiveType = classType.isPrimitive();
if(isPrimitiveType){
isJDKClass = true; //this is primitive type or wrapper class
}
}
return isJDKClass;
}
}
You can also optionally add support for such classes like java.math.BigDecimal, java.util.Date, java.sql.Timestamp. Note, however, that they are not final, so I assumed that if somebody extended them even in the trivial way, it will not be considered as JDK class.
I think an easier solution is to thing of the problem this way:
write a method to identify all classes that are defined by you. In most cases, all user defined classes follow a pattern like com.something.something. Then if they do not belong to com.something.something, it is a JDK class