Cannot build Bitfinex Java Client - Not getting any response - stream

So, the main goal here is to connect to the Bitfinex WebSocket by building a WebSocket Client. I would like to start receiving a stream of information(price,trades,etc). The problem is that at this stage I cannot even subscribe to a specific currency pair. In other words, I am sending a request for information to the WebSocket server but I am not receiving any responses and I cannot figure why this is. My code is below.
This is the main method
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
String URL = "wss://api-pub.bitfinex.com/ws/2/";
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
StompSessionHandler sessionHandler = new MyStompSessionHandler();
stompClient.connect(URL,sessionHandler);
new Scanner(System.in).nextLine(); // Don't close immediately.
}
}
This is the MyStompSessionHandler
public class MyStompSessionHandler extends StompSessionHandlerAdapter{
#Override
public void afterConnected(
StompSession session, StompHeaders connectedHeaders) {
System.out.println("New session established : " + session.getSessionId());
System.out.println("wss://api-pub.bitfinex.com/ws/2");
session.send("wss://api-pub.bitfinex.com/ws/2/", getSampleMessage());
System.out.println("Message sent to websocket server");
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
System.out.println("Got an exception:" + exception);
}
#Override
public Type getPayloadType(StompHeaders headers) {
return OutboundMessage.class;
}
private Object getSampleMessage() {
InboundMessage inboundMessage = new InboundMessage();
inboundMessage.setEvent("subscribe");
inboundMessage.setChannel("ticker");
inboundMessage.setSymbol("tBTCUSD");
return inboundMessage;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
InboundMessage msg = (InboundMessage) payload;
System.out.println(msg.toString());
}
}
This is the InboundMessage class
public class InboundMessage {
private String event;
private String channel;
private String symbol;
public InboundMessage() {
}
public InboundMessage(String event, String channel, String symbol) {
this.event = event;
this.channel = channel;
this.symbol = symbol;
}
public String getEvent() {
return event;
}
public void setEvent(String event) {
this.event = event;
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
#Override
public String toString() {
return "InboundMessage{" +
"event='" + event + '\'' +
", channel='" + channel + '\'' +
", symbol='" + symbol + '\'' +
'}';
}

I looked at the Bitfinex website and I don't see any evidence that STOMP is supported. They just have a REST and a WebSocket API. Therefore, using STOMP from your client isn't going to work.

Related

How to use Filter in Spring Cloud Data Flow?

I would like to add a Filter in Spring Cloud Data Flow (SCDF) to debug and see how it works when receiving some requests.
I have tried to implements javax.servlet.Filter and override doFilter method but It's seem not work because there are some default Filter of SCDF had been started before my Filter class.
Are there any way do write a filter for SCDF? Is it possible if I apply this link for this purpose?
This is my Filter class:
#Component
public class CustomFilter implements Filter {
#Override
public void destroy() {
System.out.println("init fillter ");
}
#Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException, ServletException {
try {
HttpServletRequest req = (HttpServletRequest) arg0;
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(
(HttpServletResponse) arg1);
ResettableStreamHttpServletRequest wrappedRequest = new ResettableStreamHttpServletRequest(
(HttpServletRequest) req);
logger.info(">>>>>link: " + req.getServletPath() + "?");
for (Entry<String, String[]> entry : req.getParameterMap().entrySet()) {
logger.info(">>>>>param: " + entry.getKey() + ":" + entry.getValue()[0]);
}
String bodyRequest = IOUtils.toString(wrappedRequest.getReader());
logger.info(">>>>>link: " + req.getServletPath() + "?");
for (Entry<String, String[]> entry : req.getParameterMap().entrySet()) {
logger.info(">>>>>param: " + entry.getKey() + ":" + entry.getValue()[0]);
}
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setAttribute("indent-number", 2);
javax.xml.transform.Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
try {
logger.info("Request: " + bodyRequest);
} catch (Exception e) {
logger.info("Request: " + bodyRequest);
}
wrappedRequest.resetInputStream();
arg2.doFilter(wrappedRequest, responseWrapper);
logger.info("Response: " + IOUtils.toString(responseWrapper.getContentInputStream()));
responseWrapper.copyBodyToResponse();
} catch (Exception ex) {
logger.info("doFilter: " + ex);
}
}
#Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("init fillter " + arg0);
}
private static class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper {
private byte[] rawData;
private HttpServletRequest request;
private ResettableServletInputStream servletStream;
public ResettableStreamHttpServletRequest(HttpServletRequest request) {
super(request);
this.request = request;
this.servletStream = new ResettableServletInputStream();
}
public void resetInputStream() {
servletStream.stream = new ByteArrayInputStream(rawData);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getReader());
servletStream.stream = new ByteArrayInputStream(rawData);
}
return servletStream;
}
#Override
public BufferedReader getReader() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getReader());
servletStream.stream = new ByteArrayInputStream(rawData);
}
return new BufferedReader(new InputStreamReader(servletStream));
}
private class ResettableServletInputStream extends ServletInputStream {
private InputStream stream;
#Override
public int read() throws IOException {
return stream.read();
}
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener arg0) {
}
}
}
}
Spring Cloud Data Flow server is inherently a Spring Boot application and hence it allows you to inject specific configuration beans into Data Flow configuration to extend/customize.
In your case, you can create a bean that extends javax.servlet.http.HttpFilter or implements javax.servlet.Filter and have that bean injected into SCDF configuration.

