Unit test failing for custom processor's 'optional' properties - apache-nifi

To create a custom processor, I followed the documentation.
I made the necessary code changes in the MyProcessor.java and the MyProcessorTest runs fine except when I try to use some 'optional' properties. Note : I tried all the builder methods like required(false), addValidator() etc. for the optional properties, in vain. Actually, a validator doesn't make sense for an optional property ...
MyProcessor.java
#Tags({ "example" })
#CapabilityDescription("Provide a description")
#SeeAlso({})
#ReadsAttributes({ #ReadsAttribute(attribute = "", description = "") })
#WritesAttributes({ #WritesAttribute(attribute = "", description = "") })
#Stateful(description = "After a db-level LSN is processed, the same should be persisted as the last processed LSN", scopes = { Scope.CLUSTER })
public class MyProcessor extends AbstractProcessor {
public static final Relationship REL_SUCCESS = new Relationship.Builder()
.name("success")
.description(
"Successfully created FlowFile from SQL query result set.")
.build();
public static final Relationship REL_FAILURE = new Relationship.Builder()
.name("failure").description("SQL query execution failed. ???")
.build();
/* Start : Mandatory properties */
public static final PropertyDescriptor DBCP_SERVICE = new PropertyDescriptor.Builder()
.name("Database Connection Pooling Service")
.description(
"The Controller Service that is used to obtain connection to database")
.required(true).identifiesControllerService(DBCPService.class)
.build();
public static final PropertyDescriptor CONTAINER_DB = new PropertyDescriptor.Builder()
.name("containerDB").displayName("Container Database")
.description("The name of the container database").required(true)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
...
...more mandatory properties
...
/* End : Mandatory properties */
/*Start : Optional properties */
public static final PropertyDescriptor CDC_TS_FROM = new PropertyDescriptor.Builder()
.name("cdcTSFrom").displayName("Load CDC on or after")
.description("The CDC on or after this datetime will be fetched.")
.required(false).defaultValue(null).build();
public static final PropertyDescriptor SCHEMA = new PropertyDescriptor.Builder()
.name("schema").displayName("DB Schema")
.description("The schema which contains the xxxxxx")
.defaultValue(null).required(false).build();
/*End : Optional properties */
private List<PropertyDescriptor> descriptors;
private Set<Relationship> relationships;
#Override
protected void init(final ProcessorInitializationContext context) {
final List<PropertyDescriptor> descriptors = new ArrayList<PropertyDescriptor>();
descriptors.add(CONTAINER_DB);
descriptors.add(DBCP_SERVICE);
...
...
...
descriptors.add(CDC_TS_FROM);
descriptors.add(SCHEMA);
...
...
...
this.descriptors = Collections.unmodifiableList(descriptors);
final Set<Relationship> relationships = new HashSet<Relationship>();
relationships.add(REL_FAILURE);
relationships.add(REL_SUCCESS);
this.relationships = Collections.unmodifiableSet(relationships);
}
#Override
public Set<Relationship> getRelationships() {
return this.relationships;
}
#Override
public final List<PropertyDescriptor> getSupportedPropertyDescriptors() {
return descriptors;
}
// TODO : Check if the component lifecycle methods esp. onScheduled() and
// onShutDown() are required
#Override
public void onTrigger(final ProcessContext context,
final ProcessSession session) throws ProcessException {
...
...
...
}
}
MyProcessorTest.java
public class MyProcessorTest {
private TestRunner testRunner;
private final String CONTAINER_DB = "test";
private final String DBCP_SERVICE = "test_dbcp";
...
...
...
private final String SCHEMA = "dbo";
private final String CDC_TS_FROM = "";
...
...
...
#Before
public void init() throws InitializationException {
testRunner = TestRunners.newTestRunner(MyProcessor.class);
final DBCPService dbcp = new DBCPServiceSQLServerImpl(...);
final Map<String, String> dbcpProperties = new HashMap<>();
testRunner = TestRunners.newTestRunner(MyProcessor.class);
testRunner.addControllerService(DBCP_SERVICE, dbcp, dbcpProperties);
testRunner.enableControllerService(dbcp);
testRunner.assertValid(dbcp);
testRunner.setProperty(MyProcessor.DBCP_SERVICE, DBCP_SERVICE);
testRunner.setProperty(MyProcessor.CONTAINER_DB, CONTAINER_DB);
...
...
...
testRunner.setProperty(MyProcessor.CDC_TS_FROM, CDC_TS_FROM);
testRunner.setProperty(MyProcessor.SCHEMA, SCHEMA);
...
...
...
}
#Test
public void testProcessor() {
testRunner.run();
}
/**
* Simple implementation only for MyProcessor processor testing.
*/
private class DBCPServiceSQLServerImpl extends AbstractControllerService
implements DBCPService {
private static final String SQL_SERVER_CONNECT_URL = "jdbc:sqlserver://%s;database=%s";
private String containerDB;
private String password;
private String userName;
private String dbHost;
public DBCPServiceSQLServerImpl(String containerDB, String password,
String userName, String dbHost) {
super();
this.containerDB = containerDB;
this.password = password;
this.userName = userName;
this.dbHost = dbHost;
}
#Override
public String getIdentifier() {
return DBCP_SERVICE;
}
#Override
public Connection getConnection() throws ProcessException {
try {
Connection connection = DriverManager.getConnection(String
.format(SQL_SERVER_CONNECT_URL, dbHost, containerDB),
userName, password);
return connection;
} catch (final Exception e) {
throw new ProcessException("getConnection failed: " + e);
}
}
}
}
Now if I comment the optional properties in the test class :
//testRunner.setProperty(MyProcessor.CDC_TS_FROM, CDC_TS_FROM);
//testRunner.setProperty(MyProcessor.SCHEMA, SCHEMA);
, the test completes normally but if I enable any or all of the optional properties, say, CDC_TS_FROM, then I the test case assertion fails, no matter what value I put for CDC_TS_FROM :
java.lang.AssertionError: Processor has 1 validation failures:
'cdcTSFrom' validated against '' is invalid because 'cdcTSFrom' is not a supported property
at org.junit.Assert.fail(Assert.java:88)
at org.apache.nifi.util.MockProcessContext.assertValid(MockProcessContext.java:251)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:161)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:152)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:147)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:142)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:137)
at processors.NiFiCDCPoC.sqlserver.MyProcessorTest.testProcessor(MyProcessorTest.java:74)
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:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Edit-1 :
I added two(?) validators :
public static final PropertyDescriptor CDC_TS_FROM = new PropertyDescriptor.Builder()
.name("cdcTSFrom").displayName("Load CDC on or after")
.description("The CDC on or after this datetime will be fetched.")
.required(false).defaultValue(null).addValidator(Validator.VALID)
.addValidator(StandardValidators.TIME_PERIOD_VALIDATOR).build();
Error :
java.lang.AssertionError: Processor has 1 validation failures:
'cdcTSFrom' validated against '2017-03-06 10:00:00' is invalid because Must be of format <duration> <TimeUnit> where <duration> is a non-negative integer and TimeUnit is a supported Time Unit, such as: nanos, millis, secs, mins, hrs, days

