I am trying to make a bot respond to a command with an embed message that has multiple reactions under it.
I have got it working to the point that it adds 1 reaction but I need to add different reactions as well, like this: (http://prntscr.com/qd8da4) There are a lot of tutorials online that add 1 reaction, but none that add multiple.
I am using the latest version of the Discord JDA.
The code I currently have is:
public void onGuildMessageReceived(GuildMessageReceivedEvent event) {
String[] args = event.getMessage().getContentRaw().split("\\s+");
if (args[0].equalsIgnoreCase(DiscordBot.prefix + "info")) {
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss z");
Date date = new Date(System.currentTimeMillis());
MessageChannel channel = event.getChannel(); // Channel the command was sent in.
EmbedBuilder info = new EmbedBuilder();
info.setTitle("Server Info");
info.setDescription("Info About the bot.");
info.addField("Creator", "Name", false);
info.setColor(0xf45642);
info.setTimestamp(Instant.now());
channel.sendMessage(info.build()).queue(message -> message.addReaction("✔️").queue());
}
}
I had the same problem myself as there wasn't really a native way to do what I want which was to have certain bot actions when a reaction was clicked.
I ended up creating a couple helper classes that allow the program to have callbacks when reactions are clicked:
The following is the reaction which will listen for reactions to be clicked:
public class ReactionListener<T> {
private final Map<String, Consumer<Message>> reactions;
private final long userId;
private volatile T data;
private Long expiresIn, lastAction;
private boolean active;
public ReactionListener(long userId, T data) {
this.data = data;
this.userId = userId;
reactions = new LinkedHashMap<>();
active = true;
lastAction = System.currentTimeMillis();
expiresIn = TimeUnit.MINUTES.toMillis(5);
}
public boolean isActive() {
return active;
}
public void disable() {
this.active = false;
}
/**
* The time after which this listener expires which is now + specified time
* Defaults to now+5min
*
* #param timeUnit time units
* #param time amount of time units
*/
public void setExpiresIn(TimeUnit timeUnit, long time) {
expiresIn = timeUnit.toMillis(time);
}
/**
* Check if this listener has specified emote
*
* #param emote the emote to check for
* #return does this listener do anything with this emote?
*/
public boolean hasReaction(String emote) {
return reactions.containsKey(emote);
}
/**
* React to the reaction :')
*
* #param emote the emote used
* #param message the message bound to the reaction
*/
public void react(String emote, Message message) {
if (hasReaction(emote)) reactions.get(emote).accept(message);
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
/**
* Register a consumer for a specified emote
* Multiple emote's will result in overriding the old one
*
* #param emote the emote to respond to
* #param consumer the behaviour when emote is used
*/
public void registerReaction(String emote, Consumer<Message> consumer) {
reactions.put(emote, consumer);
}
/**
* #return list of all emotes used in this reaction listener
*/
public Set<String> getEmotes() {
return reactions.keySet();
}
/**
* updates the timestamp when the reaction was last accessed
*/
public void updateLastAction() {
lastAction = System.currentTimeMillis();
}
/**
* When does this reaction listener expire?
*
* #return timestamp in millis
*/
public Long getExpiresInTimestamp() {
return lastAction + expiresIn;
}
public long getUserId() {
return userId;
}
}
Next we want to handle the reactions as they are clicked. This method requires the current logged in userId to make sure only that users actions will be recorded and no one elses. It can be modified to be global if others can react too.:
public class ReactionHandler {
private final ConcurrentHashMap<Long, ConcurrentHashMap<Long, ReactionListener<?>>> reactions;
private ReactionHandler() {
reactions = new ConcurrentHashMap<>();
}
public synchronized void addReactionListener(long guildId, Message message, ReactionListener<?> handler) {
addReactionListener(guildId, message, handler, true);
}
public synchronized void addReactionListener(long guildId, Message message, ReactionListener<?> handler, boolean queue) {
if (handler == null) {
return;
}
if (message.getChannelType().equals(ChannelType.TEXT)) {
if (!PermissionUtil.checkPermission(message.getTextChannel(), message.getGuild().getSelfMember(), Permission.MESSAGE_ADD_REACTION)) {
return;
}
}
if (!reactions.containsKey(guildId)) {
reactions.put(guildId, new ConcurrentHashMap<>());
}
if (!reactions.get(guildId).containsKey(message.getIdLong())) {
for (String emote : handler.getEmotes()) {
RestAction<Void> action = message.addReaction(emote);
if (queue) action.queue(); else action.complete();
}
}
reactions.get(guildId).put(message.getIdLong(), handler);
}
public synchronized void removeReactionListener(long guildId, long messageId) {
if (!reactions.containsKey(guildId)) return;
reactions.get(guildId).remove(messageId);
}
/**
* Handles the reaction
*
* #param channel TextChannel of the message
* #param messageId id of the message
* #param userId id of the user reacting
* #param reaction the reaction
*/
public void handle(TextChannel channel, long messageId, long userId, MessageReaction reaction) {
ReactionListener<?> listener = reactions.get(channel.getGuild().getIdLong()).get(messageId);
if (!listener.isActive() || listener.getExpiresInTimestamp() < System.currentTimeMillis()) {
reactions.get(channel.getGuild().getIdLong()).remove(messageId);
} else if ((listener.hasReaction(reaction.getReactionEmote().getName())) && listener.getUserId() == userId) {
reactions.get(channel.getGuild().getIdLong()).get(messageId).updateLastAction();
Message message = channel.retrieveMessageById(messageId).complete();
listener.react(reaction.getReactionEmote().getName(), message);
}
}
/**
* Do we have an event for a message?
*
* #param guildId discord guild-id of the message
* #param messageId id of the message
* #return do we have an handler?
*/
public boolean canHandle(long guildId, long messageId) {
return reactions.containsKey(guildId) && reactions.get(guildId).containsKey(messageId);
}
public synchronized void removeGuild(long guildId) {
reactions.remove(guildId);
}
/**
* Delete expired handlers
*/
public synchronized void cleanCache() {
long now = System.currentTimeMillis();
for (Iterator<Map.Entry<Long, ConcurrentHashMap<Long, ReactionListener<?>>>> iterator = reactions.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<Long, ConcurrentHashMap<Long, ReactionListener<?>>> mapEntry = iterator.next();
mapEntry.getValue().values().removeIf(listener -> !listener.isActive() || listener.getExpiresInTimestamp() < now);
if (mapEntry.getValue().values().isEmpty()) {
reactions.remove(mapEntry.getKey());
}
}
}
}
Then with both of these libraries in hand we can implement them in our messages as such.
channel.sendMessage(info.build()).queue((msg) -> {
ReactionListener<String> handler = new ReactionListener<>(userId, msg.getId());
handler.setExpiresIn(TimeUnit.MINUTES, 1);
handler.registerReaction("✔️", (ret) -> foo());
handler.registerReaction("X", (ret) -> bar());
reactionHandler.addReactionListener(guild.getIdLong(), msg, handler);
});
Related
The expiration time of a JWT can be set by configuring micronaut.security.token.jwt.generator.access-token-expiration.
Is it possible to have a custom value for individually issued JWT tokens? Searching the documentation I haven't found any useful information except you can replace the BearerTokenRenderer and return a custom response.
public AccessRefreshToken render(Integer expiresIn, String accessToken, #Nullable String refreshToken) {
return new AccessRefreshToken(accessToken, refreshToken, BEARER_TOKEN_TYPE, ***customValue**);
}
Will returning a different value for expires_in work in this case?
According to micronaut documentation - pt 9.3.5
You can replace ClaimGenerator with your own:
ClaimGenerator
Which has a method with parameter Integer expiration
You can replace classes by using #Replaces annotation like described here:
https://micronaut-projects.github.io/micronaut-core/latest/guide/#replaces
I hope that solves your issue
The solution is to extend the JwTClaimsSetGenerator
#Replaces(JWTClaimsSetGenerator.class)
#Singleton
public class CustomClaimsGenerator extends JWTClaimsSetGenerator {
private static final Logger LOG = LoggerFactory.getLogger(CustomClaimsGenerator.class);
private static final String ROLES_KEY = "rolesKey";
private final TokenConfiguration tokenConfiguration;
private final JwtIdGenerator jwtIdGenerator;
private final ClaimsAudienceProvider claimsAudienceProvider;
private final String appName;
/**
* #param tokenConfiguration Token Configuration
* #param jwtIdGenerator Generator which creates unique JWT ID
* #param claimsAudienceProvider Provider which identifies the recipients that the JWT is intended for.
* #param applicationConfiguration The application configuration
*/
public CustomClaimsGenerator(TokenConfiguration tokenConfiguration,#Nullable JwtIdGenerator jwtIdGenerator, #Nullable ClaimsAudienceProvider claimsAudienceProvider, ApplicationConfiguration applicationConfiguration) {
super(tokenConfiguration, jwtIdGenerator, claimsAudienceProvider, applicationConfiguration);
this.tokenConfiguration = tokenConfiguration;
this.jwtIdGenerator = jwtIdGenerator;
this.claimsAudienceProvider = claimsAudienceProvider;
this.appName = applicationConfiguration != null ? applicationConfiguration.getName().orElse(Environment.MICRONAUT) : Environment.MICRONAUT;
}
/**
* #param authentication Authenticated user's representation.
* #param expiration expiration time in seconds
* #return The authentication claims
*/
#Override
public Map<String, Object> generateClaims(Authentication authentication, #Nullable Integer expiration) {
expiration = 1000; //works with this value
JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
populateIat(builder);
populateExp(builder, 15);
populateJti(builder);
populateIss(builder);
populateAud(builder);
populateNbf(builder);
populateWithAuthentication(builder, authentication);
if (LOG.isDebugEnabled()) {
LOG.debug("Generated claim set: {}", builder.build().toJSONObject());
}
return builder.build().getClaims();
}
/**
* Populates iss claim.
*
* #param builder The Claims Builder
* #see iss (Issuer) Claim
*/
protected void populateIss(JWTClaimsSet.Builder builder) {
if (appName != null) {
builder.issuer(appName); // iss
}
}
/**
* Populates sub claim.
*
* #param builder The Claims Builder
* #param authentication Authenticated user's representation.
* #see sub (Subject) Claim
*/
protected void populateSub(JWTClaimsSet.Builder builder, Authentication authentication) {
builder.subject(authentication.getName()); // sub
}
/**
* Populates aud claim.
*
* #param builder The Claims Builder
* #see aud (Audience) Claim
*/
protected void populateAud(JWTClaimsSet.Builder builder) {
if (claimsAudienceProvider != null) {
builder.audience(claimsAudienceProvider.audience()); // aud
}
}
/**
* Populates exp claim.
*
* #param builder The Claims Builder
* #param expiration expiration time in seconds
* #see exp (ExpirationTime) Claim
*/
protected void populateExp(JWTClaimsSet.Builder builder, #Nullable Integer expiration) {
if (expiration != null) {
LOG.debug("Setting expiration to {}", expiration);
System.out.print(Date.from(Instant.now().plus(expiration, ChronoUnit.SECONDS)));
builder.expirationTime(Date.from(Instant.now().plus(expiration, ChronoUnit.SECONDS))); // exp
}
}
/**
* Populates nbf claim.
*
* #param builder The Claims Builder
* #see nbf (Not Before) Claim
*/
protected void populateNbf(JWTClaimsSet.Builder builder) {
builder.notBeforeTime(new Date()); // nbf
}
/**
* Populates iat claim.
*
* #param builder The Claims Builder
* #see iat (Issued At) Claim
*/
protected void populateIat(JWTClaimsSet.Builder builder) {
builder.issueTime(new Date()); // iat
}
/**
* Populates jti claim.
*
* #param builder The Claims Builder
* #see jti (JWT ID) Claim
*/
protected void populateJti(JWTClaimsSet.Builder builder) {
if (jwtIdGenerator != null) {
builder.jwtID(jwtIdGenerator.generateJtiClaim()); // jti
}
}
/**
* Populates Claims with Authentication object.
*
* #param builder the Claims Builder
* #param authentication Authenticated user's representation.
*/
protected void populateWithAuthentication(JWTClaimsSet.Builder builder, Authentication authentication) {
populateSub(builder, authentication);
authentication.getAttributes().forEach(builder::claim);
String rolesKey = tokenConfiguration.getRolesName();
if (!rolesKey.equalsIgnoreCase(TokenConfiguration.DEFAULT_ROLES_NAME)) {
builder.claim(ROLES_KEY, rolesKey);
}
builder.claim(rolesKey, authentication.getRoles());
}
/**
* #param oldClaims The old claims to use as a base in the new token generation.
* #param expiration expiration time in seconds
* #return Instance of {#link JWTClaimsSet}
*/
#Override
public Map<String, Object> generateClaimsSet(Map<String, ?> oldClaims, Integer expiration) {
JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
List<String> excludedClaims = Arrays.asList(JwtClaims.EXPIRATION_TIME, JwtClaims.ISSUED_AT, JwtClaims.NOT_BEFORE);
for (String k : oldClaims.keySet()
.stream()
.filter(p -> !excludedClaims.contains(p))
.collect(Collectors.toList())) {
builder.claim(k, oldClaims.get(k));
}
populateExp(builder, expiration);
populateIat(builder);
populateNbf(builder);
return builder.build().getClaims();
}
}
The unit tests (JUnit, Mockito) that I've written for my Caffeine CacheLoader implementation all succeed when I run them individually, but one of them fails when I run them all together. I believe that I am following best practices in using #Before for all of my test object setups.
When run with the others, the test testGet_WhenCalledASecondAndThirdTimeBeyondCacheDuration_LoadingMethodCalledASecondTime fails every time with the following error:
org.mockito.exceptions.verification.TooLittleActualInvocations:
testDataSource.getObjectWithKey(
"mountain-bikes"
);
Wanted 2 times:
-> at ErrorHandlingLoadingCacheFactoryTest.testGet_WhenCalledASecondAndThirdTimeBeyondCacheDuration_LoadingMethodCalledASecondTime(ErrorHandlingLoadingCacheFactoryTest.java:67)
But was 1 time:
-> at ErrorHandlingCacheLoader.load(ErrorHandlingCacheLoader.java:41)
Something would appear to be carrying over between the tests, but given what I am doing in my #Before method I'm not sure how that could be. I've tried calling the following in an #After method:
invalidateAll()
cleanUp()
Mockito.reset(testDataSource)
I have also tried to manually pass a singleThreadExecutor to the cache builder, and waiting for it to finish whatever it is doing in #After in case that has anything to do with it.
My Caffeine CacheLoader implementation just overrides the reload method to return the currently cached value if the attempt to refresh it fails (throws an exception). Other than that, its pretty vanilla.
#Component
public class ErrorHandlingLoadingCacheFactory {
private final Ticker ticker;
#Autowired
public ErrorHandlingLoadingCacheFactory(Ticker ticker) {
this.ticker = ticker;
}
public <T> LoadingCache<String, T> buildCache(String cacheName,
long duration,
TimeUnit timeUnit,
Function<String, T> valueResolver) {
return Caffeine.newBuilder()
.refreshAfterWrite(duration, timeUnit)
.ticker(ticker)
.build(new ErrorHandlingCacheLoader<>(cacheName, valueResolver));
}
}
/**
* a LoadingCache that retains stale cache values if
* an attempt to retrieve a fresh value for a given key fails.
*
* #param <K> the cache key type
* #param <V> the cache value type
*/
class ErrorHandlingCacheLoader<K, V> implements CacheLoader<K, V> {
private final static Logger logger = LoggerFactory.getLogger(ErrorHandlingCacheLoader.class);
private final String cacheName;
private final Function<K, V> valueResolver;
/**
* Create a cache.
*
* #param cacheName the cache name
* #param valueResolver the method used to get a value for a key
*/
public ErrorHandlingCacheLoader(String cacheName, Function<K, V> valueResolver) {
this.cacheName = cacheName;
this.valueResolver = valueResolver;
}
/**
* Load the initial cache value for a given key.
* #param key the cache key
* #return the initial value to cache
*/
#Override
public V load(#NonNull K key) {
return valueResolver.apply(key);
}
/**
* Attempt to reload a value for a given key.
* #param key the cache key
* #param oldValue the currently cached value for the given key
* #return
*/
#Override
public V reload(#NonNull K key, V oldValue) {
V value = oldValue;
try {
value = valueResolver.apply(key);
} catch (RuntimeException e) {
logger.warn("Failed to retrieve value for key '{}' in cache '{}'. Returning currently cached value '{}'.", key, cacheName, oldValue);
}
return value;
}
}
public class ErrorHandlingLoadingCacheFactoryTest {
private ErrorHandlingLoadingCacheFactory errorHandlingLoadingCacheFactory;
private FakeTicker fakeTicker;
private TestDataSource testDataSource;
private LoadingCache<String, TestObject> loadingCache;
#Before
public void setUp() {
fakeTicker = new FakeTicker();
testDataSource = mock(TestDataSource.class);
errorHandlingLoadingCacheFactory = new ErrorHandlingLoadingCacheFactory(fakeTicker::read);
loadingCache = errorHandlingLoadingCacheFactory.buildCache("testCache", 1, TimeUnit.HOURS, testDataSource::getObjectWithKey);
}
#After
public void tearDown() {
validateMockitoUsage();
}
#Test
public void testGet_WhenCalledTwiceWithinCachePeriod_LoadingMethodCalledOnce() {
// Arrange
TestObject testObject = new TestObject("Mountain Bikes");
when(testDataSource.getObjectWithKey("mountain-bikes")).thenReturn(testObject);
// Act
TestObject result1 = loadingCache.get("mountain-bikes");
TestObject result2 = loadingCache.get("mountain-bikes");
// Assert
verify(testDataSource, times(1)).getObjectWithKey("mountain-bikes");
assertThat(result1).isEqualTo(testObject);
assertThat(result2).isEqualTo(testObject);
}
#Test
public void testGet_WhenCalledASecondAndThirdTimeBeyondCacheDuration_LoadingMethodCalledASecondTime() {
// Arrange
TestObject testObject1 = new TestObject("Mountain Bikes 1");
TestObject testObject2 = new TestObject("Mountain Bikes 2");
when(testDataSource.getObjectWithKey("mountain-bikes")).thenReturn(testObject1, testObject2);
// Act
TestObject result1 = loadingCache.get("mountain-bikes");
fakeTicker.advance(2, TimeUnit.HOURS);
TestObject result2 = loadingCache.get("mountain-bikes");
TestObject result3 = loadingCache.get("mountain-bikes");
// Assert
verify(testDataSource, times(2)).getObjectWithKey("mountain-bikes");
assertThat(result1).isEqualTo(testObject1);
assertThat(result2).isEqualTo(testObject1);
assertThat(result3).isEqualTo(testObject2);
}
#Test(expected = RuntimeException.class)
public void testGet_WhenFirstLoadCallThrowsRuntimeException_ThrowsRuntimeException() {
// Arrange
when(testDataSource.getObjectWithKey("mountain-bikes")).thenThrow(new RuntimeException());
// Act
loadingCache.get("mountain-bikes");
}
#Test
public void testGet_WhenFirstLoadCallSuccessfulButSecondThrowsRuntimeException_ReturnsCachedValueFromFirstCall() {
// Arrange
TestObject testObject1 = new TestObject("Mountain Bikes 1");
when(testDataSource.getObjectWithKey("mountain-bikes")).thenReturn(testObject1).thenThrow(new RuntimeException());
// Act
TestObject result1 = loadingCache.get("mountain-bikes");
fakeTicker.advance(2, TimeUnit.HOURS);
TestObject result2 = loadingCache.get("mountain-bikes");
// Assert
verify(testDataSource, times(2)).getObjectWithKey("mountain-bikes");
assertThat(result1).isEqualTo(testObject1);
assertThat(result2).isEqualTo(testObject1);
}
#Test
public void testGet_WhenFirstLoadCallSuccessfulButSecondThrowsRuntimeException_SubsequentGetsReturnCachedValueFromFirstCall() {
// Arrange
TestObject testObject1 = new TestObject("Mountain Bikes 1");
when(testDataSource.getObjectWithKey("mountain-bikes")).thenReturn(testObject1).thenThrow(new RuntimeException());
// Act
TestObject result1 = loadingCache.get("mountain-bikes");
fakeTicker.advance(2, TimeUnit.HOURS);
TestObject result2 = loadingCache.get("mountain-bikes");
TestObject result3 = loadingCache.get("mountain-bikes");
// Assert
verify(testDataSource, times(2)).getObjectWithKey("mountain-bikes");
assertThat(result1).isEqualTo(testObject1);
assertThat(result2).isEqualTo(testObject1);
assertThat(result3).isEqualTo(testObject1);
}
#Test(expected = NullPointerException.class)
public void testGet_WhenKeyIsNull_ThrowsNullPointerException() {
// Arrange
String key = null;
// Act
loadingCache.get(key);
}
#Test
public void testGet_WhenFirstLoadCallReturnsNull_DoesNotCacheResult() {
// Arrange
TestObject testObject1 = new TestObject("Mountain Bikes 1");
when(testDataSource.getObjectWithKey("mountain-bikes")).thenReturn(null).thenReturn(testObject1);
// Act
TestObject result1 = loadingCache.get("mountain-bikes");
TestObject result2 = loadingCache.get("mountain-bikes");
// Assert
verify(testDataSource, times(2)).getObjectWithKey("mountain-bikes");
assertThat(result1).isEqualTo(null);
assertThat(result2).isEqualTo(testObject1);
}
#Data
class TestObject {
private String id;
public TestObject(String id) {
this.id = id;
}
}
interface TestDataSource {
TestObject getObjectWithKey(String key);
}
}
Ben Manes suggested in his comment that I use Runnable::run as the LoadingCache's executor when running unit tests, which did the trick!
I implemented a second protected buildCache method on my factory that additionally takes an Executor parameter, which my test class uses to pass Runnable::run.
The updated ErrorHandlingLoadingCacheFactory:
public class ErrorHandlingLoadingCacheFactory {
private final Ticker ticker;
#Autowired
public ErrorHandlingLoadingCacheFactory(Ticker ticker) {
this.ticker = ticker;
}
/**
* Create an in-memory LoadingCache
*
* #param cacheName the name of the cache
* #param duration how long to keep values in the cache before attempting to refresh them
* #param timeUnit the unit of time of the given duration
* #param valueResolver the method to call to get a value to load into the cache for a given key
* #param <T> the type of object to store into the cache
* #return the newly created cache
*/
public <T> LoadingCache<String, T> buildCache(String cacheName,
long duration,
TimeUnit timeUnit,
Function<String, T> valueResolver) {
return buildCache(cacheName, duration, timeUnit, valueResolver, ForkJoinPool.commonPool());
}
/**
* Create an in-memory LoadingCache
*
* #param cacheName the name of the cache
* #param duration how long to keep values in the cache before attempting to refresh them
* #param timeUnit the unit of time of the given duration
* #param valueResolver the method to call to get a value to load into the cache for a given key
* #param executor the executor for the cache to use
* #param <T> the type of object to store into the cache
* #return the newly created cache
*/
protected <T> LoadingCache<String, T> buildCache(String cacheName,
long duration,
TimeUnit timeUnit,
Function<String, T> valueResolver,
Executor executor) {
return Caffeine.newBuilder()
.refreshAfterWrite(duration, timeUnit)
.ticker(ticker)
.executor(executor)
.build(new ErrorHandlingCacheLoader<>(cacheName, valueResolver));
}
}
the updated setUp() method in ErrorHandlingLoadingCacheFactoryTest:
...
#Before
public void setUp() {
fakeTicker = new FakeTicker();
testDataSource = mock(TestDataSource.class);
errorHandlingLoadingCacheFactory = new ErrorHandlingLoadingCacheFactory(fakeTicker::read);
loadingCache = errorHandlingLoadingCacheFactory.buildCache("testCache", 1, TimeUnit.HOURS, testDataSource::getObjectWithKey, Runnable::run);
}
...
There must have been a race between my tests that my single-threaded executor didn't catch, probably because I didn't properly wait for it to terminate in my #After method. Ben suggested that if I used awaitTermination on the single-threaded executor, that might also have worked.
Good morning.
I have a problem with the answers of rest services that use generics - I have a generic object that has the following structure:
import java.io.Serializable;
public class Data implements Serializable {
private Object data;
private Long numero_reg;
/**
* #return the data
*/
public Object getData() {
return data;
}
/**
* #param data the data to set
*/
public void setData(Object data) {
this.data = data;
}
/**
* #return the numero_reg
*/
public long getNumero_reg() {
return numero_reg;
}
/**
* #param numero_reg the numero_reg to set
*/
public void setNumero_reg(long numero_reg) {
this.numero_reg = numero_reg;
}
}
When answer a rest one to object to the objects that meet the different rest with the purpose of standardized response follows:
#Path("/pruebas")
public class Pruebita {
#GET
public Data getalgo(){
ObjectoDatos od=new ObjectoDatos();
od.setPrueba("hola");
od.setPrueba2("hola2");
Data sd=new Data();
sd.setData(od);
sd.setNumero_reg(1);
return sd;
}
}
When it meets the response json answer it becomes a toString of the object and is not showing the data of the object.
Can someone help me?
{"data":"bo.ObjectoDatos#6d858b63","numero_reg":1}
This is the error - I got to the toString of the object , and not the object itself.
I found a solution -
I use the HashMaps class from guava repackaged :
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
int response = responseContext.getStatus();
Object entidad = responseContext.getEntity();
if (entidad != null && response == 200) {
int nroRegistros = (entidad instanceof List) ? (((List) entidad).size()) : 1;
Map<String, Object> respuesta = Maps.newHashMap();
respuesta.put("data", entidad);
if(nroRegistros != 1) {
respuesta.put("numero_reg", nroRegistros);
}
responseContext.setEntity(respuesta);
}
}
The above code works well and solves my issues.
I have a question regarding static method access. I have a class within i have 4 static method. as shown in code:
package com.itrucking.util;
public class ZKUtil implements Serializable {
private static final long serialVersionUID = 1L;
/**
*
* #author Shekhar
* #param _class
* #param listbox
* To make Listbox sorting enabled
* #throws NoSuchMethodException
* #throws SecurityException
*/
public static void setSortingEnabled(Class<?> _class, Listbox listbox){
Map<Listheader, String> sortingPair = new HashMap<Listheader, String>();
sortingPair = getMapForSorting(_class, listbox);
if (!sortingPair.isEmpty()) {
for (Map.Entry<Listheader, String> entry : sortingPair.entrySet()) {
entry.getKey().setSortAscending(
new FieldComparator(entry.getValue(), true));
entry.getKey().setSortDescending(
new FieldComparator(entry.getValue(), false));
}
}
}
/**
* #author Shekhar
* #param _class
* #param listbox
* #return Map<Listheader, String>
*/
private static Map<Listheader, String> getMapForSorting(Class<?> _class,Listbox listbox) {
List<Listheader> headerList = getListHeaderList(listbox);
Map<Listheader, String> sortingPair = new HashMap<Listheader, String>();
System.out.println(_class);
Field[] fields = _class.getDeclaredFields();
for (Field f : fields) {
// System.out.println(f.getName()+":"+f.getType());
for (Listheader lh : headerList) {
if (f.getName().equals(getId(lh)))
sortingPair.put(lh, f.getName());
}
}
System.out.println(sortingPair);
return sortingPair;
}
private static String getId(Listheader listheader) {
String listheaderId = null;
if (listheader.getId().contains("_")) {
listheaderId = listheader.getId().split("_")[1];
// System.out.println("listheaderId->"+listheaderId);
}
return listheaderId;
}
/**
* #author Shekhar
* #param listbox
* #return List<Listheader>
*/
#SuppressWarnings("unchecked")
private static List<Listheader> getListHeaderList(Listbox listbox) {
List<Listheader> headerList = new ArrayList<Listheader>();
Listhead listhead = null;
List<Component> listboxComponentList = listbox.getChildren();
for (Component listboxComponent : listboxComponentList) {
if (listboxComponent instanceof Listhead) {
listhead = (Listhead) listboxComponent;
break;
}
}
List<Component> listOfComp = listhead.getChildren();
if (listhead != null) {
for (Component c : listOfComp) {
if (c instanceof Listheader)
headerList.add((Listheader) c);
}
}
return headerList;
}
}
and i am calling setSortingEnabled() method from onLoadShipperDetailsListCtrl() from code bellow :
package com.itrucking.webui.controller;
public class ShipperDetailsListCtrl{
/**
* #param e
* #return void
*/
public void onCreate$window_shipperDetailsList(Event e){
onLoadShipperDetailsListCtrl();
}
/**
* #return void
*/
public void onLoadShipperDetailsListCtrl(){
System.out.println("onLoadShipperDetailsListCtrl called.");
shipperList = shipperService.getShipperList();
doRenderListboxShipperDetailsList(shipperList);
ZKUtil.setSortingEnabled(ShipperMaster.class, listbox_shipperDetailsList);
}
}
so what i think if i am calling setSortingEnabled() method from other class so i kept is as public and other method's i kept as private but it's giving me error as :
java.lang.NoSuchMethodError: com/itrucking/util/ZKUtil.getMapForSorting(Ljava/lang/Class;Lorg/zkoss/zul/Listbox;)Ljava/util/Map;
Why there is error NoSuchMethodError for ZKUtil.getMapForSorting() call in setSortingEnabled()
I know we can call private method from public in the same class. So i am not able to understand what is the problem.
Thanks in advance.
A NoSuchMethodError (the runtime error saying a method can't be found, instead of a compiler error) usually means that the .class files you're using are of a different version than the files you compiled against. In this case, you probably made changes to ZKUtil.java, but the JVM is loading an outdated version of ZKUtil.class. Clean and rebuild all of your .class files.
The original reason for my Jaxb Question
JaxB reference resolving
was that I couldn't get the same issue working with the simple-framework:
http://old.nabble.com/Two-Phase-support-for-CycleStrategy--td34802791.html
Today I got the things working with a persister call back to the same point as in my Jaxb Questions:
I get copies - not references. Again I am looking for a solution with proper references. This time for the Simple XML framework.
The example here has the base class "ModelElement" not Person as in the other question. Otherwise the problem is the same.
Again I am calling the unmarshalling twice to get all ids in PASS 1 and use the gathered results from the lookup HashMap created in PASS2.
What would be a solution to get proper references? My assumption is that adding a call back that actually lets the called function modify the unmarshalling result (see How to use an output parameter in Java? for a wrapping approach)
would do the trick (comparable to the JaxB solution I have posted in the meantime).
Persister serializer = new Persister();
ModelElementSimpleXmlImpl.lookup.clear();
serializer.read(result, xml);
System.err.println("PASS 2");
serializer.read(result, xml);
This code is from the ModelElementSimpleXmlImpl base class:
...
protected String ref;
/**
* getter for xsd:string/String id
* #return id
*/
#org.simpleframework.xml.Attribute(name="ref",required=false)
public String getRef() {
return ref;
}
/**
* setter for xsd:string/String id
* #param pid - new value for id
*/
#org.simpleframework.xml.Attribute(name="ref",required=false)
public void setRef(String pRef) {
ref=pRef;
}
private boolean debug=true;
/**
* show debug information
* #param title
* #param key
* #param me
* #param found
*/
public void showDebug(String title,String key,ModelElementSimpleXmlImpl me, ModelElementSimpleXmlImpl found) {
String deref="?";
if (found!=null)
deref="->"+found.getId()+"("+found.getClass().getSimpleName()+")";
if (debug)
System.err.println(title+": "+key+"("+me.getClass().getSimpleName()+")"+deref+" - "+this);
}
/**
* keep track of the elements already seen
*/
public static Map<String,ModelElementSimpleXmlImpl> lookup=new HashMap<String,ModelElementSimpleXmlImpl>();
#Validate
public void validate() {
ModelElementSimpleXmlImpl me=this;
String key=me.getId();
if (key!=null) {
showDebug("id",key,me,null);
lookup.put(key, me);
}
key=me.getRef();
if (key!=null) {
if (lookup.containsKey(key)) {
ModelElementSimpleXmlImpl meRef=lookup.get(key);
showDebug("ref",key,me,meRef);
me.setRef(null);
me.copyFrom(meRef);
} else {
if (debug)
showDebug("ref",me.getRef(),me,null);
}
}
}
Niall Gallagher suggested:
Should be fairly easy to do with something like the CycleStrategy. Just create MyCycleStrategy and where there is an exception with "Invalid reference" just return null and remember the reference. When you have picked up all the ids and the values then do a second pass. In the second pass assign the value to the first occurrence of either the ref or the id. Then all following references should be given the same value. This should work.
And he is right. The following extended Cycle Strategy works as proposed:
/**
* Two Path Cycle Strategy
*
* #author wf
*
*/
public static class TwoPathCycleStrategy extends CycleStrategy {
String id;
String ref;
public static boolean debug = false;
/**
* create a twoPath Cycle Strategy
*
* #param id
* #param ref
*/
public TwoPathCycleStrategy(String id, String ref) {
super(id, ref);
this.id = id;
this.ref = ref;
}
/**
* show debug information
*
* #param title
* #param key
* #param value
*/
public void showDebug(String title, String key, Value value) {
if (debug) {
String id = "?";
Object v = value;
while ((v instanceof Value) && ((Value) v).isReference()) {
v = ((Value) v).getValue();
}
if (v == null) {
id = "null";
} else {
// FIXME - adapt to your code or comment out
//if (v instanceof ModelElement) {
// ModelElement me = (ModelElement) v;
// id = me.getId();
//}
}
System.err.println(title + ":" + key + "->"
+ v.getClass().getSimpleName() + ":"
+ value.getType().getSimpleName() + ":" + value.isReference() + ":"
+ id);
}
}
public Map<String, Value> lookup = new HashMap<String, Value>();
#SuppressWarnings("rawtypes")
#Override
public Value read(Type type, NodeMap node, Map map) throws Exception {
Value value = null;
Node refNode = node.get(ref);
Node keyNode = node.get(id);
try {
value = super.read(type, node, map);
if (keyNode != null) {
String key = keyNode.getValue();
if (value != null) {
showDebug("id", key, value);
lookup.put(key, value);
} else {
showDebug("id?", key, value);
}
}
} catch (CycleException ce) {
if (ce.getMessage().contains("Invalid reference")) {
if (refNode != null) {
String key = refNode.getValue();
if (lookup.containsKey(key)) {
value = lookup.get(key);
showDebug("ref", key, value);
} else {
showDebug("ref?",key,null);
}
}
} else {
throw ce;
}
}
return value;
}
}