FeignClient throws instead of returning ResponseEntity with error http status

As I'm using ResponseEntity<T> as return value for my FeignClient method, I was expecting it to return a ResponseEntity with 400 status if it's what the server returns. But instead it throws a FeignException.
How can I get a proper ResponseEntity instead of an Exception from FeignClient ?
Here is my FeignClient:
#FeignClient(value = "uaa", configuration = OauthFeignClient.Conf.class)
public interface OauthFeignClient {
#RequestMapping(
value = "/oauth/token",
method = RequestMethod.POST,
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
ResponseEntity<OauthTokenResponse> token(Map<String, ?> formParams);
class Conf {
#Value("${oauth.client.password}")
String oauthClientPassword;
#Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
#Bean
public Contract feignContract() {
return new SpringMvcContract();
}
#Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("web-client", oauthClientPassword);
}
}
}
and here how I use it:
#PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(#RequestBody #Valid LoginRequest userCredentials) {
Map<String, String> formData = new HashMap<>();
ResponseEntity<OauthTokenResponse> response = oauthFeignClient.token(formData);
//code never reached if contacted service returns a 400
...
}
By the way, solution I gave before works, but my initial intention is bad idea: an error is an error and should not be handled on nominal flow. Throwing an exception, like Feign does, and handling it with an #ExceptionHandler is a better way to go in Spring MVC world.
So two solutions:
add an #ExceptionHandler for FeignException
configure the FeignClient with an ErrorDecoder to translate the error in an Exception your business layer knows about (and already provide #ExceptionHandler for)
I prefer second solution because received error message structure is likely to change from a client to an other, so you can extract finer grained data from those error with a per-client error decoding.
FeignClient with conf (sorry for the noise introduced by feign-form)
#FeignClient(value = "uaa", configuration = OauthFeignClient.Config.class)
public interface OauthFeignClient {
#RequestMapping(
value = "/oauth/token",
method = RequestMethod.POST,
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
DefaultOAuth2AccessToken token(Map<String, ?> formParams);
#Configuration
class Config {
#Value("${oauth.client.password}")
String oauthClientPassword;
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
#Bean
public Decoder springDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
}
#Bean
public Contract feignContract() {
return new SpringMvcContract();
}
#Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("web-client", oauthClientPassword);
}
#Bean
public ErrorDecoder uaaErrorDecoder(Decoder decoder) {
return (methodKey, response) -> {
try {
OAuth2Exception uaaException = (OAuth2Exception) decoder.decode(response, OAuth2Exception.class);
return new SroException(
uaaException.getHttpErrorCode(),
uaaException.getOAuth2ErrorCode(),
Arrays.asList(uaaException.getSummary()));
} catch (Exception e) {
return new SroException(
response.status(),
"Authorization server responded with " + response.status() + " but failed to parse error payload",
Arrays.asList(e.getMessage()));
}
};
}
}
}
Common business exception
public class SroException extends RuntimeException implements Serializable {
public final int status;
public final List<String> errors;
public SroException(final int status, final String message, final Collection<String> errors) {
super(message);
this.status = status;
this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SroException)) return false;
SroException sroException = (SroException) o;
return status == sroException.status &&
Objects.equals(super.getMessage(), sroException.getMessage()) &&
Objects.equals(errors, sroException.errors);
}
#Override
public int hashCode() {
return Objects.hash(status, super.getMessage(), errors);
}
}
Error handler (extracted from a ResponseEntityExceptionHandler extension)
#ExceptionHandler({SroException.class})
public ResponseEntity<Object> handleSroException(SroException ex) {
return new SroError(ex).toResponse();
}
Error response DTO
#XmlRootElement
public class SroError implements Serializable {
public final int status;
public final String message;
public final List<String> errors;
public SroError(final int status, final String message, final Collection<String> errors) {
this.status = status;
this.message = message;
this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
}
public SroError(final SroException e) {
this.status = e.status;
this.message = e.getMessage();
this.errors = Collections.unmodifiableList(e.errors);
}
protected SroError() {
this.status = -1;
this.message = null;
this.errors = null;
}
public ResponseEntity<Object> toResponse() {
return new ResponseEntity(this, HttpStatus.valueOf(this.status));
}
public ResponseEntity<Object> toResponse(HttpHeaders headers) {
return new ResponseEntity(this, headers, HttpStatus.valueOf(this.status));
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SroError)) return false;
SroError sroException = (SroError) o;
return status == sroException.status &&
Objects.equals(message, sroException.message) &&
Objects.equals(errors, sroException.errors);
}
#Override
public int hashCode() {
return Objects.hash(status, message, errors);
}
}
Feign client usage notice how errors are transparently handled (no try / catch) thanks to #ControllerAdvice & #ExceptionHandler({SroException.class})
#RestController
#RequestMapping("/uaa")
public class AuthenticationController {
private static final BearerToken REVOCATION_TOKEN = new BearerToken("", 0L);
private final OauthFeignClient oauthFeignClient;
private final int refreshTokenValidity;
#Autowired
public AuthenticationController(
OauthFeignClient oauthFeignClient,
#Value("${oauth.ttl.refresh-token}") int refreshTokenValidity) {
this.oauthFeignClient = oauthFeignClient;
this.refreshTokenValidity = refreshTokenValidity;
}
#PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(#RequestBody #Valid LoginRequest userCredentials) {
Map<String, String> formData = new HashMap<>();
formData.put("grant_type", "password");
formData.put("client_id", "web-client");
formData.put("username", userCredentials.username);
formData.put("password", userCredentials.password);
formData.put("scope", "openid");
DefaultOAuth2AccessToken response = oauthFeignClient.token(formData);
return ResponseEntity.ok(new LoginTokenPair(
new BearerToken(response.getValue(), response.getExpiresIn()),
new BearerToken(response.getRefreshToken().getValue(), refreshTokenValidity)));
}
#PostMapping("/logout")
public ResponseEntity<LoginTokenPair> revokeTokens() {
return ResponseEntity
.ok(new LoginTokenPair(REVOCATION_TOKEN, REVOCATION_TOKEN));
}
#PostMapping("/refresh")
public ResponseEntity<BearerToken> refreshToken(#RequestHeader("refresh_token") String refresh_token) {
Map<String, String> formData = new HashMap<>();
formData.put("grant_type", "refresh_token");
formData.put("client_id", "web-client");
formData.put("refresh_token", refresh_token);
formData.put("scope", "openid");
DefaultOAuth2AccessToken response = oauthFeignClient.token(formData);
return ResponseEntity.ok(new BearerToken(response.getValue(), response.getExpiresIn()));
}
}
So, looking at source code, it seams that only solution is actually using feign.Response as return type for FeignClient methods and hand decoding the body with something like new ObjectMapper().readValue(response.body().asReader(), clazz) (with a guard on 2xx status of course because for error statuses, it's very likely that body is an error description and not a valid payload ;).
This makes possible to extract and forward status, header, body, etc. even if status is not in 2xx range.
Edit:
Here is a way to forward status, headers and mapped JSON body (if possible):
public static class JsonFeignResponseHelper {
private final ObjectMapper json = new ObjectMapper();
public <T> Optional<T> decode(Response response, Class<T> clazz) {
if(response.status() >= 200 && response.status() < 300) {
try {
return Optional.of(json.readValue(response.body().asReader(), clazz));
} catch(IOException e) {
return Optional.empty();
}
} else {
return Optional.empty();
}
}
public <T, U> ResponseEntity<U> toResponseEntity(Response response, Class<T> clazz, Function<? super T, ? extends U> mapper) {
Optional<U> payload = decode(response, clazz).map(mapper);
return new ResponseEntity(
payload.orElse(null),//didn't find a way to feed body with original content if payload is empty
convertHeaders(response.headers()),
HttpStatus.valueOf(response.status()));
}
public MultiValueMap<String, String> convertHeaders(Map<String, Collection<String>> responseHeaders) {
MultiValueMap<String, String> responseEntityHeaders = new LinkedMultiValueMap<>();
responseHeaders.entrySet().stream().forEach(e ->
responseEntityHeaders.put(e.getKey(), new ArrayList<>(e.getValue())));
return responseEntityHeaders;
}
}
that can be used as follow:
#PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(#RequestBody #Valid LoginRequest userCredentials) throws IOException {
Response response = oauthFeignClient.token();
return feignHelper.toResponseEntity(
response,
OauthTokenResponse.class,
oauthTokenResponse -> new LoginTokenPair(
new BearerToken(oauthTokenResponse.access_token, oauthTokenResponse.expires_in),
new BearerToken(oauthTokenResponse.refresh_token, refreshTokenValidity)));
}
This saves headers and status code, but error message is lost :/