All Property Descriptors (required or optional) must have a Validator set explicitly, otherwise it will return the error you are seeing. It appears you are not looking to perform validation, but you still must set a validator, so on your optional properties add the following to the builder:
.addValidator(Validator.VALID)
EDIT (see comments below): Marking the PropertyDescriptor as required(false) allows it to be an optional property and thus can have no value specified. If the user enters a value, and you want to validate that against certain rules, you can add that particular Validator (or write your own and add that). For a Time Period (2 seconds, e.g.), and for other cases, there are a set of built-in validators, for example allowing only values between 2 and 20 seconds:
.addValidator(StandardValidators.createTimePeriodValidator(
2, TimeUnit.SECONDS, 20, TimeUnit.SECONDS
))

Related

Spring boot & mongodb, domain class issue with findAll() and findById()

I am implementing a spring boot service with an underlying mongodb database for data storage.
In this service I have to use some domain classes that comes from another library which I can not alter. Unfortunately these classes have instance variables marked as final. For example here is the Discount domain class:
#Value
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#SuperBuilder(toBuilder = true)
#JsonDeserialize(builder = Discount.DiscountBuilderImpl.class)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public class Discount extends AbstractEntityBase {
#NonNull
UUID id;
#NonNull
String no;
#JsonProperty("iNo")
Integer iNo;
#NonNull
LocalizedTexts designation;
LocalizedTexts printText;
boolean isAutomatic;
LocalDate validFromDate;
LocalDate validToDate;
LocalTime validFromTime;
LocalTime validToTime;
String checkScriptParameters;
ReferenceScript checkScript;
#NonNull
ReferenceScript calculationScript;
public static DiscountBuilder<?, ?> builder(UUID id,
String no,
LocalizedTexts designation,
ReferenceScript calculationScript,
boolean isAutomatic) {
return new DiscountBuilderImpl()
.id(id)
.no(no)
.designation(designation)
.calculationScript(calculationScript)
.isAutomatic(isAutomatic);
}
/**
* Overwritten getters for optional properties
*/
...
#JsonPOJOBuilder(withPrefix = "")
#JsonIgnoreProperties(ignoreUnknown = true)
public static final class DiscountBuilderImpl
extends DiscountBuilder<Discount, DiscountBuilderImpl> {
}
}
In my test class I am testing save and find (findAll, findById) operations. Save operations work fine but I have issues with findAll and findById methods. Here is such a test class:
#RunWith(SpringRunner.class)
#DataMongoTest
public class TestDiscountRepository {
#Autowired
DiscountRepository discountRepository;
#Before
public void init(){
}
#After
public void resetMongoDb() {
//discountRepository.deleteAll();
}
#Test
public void save_success() {
UUID id = UUID.randomUUID();
...
Discount discount = Discount.builder(id, no, designation, calculationScript, true)
.iNo(iNo)
.printText(printText)
.isAutomatic(isAutomatic)
.validFromDate(validFromDate)
.validToDate(validToDate)
.validFromTime(validFromTime)
.validToTime(validToTime)
.checkScriptParameters(checkScriptParameters)
.checkScript(checkScript)
.build();
Discount savedDiscount = discountRepository.save(discount);
assertThat(savedDiscount).isNotNull();
assertThat(savedDiscount.getId()).isNotNull();
}
#Test
public void save_bulk_success() {
UUID id1 = UUID.randomUUID();
...
UUID id2 = UUID.randomUUID();
...
UUID id3 = UUID.randomUUID();
...
List<Discount> discounts = Arrays.asList(
Discount.builder(id1, no1, designation1, calculationScript1, true)
...
.build(),
Discount.builder(id2, no2, designation2, calculationScript2, true)
...
.build(),
Discount.builder(id3, no3, designation3, calculationScript3, true)
...
.build()
);
List<Discount> allDiscounts = discountRepository.saveAll(discounts);
AtomicInteger validIdFound = new AtomicInteger();
allDiscounts.forEach(discount -> {
if(discount.getId() != null) {
validIdFound.getAndIncrement();
}
});
assertThat(validIdFound.intValue()).isEqualTo(3);
}
#Test
public void find_all_success() {
List<Discount> discounts = discountRepository.findAll();
Assert.assertNotNull(discounts);
}
#Test
public void find_by_id_success() {
UUID id = UUID.randomUUID();
String no = "900";
...
Discount discount = Discount.builder(id, no, designation, calculationScript, true)
...
.build();
Discount savedDiscount = discountRepository.save(discount);
Optional<Discount> result = discountRepository.findById(savedDiscount.getId());
Assert.assertNotNull(result.get());
}
}
And here is the exception I am getting when I try to run/test findAll() and findById():
java.lang.IllegalStateException: Cannot set property id because no setter, no wither and it's not part of the persistence constructor private net.mspos.possible.svc_pos_controller_data.entity.Discount()!
at org.springframework.data.mapping.model.InstantiationAwarePropertyAccessor.setProperty(InstantiationAwarePropertyAccessor.java:118)
at org.springframework.data.mapping.model.ConvertingPropertyAccessor.setProperty(ConvertingPropertyAccessor.java:64)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readAndPopulateIdentifier(MappingMongoConverter.java:450)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.populateProperties(MappingMongoConverter.java:418)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:394)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readDocument(MappingMongoConverter.java:356)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:292)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:288)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:107)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDocumentCallback.doWith(MongoTemplate.java:3207)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindOneInternal(MongoTemplate.java:2822)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2529)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2499)
at org.springframework.data.mongodb.core.MongoTemplate.findById(MongoTemplate.java:888)
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.findById(SimpleMongoRepository.java:132)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:639)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at jdk.proxy2/jdk.proxy2.$Proxy93.findById(Unknown Source)
at net.mspos.possible.svc_pos_controller_data.repository.TestDiscountRepository.find_by_id_success(TestDiscountRepository.java:209)
Here is also how a document into the database looks like:
Any ideas on how I could overcome this issue? Unfortunately altering the domain classes is not an option.
Finally the easiest and more convenient solution in my case is to avoid using repositories to map the domain classes and provide basic CRUD operations.
Instead of that I am using the MongoCollection interface alongside with ObjectMapper to serialize/deserialize objects into mongodb documents for all CRUD operations. For instance a relevant service for a domain class could look like:
#Service
#EnableConfigurationProperties(MongoProperties.class)
public class DiscountMongoService implements BasicMongoService {
private MongoProperties mongoProperties;
private CustomMongoConfig customMongoConfig;
private BusinessEntityMapper mapper;
private MongoCollection<Document> discountCollection;
#Autowired
public DiscountMongoService(MongoProperties mongoProperties, CustomMongoConfig customMongoConfig, BusinessEntityMapper mapper) {
this.mongoProperties = mongoProperties;
this.customMongoConfig = customMongoConfig;
this.mapper = mapper;
discountCollection =
this.customMongoConfig.mongoClient().getDatabase(this.mongoProperties.getDatabase()).getCollection(Discount.class.getSimpleName());
}
// Insert
public boolean insertOne(Discount discount) throws JsonProcessingException {
Document discountDoc = transformEntityIntoDocumentWithMongodbId(discount);
InsertOneResult result = discountCollection.insertOne(discountDoc);
System.out.println(result);
return result.wasAcknowledged();
}
....
// Find
public List<Discount> findAll() {
List<Discount> discounts = new ArrayList<>();
discountCollection.find().forEach(dd -> {
Discount discount;
try {
discount = (Discount) mapper.getBusinessEntityFromJSON(dd.toJson(),Discount.class);
discounts.add(discount);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
});
return discounts;
}

register webhook tg bot using spring boot

I am using telegrambots-spring-boot-starter v 5.2.0 and trying register my bot
Here's my bot config:
bot.url=https://74e437885ee9.ngrok.io
bot.path=adam
#Slf4j
#Configuration
public class BotConfig {
#Value("${bot.url}")
private String BOT_URL;
#Bean
public SetWebhook setWebhookInstance() {
return SetWebhook.builder().url(BOT_URL).build();
}
// Create it as
#Bean
public AdamSmithBot adamSmithBot(SetWebhook setWebhookInstance) throws TelegramApiException {
AdamSmithBot adamSmithBot = new AdamSmithBot(setWebhookInstance);
// DefaultWebhook defaultWebhook = new DefaultWebhook();
// defaultWebhook.setInternalUrl(BOT_URL);
// defaultWebhook.registerWebhook(adamSmithBot);
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
log.info("SetWebHook from AdamSmith bot {}", adamSmithBot.getSetWebhook());
telegramBotsApi.registerBot(adamSmithBot, adamSmithBot.getSetWebhook());
return adamSmithBot;
}
}
But it dont working, but when i send this request, it working perfectly and updates recieve to me
https://api.telegram.org/MY_TOKEN_HERE/setWebhook?url=https://74e437885ee9.ngrok.io
I think my mistake in BotConfig,but i also publush my other clases bot and controller:
public class AdamSmithBot extends SpringWebhookBot {
#Value("${bot.token}")
private String TOKEN;
#Value("${bot.name}")
private String BOT_USERNAME;
#Value("${bot.path}")
private String BOT_PATH;
public AdamSmithBot(SetWebhook setWebhook) {
super(setWebhook);
}
public AdamSmithBot(DefaultBotOptions options, SetWebhook setWebhook) {
super(options, setWebhook);
}
#Override
public String getBotUsername() {
return BOT_USERNAME;
}
#Override
public String getBotToken() {
return TOKEN;
}
#Override
public BotApiMethod<?> onWebhookUpdateReceived(Update update) {
if (update.getMessage() != null && update.getMessage().hasText()) {
Long chatId = update.getMessage().getChatId();
try {
execute(new SendMessage(chatId.toString(), "HI HANDSOME " + update.getMessage().getText()));
} catch (TelegramApiException e) {
throw new IllegalStateException(e);
}
}
return null;
}
#Override
public String getBotPath() {
return "adam";
}
}
Controller:
#Slf4j
#RestController
public class WebHookBotRecieveController {
#Autowired
private AdamSmithBot adamSmithBot;
#PostMapping("/")
public void getUpdate(#RequestBody Update update){
log.info("some update recieved {}",update.toString());
adamSmithBot.onWebhookUpdateReceived(update);
}
#PostMapping("/callback/adam")
public void getUpdateWithDifferentUrl(#RequestBody Update update){
log.info("some update recieved {}",update.toString());
adamSmithBot.onWebhookUpdateReceived(update);
}
}
NOTE: I seemd some info here:
https://github.com/rubenlagus/TelegramBots/wiki/How-To-Update
They do that:
https://i.stack.imgur.com/9JKRT.png
But when i trying put DefaultWebhook class instead it produce NullPointerException
What i made wrong?
EDIT : I refactored some code
#Value("${bot.url}") private String BOT_URL; - where was null value (fixed),reloaded library, but now i have that exception:
Caused by: javax.ws.rs.ProcessingException: Failed to start Grizzly HTTP server: Cannot assign requested address: bind
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:270) ~[jersey-container-grizzly2-http-2.33.jar:na]
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:93) ~[jersey-container-grizzly2-http-2.33.jar:na]
at org.telegram.telegrambots.updatesreceivers.DefaultWebhook.startServer(DefaultWebhook.java:64) ~[telegrambots-5.2.0.jar:na]
at org.telegram.telegrambots.meta.TelegramBotsApi.<init>(TelegramBotsApi.java:50) ~[telegrambots-meta-5.2.0.jar:na]
at ru.website.selenium.bot.telegram.config.BotConfig.adamSmithBot(BotConfig.java:44) ~[classes/:na]
at ru.website.selenium.bot.telegram.config.BotConfig$$EnhancerBySpringCGLIB$$4eb8259d.CGLIB$adamSmithBot$0(<generated>) ~[classes/:na]
at ru.website.selenium.bot.telegram.config.BotConfig$$EnhancerBySpringCGLIB$$4eb8259d$$FastClassBySpringCGLIB$$1e185cfd.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.3.8.jar:5.3.8]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.8.jar:5.3.8]
at ru.website.selenium.bot.telegram.config.BotConfig$$EnhancerBySpringCGLIB$$4eb8259d.adamSmithBot(<generated>) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.8.jar:5.3.8]
... 39 common frames omitted
Caused by: java.net.BindException: Cannot assign requested address: bind
at java.base/sun.nio.ch.Net.bind0(Native Method) ~[na:na]
at java.base/sun.nio.ch.Net.bind(Net.java:461) ~[na:na]
at java.base/sun.nio.ch.Net.bind(Net.java:453) ~[na:na]
at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:227) ~[na:na]
at java.base/sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:80) ~[na:na]
at org.glassfish.grizzly.nio.transport.TCPNIOBindingHandler.bindToChannelAndAddress(TCPNIOBindingHandler.java:107) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.nio.transport.TCPNIOBindingHandler.bind(TCPNIOBindingHandler.java:64) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.bind(TCPNIOTransport.java:215) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.bind(TCPNIOTransport.java:195) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.bind(TCPNIOTransport.java:186) ~[grizzly-framework-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.http.server.NetworkListener.start(NetworkListener.java:711) ~[grizzly-http-server-2.4.4.jar:2.4.4]
at org.glassfish.grizzly.http.server.HttpServer.start(HttpServer.java:256) ~[grizzly-http-server-2.4.4.jar:2.4.4]
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:267) ~[jersey-container-grizzly2-http-2.33.jar:na]
... 53 common frames omitted
Process finished with exit code 0
Okay, I get it, I didn't know that TelegramBotApi runs a grizzly server underneath and that was the reason for my mistakes
For those who ever come here with the same problem, I will describe the solution :
Let's start with the fact that for the test on localhost we need a public address , e.g. ngrok , then we have to do the following, start grizzli on , let's say port 80 (localhost), specify the correct ngrok url , and then run spring boot application on , let's say port 8080, examples in the code
For ex. ngrok forwarding:
Forwarding http://b44ecce72666.ngrok.io -> http://localhost:8080
Forwarding https://b44ecce72666.ngrok.io -> http://localhost:8080
#Slf4j
#Configuration
public class BotConfig {
// #Value("${bot.url}")
// #NotNull
// #NotEmpty
// private String BOT_URL;
#Value("${bot.token}")
#NotNull
#NotEmpty
private String TOKEN;
#Value("${bot.name}")
#NotNull
#NotEmpty
private String BOT_USERNAME;
#Bean
public SetWebhook setWebhookInstance() {
return SetWebhook.builder().url("https://b44ecce72666.ngrok.io").build();
} // public address, now it is ngrok, in the future it will (i think) be the server address
// Create it as
#Bean
public AdamSmithBot adamSmithBot(SetWebhook setWebhookInstance) throws TelegramApiException {
AdamSmithBot adamSmithBot = new AdamSmithBot(setWebhookInstance);
adamSmithBot.setBOT_USERNAME(BOT_USERNAME);
adamSmithBot.setTOKEN(TOKEN);
adamSmithBot.setBOT_PATH("adam");
DefaultWebhook defaultWebhook = new DefaultWebhook();
defaultWebhook.setInternalUrl(
"http://localhost:80"); // the port to start the server, on the localhost computer, on the server it
// be the server address
// defaultWebhook.registerWebhook(adamSmithBot);
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class, defaultWebhook);
log.info("SetWebHook from AdamSmith bot {}", setWebhookInstance);
adamSmithBot.getBotUsername();
telegramBotsApi.registerBot(adamSmithBot, setWebhookInstance);
return adamSmithBot;
}
}
Next is code of webhook bot:
#Slf4j
#Setter
public class AdamSmithBot extends SpringWebhookBot {
#Value("${bot.token}")
private String TOKEN;
#Value("${bot.name}")
private String BOT_USERNAME;
#Value("${bot.path}")
private String BOT_PATH;
#Autowired
private MainService mainService;
public AdamSmithBot(SetWebhook setWebhook) {
super(setWebhook);
}
public AdamSmithBot(DefaultBotOptions options, SetWebhook setWebhook) {
super(options, setWebhook);
}
#Override
public String getBotUsername() {
log.info("BOT_USERNAME FROM ADAM BOT {}",BOT_USERNAME);
return BOT_USERNAME;
}
#Override
public String getBotToken() {
return TOKEN;
}
#Override
public BotApiMethod<?> onWebhookUpdateReceived(Update update) { //All messages coming from the grizzly server will trigger this method
log.info("Message teext {}",update.toString());
if (update.getMessage() != null && update.getMessage().hasText()) {
Long chatId = update.getMessage().getChatId();
List<PartialBotApiMethod<Message>> listOfCommands= mainService.receiveUpdate(update);
listOfCommands.forEach(x->{
try {
if(x instanceof SendMessage){
execute((SendMessage)x);
}
if(x instanceof SendPhoto){
execute((SendPhoto) x);
}
} catch (TelegramApiException e) {
e.printStackTrace();
}
});
}
return null;
}
#Override
public String getBotPath() {
return "adam"; //any other path here
}
}
My code is excessive in some places, and too fancy, but it's not hard to figure out. If you have any suggestions on how to make it better, I'd love to hear them.
This was the most helpful question and answer from #handsome so decided to post here some more additional info that was not so evident for me at the beginning.
#Configuration
public class BotConfig {
#Value("${telegram.webhook-host}")
private String webhookHost;
#Value("${telegram.endpoint}")
private String botEndpoint;
#Value("${telegram.token}")
private String botToken;
#Value("${telegram.username}")
private String botUsername;
#Bean
public SetWebhook setWebhookInstance() {
return SetWebhook.builder().url(webhookHost).build();
}
#Bean
public ParserBot getBot(SetWebhook setWebhook) {
ParserBot parserBot = new ParserBot(setWebhook);
parserBot.setBotPath(botEndpoint);
parserBot.setBotToken(botToken);
parserBot.setBotUsername(botUsername);
try {
DefaultWebhook defaultWebhook = new DefaultWebhook();
defaultWebhook.setInternalUrl("http://localhost:80");
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class, defaultWebhook);
telegramBotsApi.registerBot(parserBot, parserBot.getSetWebhook());
} catch (TelegramApiException e) {
throw new RuntimeException("Can't register telegram bot", e);
}
return parserBot;
}
}
Most of the info is clear but sometimes it is necessary to say:
You can register webhook endpoint manually by calling https://api.telegram.org/bot<your_token>/setWebHook?url=<your_url>
In this case your URL can be any path you want. If you register your webhook endpoint using TelegramBotsApi, then your endpoint will look like https://host/callback/ , as example
telegram.webhook-host=https://12345124.eu.ngrok.io
telegram.endpoint="webhook"
then your endpoint is https://12345124.eu.ngrok.io/callback/webhook
Yes, you can't set any path to your endpoint but you don't need to set it manually
Another thing. To activate WebhookBot in application you have to use DefaultWebhook to activate TelegramBotsApi.useWebhook property. This is according to source.
Your endpoint have to be https. It can't be http.
Webhook can be set up only on ports 80, 88, 443 or 8443

