I`am using RoboSpice to receive and handle JSON answer from server. I have made all like in RoboSpice tutorial: added service(NetworkSpiceService) in AndroidManifest.xml, added needful libs, implemented a special response object - SimpleResponse with #JsonIgnoreProperties(ignoreUnknown = true).
The Base Activity:
public abstract class BaseActivity extends Activity {
private SpiceManager spiceManager = new SpiceManager(NetworkSpiceService.class);
#Override
protected void onStart() {
spiceManager.start(this);
super.onStart();
}
#Override
protected void onStop() {
spiceManager.shouldStop();
super.onStop();
}
public SpiceManager getSpiceManager() {
return spiceManager;
}
}
My work Activity:
public class MainActivity extends BaseActivity implements OnClickListener{
private static final String JSON_CACHE_KEY = "some_json";
private Button button1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ...
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private void load() {
getSpiceManager().execute(NetworkManager.getJson(param), JSON_CACHE_KEY, DurationInMillis.ALWAYS_EXPIRED, new JsonRequestListener());
}
private class JsonRequestListener implements RequestListener<SimpleResponse> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.e("Json", "failure: " + spiceException.getMessage());
}
#Override
public void onRequestSuccess(SimpleResponse simpleResponse) {
Log.i("Json", "response");
Log.i("Json", "response received. offset = " + simpleResponse.getOffset() + " timezone = " + simpleResponse.getTimeZone());
}
}
NetwokManager class:
public class NetworkManager {
private static final String API_KEY = ".....";
private static final String GET_SOME_URL = "https://api.servername.com/" + API_KEY + "/";
public static GetRequest<SimpleResponse> getJson(double param) {
return new GetRequest<SimpleResponse>(SimpleResponse.class, GET_SOME_URL + param);
}
}
And GetRequest:
public class GetRequest<T extends SimpleRequest> extends
SpringAndroidSpiceRequest<T> {
private Class<T> clazz;
private String url;
public GetRequest(Class<T> clazz, String url) {
super(clazz);
this.clazz = clazz;
this.url = url;
}
#Override
public T loadDataFromNetwork() throws Exception {
return getRestTemplate().getForObject(url, clazz);
}
So, after call of load() i am expected to fire OnRequestFailure(...) or onRequestSuccess(...) , but nothing. I was convinced that loadDataFromNetwork was calling. On a browser i receive a response for that address, but not in my app.
What wrong? May be I forgot something? Or may be https request needed to be handled in some other ways?
Where do you call private void load()? If it is part of the ClickListener you should be fine. If it is a method in onCreate() you might have an issue as RoboSpice hasn't been "started" yet and is unbound so your request might be queued for execution and disregarded or returned without a reference to your listener.
Try Overriding protected RequestListenerNotifier createRequestRequestListenerNotifier() in your implementation of SpiceService and throw a bunch of breakpoints to debug in your implementation of DefaultRequestListenerNotifier to check what's up with the calls.
As #Take Chances Make Cha said it, we don't see you invocation of load. It should trigger your request listener's method, at least one of the two.
Https handling in Android can become tricky if the certificate of the server is not know of Android, but this is not related to RS. That's a more general Android way of working.
Anyhow, check your logs, RS logs a lot, it will tell you what is going on, if your request is processed or not, and it will log all calls to listeners.
BTW, remove the NetworkManager static factory method, create an inheritance of request to handle common code (even constants). Such factory is not a good design, it will be impossible to mock for instance and will make testing harder.
Related
In present situation I can write in DB only result
true - is good
false - is bad
How can I get http code and analyze it?
For example, if response code is 204 or 400 for me it's good response. All others are bad... And write it to DB.
Now I'm reading about ErrorDecoder (in Feign), am I looking in the right direction?
But I would not want to change the logic of ALL FeignClient, only one method
#Slf4j
#Service
#RequiredArgsConstructor
public class TestService {
private final DataBaseService dataBaseService;
private final TestRepository testRepository;
private final TestFeign testFeign;
#Async
#Retry(name = "testRetry", fallbackMethod = "testFallback")
public void rboLimitComplexSetting(UUID uuid, TestRequest testRequest) {
log.info("Отправка запроса в RBO limitId = {} ", limitId);
testFeign.test(testRequest);
addLog(uuid, true);
}
private void testFallback(UUID uuid, TestRequest testRequest, RuntimeException ex) {
addLog(limitId, false);
}
private void addLog(UUID uuid, boolean result) {
var entity = dataBaseService.findById(uuid);
entity.setResult(result);
testRepository.save(entity);
}
}
I am trying out to write data to my local Elasticsearch Docker Container (7.4.2), for simplicity I used the AbstractReactiveElasticsearchConfiguration given from Spring also Overriding the entityMapper function. The I constructed my repository extending the ReactiveElasticsearchRepository
Then in the end I used my autowired repository to saveAll() my collection of elements containing the data. However Elasticsearch doesn't write any data. Also i have a REST controller which is starting my whole process returning nothing basicly, DeferredResult>
The REST method coming from my ApiDelegateImpl
#Override
public DeferredResult<ResponseEntity<Void>> openUsageExporterStartPost() {
final DeferredResult<ResponseEntity<Void>> deferredResult = new DeferredResult<>();
ForkJoinPool.commonPool().execute(() -> {
try {
openUsageExporterAdapter.startExport();
deferredResult.setResult(ResponseEntity.accepted().build());
} catch (Exception e) {
deferredResult.setErrorResult(e);
}
}
);
return deferredResult;
}
My Elasticsearch Configuration
#Configuration
public class ElasticSearchConfig extends AbstractReactiveElasticsearchConfiguration {
#Value("${spring.data.elasticsearch.client.reactive.endpoints}")
private String elasticSearchEndpoint;
#Bean
#Override
public EntityMapper entityMapper() {
final ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
#Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo(elasticSearchEndpoint)
.build();
return ReactiveRestClients.create(clientConfiguration);
}
}
My Repository
public interface OpenUsageRepository extends ReactiveElasticsearchRepository<OpenUsage, Long> {
}
My DTO
#Data
#Document(indexName = "open_usages", type = "open_usages")
#TypeAlias("OpenUsage")
public class OpenUsage {
#Field(name = "id")
#Id
private Long id;
......
}
My Adapter Implementation
#Autowired
private final OpenUsageRepository openUsageRepository;
...transform entity into OpenUsage...
public void doSomething(final List<OpenUsage> openUsages){
openUsageRepository.saveAll(openUsages)
}
And finally my IT test
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#Testcontainers
#TestPropertySource(locations = {"classpath:application-it.properties"})
#ContextConfiguration(initializers = OpenUsageExporterApplicationIT.Initializer.class)
class OpenUsageExporterApplicationIT {
#LocalServerPort
private int port;
private final static String STARTCALL = "http://localhost:%s/open-usage-exporter/start/";
#Container
private static ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:6.8.4").withExposedPorts(9200);
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
final List<String> pairs = new ArrayList<>();
pairs.add("spring.data.elasticsearch.client.reactive.endpoints=" + container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
pairs.add("spring.elasticsearch.rest.uris=http://" + container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
TestPropertyValues.of(pairs).applyTo(configurableApplicationContext);
}
}
#Test
void testExportToES() throws IOException, InterruptedException {
final List<OpenUsageEntity> openUsageEntities = dbPreparator.insertTestData();
assertTrue(openUsageEntities.size() > 0);
final String result = executeRestCall(STARTCALL);
// Awaitility here tells me nothing is in ElasticSearch :(
}
private String executeRestCall(final String urlTemplate) throws IOException {
final String url = String.format(urlTemplate, port);
final HttpUriRequest request = new HttpPost(url);
final HttpResponse response = HttpClientBuilder.create().build().execute(request);
// Get the result.
return EntityUtils.toString(response.getEntity());
}
}
public void doSomething(final List<OpenUsage> openUsages){
openUsageRepository.saveAll(openUsages)
}
This lacks a semicolon at the end, so it should not compile.
But I assume this is just a typo, and there is a semicolon in reality.
Anyway, saveAll() returns a Flux. This Flux is just a recipe for saving your data, and it is not 'executed' until subscribe() is called by someone (or something like blockLast()). You just throw that Flux away, so the saving never gets executed.
How to fix this? One option is to add .blockLast() call:
openUsageRepository.saveAll(openUsages).blockLast();
But this will save the data in a blocking way effectively defeating the reactivity.
Another option is, if the code you are calling saveAll() from supports reactivity is just to return the Flux returned by saveAll(), but, as your doSomething() has void return type, this is doubtful.
It is not seen how your startExport() connects to doSomething() anyway. But it looks like your 'calling code' does not use any notion of reactivity, so a real solution would be to either rewrite the calling code to use reactivity (obtain a Publisher and subscribe() on it, then wait till the data arrives), or revert to using blocking API (ElasticsearchRepository instead of ReactiveElasticsearchRepository).
I have the following interceptor that tracks request/response based on saving and restoring some vars stored in MDC context for every request.
public class LoggingInterceptor implements DeferredResultProcessingInterceptor {
private final HelloSeeYouLogger helloSeeYouLogger;
private static final String X_UOW = "X-UOW";
private static final String X_REQUEST_ID = "X-RequestId";
private Map<String, String> context;
public LoggingInterceptor(HelloSeeYouLogger helloSeeYouLogger) {
this.helloSeeYouLogger = helloSeeYouLogger;
}
#Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) {
addUowAndRequestIdToMDC(request.getHeader(X_UOW), request.getHeader(X_REQUEST_ID));
final String uri = getUri((HttpServletRequest) request.getNativeRequest());
helloSeeYouLogger.logHelloThere(uri);
context = MDC.getCopyOfContextMap();
}
#Override
public <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) {
if (context != null) {
MDC.setContextMap(context);
}
final String uri = getUri((HttpServletRequest) request.getNativeRequest());
String body = getBody((HttpServletRequest) request.getNativeRequest());
if (!StringUtils.isEmpty(body)) {
body = replaceMoreThanOneSpacesWithOneSpace(hideCreditCardNumber(body));
}
helloSeeYouLogger.logSeeYou(uri, body);
clearUowAndRequestIdFromMDC();
}
public static void addUowAndRequestIdToMDC(final String uow, final String requestId) {
//This NewRelic stuff shouldn't be here as it is used for distributed tracing and not logging.
// However it helps to levarage requests tracking from a metric service such as new relic to a log aggregation service
//such as ELK.
NewRelic.addCustomParameter(UOW, uow);
NewRelic.addCustomParameter(REQUEST_ID, requestId);
MDC.put(UOW, uow);
MDC.put(REQUEST_ID, requestId);
}
public static void clearUowAndRequestIdFromMDC() {
if (MDC.get(UOW) != null) {
MDC.remove(UOW);
}
if (MDC.get(REQUEST_ID) != null) {
MDC.remove(REQUEST_ID);
}
}
I think i mn going to have concurrency problems as context is an instance variable and when running multiple concurrent thread saving and restoring MDC context will result in wrong results. Also using synchronize keyword will add performance problems.
I was wondering if there is a better approach to track MDC context when a spring controller returns a DeferredResult.
Thanks
You can use try using HandlerInterceptorAdapter instead
Check: https://www.logicbig.com/how-to/code-snippets/jcode-spring-mvc-deferredresultprocessinginterceptor.html
I want to change data inside a Vaadin UI. The change is invoked by a a rest call. There, i somehow need a reference to the UI class to call its method´, e.g. changeValue(string value).
I'm using vaadin-spring-boot-starter 1.0.0
Is that somehow possible?
EDIT: Another question now:
I was trying to do that Server Push, mentioned by #Eric, inside of a View, so that the view will get updated on a Broadcast message. However, this is not working (no exceptions, nothing to debug, just no updates in the view). This is what i do in my View:
#UIScope
#SpringView(name = LoadWebsiteView.VIEW_NAME)
#Push
public class LoadWebsiteView extends VerticalLayout implements View, Broadcaster.BroadcastListener {
...
#Autowired
public LoadWebsiteView(ScraperMainUI scraperMainUi) {
this.scraperMainUi = scraperMainUi;
Broadcaster.register(this);
initControlPane();
}
#Override
public void receiveBroadcast(String message) {
scraperMainUi.access(new Runnable() {
#Override
public void run() {
urlTxtField.setValue(message);
}
});
}
and here is the simple stuff i do in my restcontroller:
Broadcaster.broadcast(text);
What you are looking for is Vaadin's Push feature and a way to send a message to a list of registered "clients" (in this case, the Vaadin UIs who need to known about the changes).
You can read about Vaadin Push here: Enabling Server Push and also in the article Advanced Push
The Vaadin push function allows your server to force updates to the client instead of waiting on the browser to request again.
The message component simply acts as a way to tell subscribed UIs that there is an update they need to action.
This said, I have a project that does about the same as multiple users are actioning items and there are Spring scheduled tasks that also can effect changes the user needs to know about.
Note, the below examples are based on the examples available in Enabling Server Push article.
Broadcaster.java - Acts as the mechanism that registers instances to receive broadcasts and provides a facility to send broadcasts. In the below example, I have I have a class that represents a message (BroadcastMessage) but you could simplify it of course.
public class Broadcaster implements Serializable {
private static final long serialVersionUID = 3540459607283346649L;
static ExecutorService executorService = Executors.newSingleThreadExecutor();
private static LinkedList<BroadcastListener> listeners = new LinkedList<BroadcastListener>();
public interface BroadcastListener {
void receiveBroadcast(BroadcastMessage message);
}
public static synchronized void register(BroadcastListener listener) {
listeners.add(listener);
}
public static synchronized void unregister(BroadcastListener listener) {
listeners.remove(listener);
}
public static synchronized void broadcast(final BroadcastMessage message) {
for (final BroadcastListener listener: listeners)
executorService.execute(new Runnable() {
#Override
public void run() {
listener.receiveBroadcast(message);
}
});
}
}
Here is the class I defined for my BroadcastMessage. The idea is to have a way to denote what kind of message I have and also some payload in the form of a Map
public class BroadcastMessage implements Serializable {
private static final long serialVersionUID = 5637577096751222106L;
public BroadcastMessageType messageType;
public Map<String, String> params;
public BroadcastMessage() {
}
public BroadcastMessage(BroadcastMessageType messageType) {
this.messageType = messageType;
this.params = new HashMap<String, String>();
}
public BroadcastMessage(BroadcastMessageType messageType, Map<String, String> params) {
this.messageType = messageType;
this.params = params;
}
public BroadcastMessageType getMessageType() {
return messageType;
}
public void setMessageType(BroadcastMessageType messageType) {
this.messageType = messageType;
}
public Map<String, String> getParams() {
return params;
}
public void setParams(Map<String, String> params) {
this.params = params;
}
}
This is an example Vaadin UI that wants to listen for Broadcasts. Note the #Push annotation. Without this, the client will only refresh when the browser decides to. #Push makes it immediate**
#SpringComponent
#UIScope
#Push
#SpringView(name=TaskListComponent.NAME)
public class TaskListComponent extends MyCustomComponent implements Broadcaster.BroadcastListener, View {
/** PRUNED DOWN, TO DEMONSTRATE THE KEY CODE **/
// Register this window when we enter it
#Override
public void enter(ViewChangeEvent event) {
Broadcaster.register(this);
}
// Must also unregister when the UI expires
#Override
public void detach() {
Broadcaster.unregister(this);
super.detach();
}
// Receive a broadcast
#Override
public void receiveBroadcast(BroadcastMessage message) {
getUI().access(new Runnable() {
#Override
public void run() {
// DO WHATEVER YOU NEED TO DO HERE.
// I CALLED INITIALIZE BUT IT COULD BE
// JUST YOU FIELD UPDATE
if ( message.getMessageType().equals(BroadcastMessageType.REFRESH_TASK_LIST) )
initialize();
}
});
}
}
To send a message from your rest interface:
Broadcaster.broadcast(
new BroadcastMessage(
BroadcastMessageType.AUTO_REFRESH_LIST
)
);
Hope this helps! :)
Here is my ConfigUpdater class
private final class ConfigUpdater implements ManagedService {
#SuppressWarnings("rawtypes")
#Override
public void updated(Dictionary config) throws ConfigurationException {
if (config == null) {
return;
}
String title = ((String)config.get("title"));
}
}
My question is how can I access String title in any other class? Or how can I get config dictionary in any other class... Method updated will only be called when a config file is changed... once it is changed how can access its data in other class?
In general you would create a service that exposes these properties to other components.
For example, you could give your ConfigUpdater a second interface. Another component can than lookup/inject this interface from the service registry and use it's methods to access the properties.
I created an example project on GitHub: https://github.com/paulbakker/configuration-example
The most important part is the service that implements both ManagedService and a custom interface:
#Component(properties=#Property(name=Constants.SERVICE_PID, value="example.configurationservice"))
public class ConfigurationUpdater implements ManagedService, MyConfiguration{
private volatile String message;
#Override
public void updated(#SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
message = (String)properties.get("message");
}
#Override
public String getMessage() {
return message;
}
}
The configuration can then be used like this:
#Component(provides=ExampleConsumer.class,
properties= {
#Property(name = CommandProcessor.COMMAND_SCOPE, value = "example"),
#Property(name = CommandProcessor.COMMAND_FUNCTION, values = {"showMessage"}) })
public class ExampleConsumer {
#ServiceDependency
private volatile MyConfiguration config;
public void showMessage() {
String message = config.getMessage();
System.out.println(message);
}
}