Spring websocket messaging template isn't publishing data from Controller without #Scheduler annotation

#Controller
public class WebServiceController {
private static Logger logger = LoggerFactory.getLogger(WebServiceController.class.getName());
#Autowired
private SimpMessagingTemplate template;
public static Response response;
private final MessageSendingOperations<String> messagingTemplate;
#Autowired
private XmlCommandFactory xmlCommandFactory;
#Autowired
public WebServiceController(final MessageSendingOperations<String> messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#RequestMapping(value = "/handleResponse.htm/{sessionId}")
public void handleRequest(final HttpServletRequest request, final HttpServletResponse httpResponse,
#PathVariable final String sessionId) throws Exception {
httpResponse.setStatus(200);
final StringBuilder buffer = new StringBuilder();
final BufferedReader reader = request.getReader();
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line.trim());
}
if (!buffer.toString().isEmpty()) {
try {
response = xmlCommandFactory.createCommand(buffer.toString(), sessionId);
publishData();
} catch (final Exception ex) {
logger.warn(" Error while processing response ", ex);
}
}
}
#Scheduled(fixedDelay = 2000)
public void publishData() {
try {
if (null != response && !response.getData().isEmpty()) {
this.messagingTemplate.convertAndSend("/update/config/" + response.getSessionId(), response);
}
} catch (final Exception e) {
logger.warn("exception " + e);
}
}
The above code publishes data to the UI at the scheduled delay. But if I comment out #Scheduler annotation and call this method from handleRequest, it doesn't publish data to UI. What could be the reason?