Custom Object as a #RequestParam

I have a paginated endpoint that looks like this /api/clients?range=0-25.
I'd like the getClients() method in my ClientController to directly receive an instance of a custom Range object rather than having to validate a "0-25" String but I'm having trouble figuring this out.
#Getter
final class Range {
#Min(0)
private Integer offset = 0;
#Min(1)
private Integer limit = 25;
}
#ResponseBody
#GetMapping(params = { "range" })
public ResponseEntity<?> getAllClients(#RequestParam(value = "range", required = false) QueryRange queryRange, final HttpServletResponse response) {
...
}
I'm not sure how to instruct the Controller to correctly deserialize the "0-25" string into the Range...
You can use a Converter<S, T>, as shown below:
#Component
public class RangeConverter implements Converter<String, Range> {
#Override
public Range convert(String source) {
String[] values = source.split("-");
return new Range(Integer.valueOf(values[0]), Integer.valueOf(values[1]));
}
}
You also could handle invalid values according to your needs. If you use the above converter as is, the attempt to convert an invalid value such as 1-x will result in a ConversionFailedException.
You can also do the following it seems :
public class QueryRangeEditor extends PropertyEditorSupport {
private static final Pattern PATTERN = Pattern.compile("^([1-9]\\d*|0)-([1-9]\\d*|0)$");
#Override
public void setAsText(String text) throws IllegalArgumentException {
final QueryRange range = new QueryRange();
Matcher matcher = PATTERN.matcher(text);
if (matcher.find()) {
range.setOffset(Integer.valueOf(matcher.group(1)));
range.setLimit(Integer.valueOf(matcher.group(2)));
} else {
throw new IllegalArgumentException("OI"); // todo - replace
}
setValue(range);
}
}
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(QueryRange.class, new QueryRangeEditor());
}
But #cassiomolin's looks cleaner...

JpaRepository findById method returning Optional.empty when id is matching to the correct database row?

For some reason my PluginRepository interface doesn't want to return the object saved in my database in this test here.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PluginServiceIntegrationTest {
#Autowired
private PluginRepository pluginRepository;
#Autowired
private PluginService pluginService;
#Autowired
private PluginController pluginController;
#LocalServerPort
private int port;
private String resourceUrl;
#Before
public void setUp() {
resourceUrl = "http://localhost:" + port + "/plugin/";
}
#Test
public void notNullTest() {
assertNotNull(pluginRepository);
assertNotNull(pluginService);
assertNotNull(pluginController);
}
#Test
public void postPluginTest() {
RestTemplate restTemplate = new RestTemplate();
PluginDTO requestDto = new PluginDTO();
HttpEntity<PluginDTO> request = new HttpEntity<>(requestDto);
ResponseEntity<PluginDTO> response = restTemplate.exchange(resourceUrl + "create", HttpMethod.POST, request, PluginDTO.class);
assertEquals(response.getStatusCode(), HttpStatus.OK);
PluginDTO pluginDTO = response.getBody();
assertNotNull(pluginDTO);
}
#Test
public void getPluginTest() throws Exception {
PluginDTO createdDto = new PluginDTO(UUID.fromString("20a246e8-7b74-4081-bb62-6911b8ef43ef"),"Foo", "0.1", "TestAuthor");
Plugin created = new Plugin(createdDto.getUuid(), createdDto.getName(), createdDto.getVersion(), createdDto.getAuthor());
pluginRepository.saveAndFlush(created);
pluginRepository.findAll().forEach(plugin -> System.out.print(plugin.toString()));
try {
Plugin plugin = pluginRepository.findById(createdDto.getUuid());
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(pluginService.fetchPlugin(createdDto.getUuid()).toString());
assertEquals(pluginService.fetchPlugin(createdDto.getUuid()), created);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<PluginDTO> response = restTemplate.getForEntity(resourceUrl + createdDto.getUuid(), PluginDTO.class);
assertEquals(response.getStatusCode(), HttpStatus.OK);
}
#After
public void tearDown() {
pluginRepository.deleteAllInBatch();
}
}
System.out.println(pluginRepository.findById(createdDto.getUuid()).get().toString()); // this line
assertEquals(pluginService.fetchPlugin(createdDto.getUuid()), created);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<PluginDTO> response = restTemplate.getForEntity(resourceUrl + createdDto.getUuid(), PluginDTO.class);
assertEquals(response.getStatusCode(), HttpStatus.OK);
}
Even though the fixed values are set by the createdDto object it doesn't return it and I can see the values are in the database as well. This is what it returns...
20a246e8-7b74-4081-bb62-6911b8ef43ef:Foo:0.1:TestAuthor
java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at net.ihq.plugin.controller.PluginServiceIntegrationTest.getPluginTest(PluginServiceIntegrationTest.java:79)
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.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
I am very stumped as I don't understand why it can save the object but can't get it back with the same fixed values I set it with, but it is actually there from me print the list of objects in the database on the line before the error occurs.
Repository
#Repository
public interface PluginRepository extends JpaRepository<Plugin, UUID> {
#Query(value = "SELECT * FROM plugins WHERE name=?", nativeQuery = true)
Plugin findByName(String name);
}
Entity
#Data
#Entity
#Table(name = "plugins")
#NoArgsConstructor
public class Plugin {
#Id
#Convert(converter = UUIDConverter.class)
#Column(unique = true, updatable = false, length = 36)
private UUID uuid;
private String name;
private String version;
private String author;
public Plugin(UUID uuid, String name, String version, String author) {
this.uuid = uuid;
this.name = name;
this.version = version;
if (author == null) author = "JoeTAMatthews";
this.author = author;
}
public Plugin(String name, String version, String author) {
this(UUID.randomUUID(), name, version, author);
}
public void update(PluginDTO updated) {
this.name = updated.getName();
this.version = updated.getVersion();
this.author = updated.getAuthor();
}
#Override
public String toString() {
return uuid.toString() + ":" + name + ":" + version + ":" + author;
}
}
Sorry for the inconvenience but the reason it doesn't work is because the UUID type is not supported in the database, so all I had to do was get the string type of the uuid to actually get a result because of my UUIDConverter, thanks for all your help.