PhoneStateListener causing problems in outgoin calls

Outgoing calls are ended automatically when phonestatelistener is used to monitor the calls.
This is my code and when this is executed it blocks the outgoing calls :
public class CallHelper {
public String number;
private Context ctx;
private TelephonyManager tm;
private CallStateListener callStateListener;
private OutgoingReceiver outgoingReceiver;
SharedPreferences trackMeData;
public CallHelper(Context ctx) {
this.ctx = ctx;
number ="";
callStateListener = new CallStateListener();
outgoingReceiver = new OutgoingReceiver();
trackMeData = ctx.getSharedPreferences("LockedSIM", 0);
}
private class CallStateListener extends PhoneStateListener {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
number = incomingNumber;
sendsmstoph(number);
System.out.println("Incomgin");
Toast.makeText(ctx, "Incoming: " + incomingNumber,Toast.LENGTH_LONG).show();
break;
}
}
}
public class OutgoingReceiver extends BroadcastReceiver {
public OutgoingReceiver() {
}
#Override
public void onReceive(Context context, Intent intent) {
number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Toast.makeText(ctx, "Outgoing: " + number, Toast.LENGTH_LONG).show();
sendsmstoph(number);
}
}

BlackBerry read json string from an URL

I tried to read a json string loading from an URL, but I could not find complete code sample. Can any one provide or point me to a complete client code. I'm newer to BB development.
this is what I have done but still can't get it work please help me.
Thanks!
To read and parse data from an URL you need to implement two routines. First one of them will handle reading data from the specified URL over HTTP connection, and the second one will parse the data.
Check the following application HttpUrlReaderDemoApp, which will first read the data from specified URL and then parse the retrieved data.
URL used to retrieve data: http://codeincloud.tk/json_android_example.php
Sample data format: {"name":"Froyo", "version":"Android 2.2"}
Classes:
HttpUrlReaderDemoApp - UiApplication instance
HttpResponseListener - Interface used to notify other classes about HTTP request status
HttpUrlReader - Reads the data from given url
AppMainScreen - MainScreen instance
DataParser - Parse data
DataModel - Data definition
Screenshots:
Request for data
When data retrieved successfully
Parsed data
Implementation:
HttpUrlReaderDemoApp
public class HttpUrlReaderDemoApp extends UiApplication {
public static void main(String[] args) {
HttpUrlReaderDemoApp theApp = new HttpUrlReaderDemoApp();
theApp.enterEventDispatcher();
}
public HttpUrlReaderDemoApp() {
pushScreen(new AppMainScreen("HTTP Url Reader Demo Application"));
}
}
HttpResponseListener
public interface HttpResponseListener {
public void onHttpResponseFail(String message, String url);
public void onHttpResponseSuccess(byte bytes[], String url);
}
HttpUrlReader
public class HttpUrlReader implements Runnable {
private String url;
private HttpResponseListener listener;
public HttpUrlReader(String url, HttpResponseListener listener) {
this.url = url;
this.listener = listener;
}
private String getConncetionDependentUrlSuffix() {
// Not implemented
return "";
}
private void notifySuccess(byte bytes[], String url) {
if (listener != null) {
listener.onHttpResponseSuccess(bytes, url);
}
}
private void notifyFailure(String message, String url) {
if (listener != null) {
listener.onHttpResponseFail(message, url);
}
}
private boolean isValidUrl(String url) {
return (url != null && url.length() > 0);
}
public void run() {
if (!isValidUrl(url) || listener == null) {
String message = "Invalid parameters.";
message += !isValidUrl(url) ? " Invalid url." : "";
message += (listener == null) ? " Invalid HttpResponseListerner instance."
: "";
notifyFailure(message, url);
return;
}
// update URL depending on connection type
url += DeviceInfo.isSimulator() ? ";deviceside=true"
: getConncetionDependentUrlSuffix();
// Open the connection and retrieve the data
try {
HttpConnection httpConn = (HttpConnection) Connector.open(url);
int status = httpConn.getResponseCode();
if (status == HttpConnection.HTTP_OK) {
InputStream input = httpConn.openInputStream();
byte[] bytes = IOUtilities.streamToBytes(input);
input.close();
notifySuccess(bytes, url);
} else {
notifyFailure("Failed to retrieve data, HTTP response code: "
+ status, url);
return;
}
httpConn.close();
} catch (Exception e) {
notifyFailure("Failed to retrieve data, Exception: ", e.toString());
return;
}
}
}
AppMainScreen
public class AppMainScreen extends MainScreen implements HttpResponseListener {
private final String URL = "http://codeincloud.tk/json_android_example.php";
public AppMainScreen(String title) {
setTitle(title);
}
private MenuItem miReadData = new MenuItem("Read data", 0, 0) {
public void run() {
requestData();
}
};
protected void makeMenu(Menu menu, int instance) {
menu.add(miReadData);
super.makeMenu(menu, instance);
}
public void close() {
super.close();
}
public void showDialog(final String message) {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Dialog.alert(message);
}
});
}
private void requestData() {
Thread urlReader = new Thread(new HttpUrlReader(URL, this));
urlReader.start();
showDialog("Request for data from\n \"" + URL + "\"\n started.");
}
public void onHttpResponseFail(String message, String url) {
showDialog("Failure Mesage:\n" + message + "\n\nUrl:\n" + url);
}
public void onHttpResponseSuccess(byte bytes[], String url) {
showDialog("Data retrived from:\n" + url + "\n\nData:\n"
+ new String(bytes));
// now parse response
DataModel dataModel = DataParser.getData(bytes);
if (dataModel == null) {
showDialog("Failed to parse data: " + new String(bytes));
} else {
showDialog("Parsed Data:\nName: " + dataModel.getName()
+ "\nVersion: " + dataModel.getVersion());
}
}
}
DataParser
public class DataParser {
private static final String NAME = "name";
private static final String VERSION = "version";
public static DataModel getData(byte data[]) {
String rawData = new String(data);
DataModel dataModel = new DataModel();
try {
JSONObject jsonObj = new JSONObject(rawData);
if (jsonObj.has(NAME)) {
dataModel.setName(jsonObj.getString(NAME));
}
if (jsonObj.has(VERSION)) {
dataModel.setVersion(jsonObj.getString(VERSION));
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return dataModel;
}
}
DataModel
public class DataModel {
private String name;
private String version;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String model) {
this.version = model;
}
}

Resources