Can't evict from Spring Cache in Guice DI app

I'm trying to use String Cache abstraction mechanism with guice modules.
I've created interceptors:
CacheManager cacheManager = createCacheManager();
bind(CacheManager.class).toInstance(cacheManager);
AppCacheInterceptor interceptor = new AppCacheInterceptor(
cacheManager,
createCacheOperationSource()
);
bindInterceptor(
Matchers.any(),
Matchers.annotatedWith(Cacheable.class),
interceptor
);
bindInterceptor(
Matchers.any(),
Matchers.annotatedWith(CacheEvict.class),
interceptor
);
Then, implemented Strings Cache interface and CacheManager, and finally annotated my DAO classes with #Cachable and #CacheEvict:
public class DaoTester {
QssandraConsumer qs;
#CachePut(value = "cached_consumers", key = "#consumer.id")
public void save(QssandraConsumer consumer) {
qs = consumer;
}
#Cacheable(value = "cached_consumers")
public QssandraConsumer get(String id) {
if (id != null) {
qs.getId();
}
return qs;
}
#CacheEvict(value = "cached_consumers", key = "#consumer.id")
public void remove(QssandraConsumer consumer) {
qs = consumer;
}}
Caching is simply fine - no problems here, but when i try to evict(calling remove method in this example), evrything crashes and I see:
Exception in thread "main" org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 10): Field or property 'id' cannot be found on null
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:205)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:72)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:57)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:93)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:88)
at org.springframework.cache.interceptor.ExpressionEvaluator.key(ExpressionEvaluator.java:80)
at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.generateKey(CacheAspectSupport.java:464)
at org.springframework.cache.interceptor.CacheAspectSupport.inspectCacheEvicts(CacheAspectSupport.java:260)
at org.springframework.cache.interceptor.CacheAspectSupport.inspectAfterCacheEvicts(CacheAspectSupport.java:232)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:215)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:66)
at qiwi.qommon.deployment.dao.DaoTester.main(DaoTester.java:44)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
What's wrong here?!
BTW, cached object is:
public class QssandraConsumer implements Identifiable<String> {
private String id;
private String host;
#Override
public String getId() {
return id;
}
#Override
public void setId(String id) {
this.id = id;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
#Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (null == object) {
return false;
}
if (!(object instanceof QssandraConsumer)) {
return false;
}
QssandraConsumer o = (QssandraConsumer) object;
return
Objects.equal(id, o.id)
&& Objects.equal(host, o.host);
}
#Override
public int hashCode() {
return Objects.hashCode(
id, host
);
}
#Override
public String toString() {
return Objects.toStringHelper(this)
.addValue(id)
.addValue(host)
.toString();
}
}
Finally I figured out what was the reason of the problem:
when injecting a class that uses annotation(which are intercepted, like #Cachable or #CacheEvict) Guice enhances class (AOP make bytecode modification in runtime). So when CacheInterceptor tryed to evaluate key = "#consumer.id" it failed because couldn't find argument name in enhanced class (see: LocalVariableTableParameterNameDiscoverer#inspectClass).
So it will not work in Guice out of the box.
In spring the proxy class is created - so no problems here.

Resources