Compare commits

..

No commits in common. "master" and "v3.0.6" have entirely different histories.

12 changed files with 77 additions and 212 deletions

View File

@ -1,24 +0,0 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn -B package --file pom.xml

View File

@ -6,11 +6,12 @@ This project provides a Java client to manipulate Mikrotik routers using the rem
## Versions ## Versions
![Java CI with Maven](https://github.com/GideonLeGrange/mikrotik-java/workflows/Java%20CI%20with%20Maven/badge.svg) [![Build Status](https://travis-ci.org/GideonLeGrange/mikrotik-java.svg?branch=master)](https://travis-ci.org/GideonLeGrange/mikrotik-java)
**The current stable version is 3.0.8** **The current stable version is 3.0.5**
Version 3.0.5 implements a change in the way `login()` is implemented by RouterOS from version 6.43 onward. See pull request #60 for details.
Version 3.0.8 fixes a null pointer error
### Changes in version 3.0: ### Changes in version 3.0:
@ -35,7 +36,7 @@ Maven users can use the artifact from Maven Central with this dependency:
<dependency> <dependency>
<groupId>me.legrange</groupId> <groupId>me.legrange</groupId>
<artifactId>mikrotik</artifactId> <artifactId>mikrotik</artifactId>
<version>3.0.8</version> <version>3.0.5</version>
</dependency> </dependency>
``` ```

10
pom.xml
View File

@ -9,7 +9,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>me.legrange</groupId> <groupId>me.legrange</groupId>
<artifactId>mikrotik</artifactId> <artifactId>mikrotik</artifactId>
<version>3.0.8.1</version> <version>3.0.5</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Mikrotik API Java Client Library</name> <name>Mikrotik API Java Client Library</name>
<url>https://github.com/GideonLeGrange/mikrotik-java</url> <url>https://github.com/GideonLeGrange/mikrotik-java</url>
@ -33,8 +33,8 @@
<version>2.3.2</version> <version>2.3.2</version>
<configuration> <configuration>
<showDeprecation>true</showDeprecation> <showDeprecation>true</showDeprecation>
<source>8</source> <source>1.7</source>
<target>8</target> <target>1.7</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@ -73,7 +73,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.7.0</version> <version>2.9.1</version>
<configuration> <configuration>
<excludePackageNames>example;*.impl</excludePackageNames> <excludePackageNames>example;*.impl</excludePackageNames>
</configuration> </configuration>
@ -161,7 +161,7 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.13.1</version> <version>3.8.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -7,8 +7,8 @@ package examples;
*/ */
public class Config { public class Config {
public static final String HOST = "ce2.ter.cpt.adept.za.net"; public static final String HOST = "192.168.1.1";
public static final String USERNAME = "adept"; public static final String USERNAME = "admin";
public static final String PASSWORD = "34ffp9"; public static final String PASSWORD = "";
} }

View File

@ -1,33 +0,0 @@
package examples;
import me.legrange.mikrotik.MikrotikApiException;
import java.util.List;
import java.util.Map;
/**
* Example 2: A command that returns results. Print all interfaces
*
* @author gideon
*/
public class NestedExpressions extends Example {
public static void main(String... args) throws Exception {
NestedExpressions ex = new NestedExpressions();
ex.connect();
ex.test("/ip/firewall/nat/print where (src-address=\"192.168.15.52\" or src-address=\"192.168.15.53\")");
ex.test("/ip/firewall/nat/print where chain=api_test and (src-address=192.168.15.52) and action=log ");
ex.test("/ip/firewall/nat/print where chain=api_test and (src-address=192.168.15.53 or src-address=192.168.15.52) and action=log ");
ex.test("/ip/firewall/nat/print where chain=api_test and (src-address=\"192.168.15.53\" or src-address=\"192.168.15.52\") and action=log ");
ex.disconnect();
}
private void test(String cmd) throws MikrotikApiException {
System.out.println("Command: " + cmd);
List<Map<String, String>> results = con.execute(cmd);
for (Map<String, String> result : results) {
System.out.println(result);
}
System.out.println();
}
}

View File

@ -1,28 +0,0 @@
package examples;
import me.legrange.mikrotik.MikrotikApiException;
/**
* Example 1: A very simple command: Reboot the remote router
* @author gideon
*/
public class ScriptCommand extends Example {
public static void main(String...args) throws Exception {
ScriptCommand ex = new ScriptCommand();
ex.connect();
ex.test();
ex.disconnect();
}
private void test() throws MikrotikApiException {
con.execute("/ip/hotspot/user/profile/add name=\"cahyo-cek-2\" idle-timeout=none keepalive-timeout=2m status-autorefresh=1m shared-users=1 add-mac-cookie=true mac-cookie-timeout=3d parent-queue=none rate-limit=512k/512k on-login=:put(\",re,3000,12d,3000,,Enable,\")");
}
///
/*
/ip/hotspot/user/profile/add name="cahyo-cek-2" idle-timeout=none keepalive-timeout=2m status-autorefresh=1m shared-users=1 add-mac-cookie=true mac-cookie-timeout=3d parent-queue=none rate-limit=512k/512k on-login=:put (",re,3000,12d,3000,,Enable,");
/system scheduler add name="usercahyo-cek-2-$user" interval=12:00:00 on-even="/ip hotspot active remove [find user=\"$user\"] ; /ip hotspot user set \"$user\" disable=yes ; /system scheduler remove [find name=usercahyo-cek-2-$user]"
*/
}

View File

@ -1,13 +1,12 @@
command = action [ query ] [ return ] command = action [ query ] [ return ]
action = ("/" word)+ action = ("/" word)+
query = "where" expr query = "where" expr
expr = expr "and" expr | expr "or" expr | "not" expr | hasExpr | eqExpr | lessExpr | moreExpr | notExpr | nestedExpr expr = expr "and" expr | expr "or" expr | "not" expr | hasExpr | eqExpr | lessExpr | moreExpr | notExpr
hasExpr = name hasExpr = name
eqExpr = name "=" value eqExpr = name "=" value
lessExpr = name "<" value lessExpr = name "<" value
moreExpr = name ">" value moreExpr = name ">" value
notExpr = name "!=" value notExpr = name "!=" value
nestedExpr = "(" expr ")"
return = "return" (name)+ return = "return" (name)+

View File

@ -4,40 +4,31 @@ import me.legrange.mikrotik.MikrotikApiException;
/** /**
* Thrown when the Mikrotik returns an error when receiving our command. * Thrown when the Mikrotik returns an error when receiving our command.
*
* @author GideonLeGrange * @author GideonLeGrange
*/ */
public class ApiCommandException extends MikrotikApiException { public class ApiCommandException extends MikrotikApiException {
private String tag = null;
private int category = 0;
/** /** return the tag associated with this exception, if there is one
* return the tag associated with this exception, if there is one * @return the tag associated with this exception. Null if there is no tag*/
*
* @return the tag associated with this exception. Null if there is no tag
*/
public String getTag() { public String getTag() {
return tag; return tag;
} }
ApiCommandException(String msg) { ApiCommandException(String msg) {
super(msg); super(msg);
} }
ApiCommandException(String msg, Throwable err) { ApiCommandException(String msg, Throwable err) {
super(msg, err); super(msg, err);
} }
public int getCategory() {
return category;
}
ApiCommandException(Error err) { ApiCommandException(Error err) {
super(err.getMessage()); super(err.getMessage());
tag = err.getTag(); tag = err.getTag();
category = err.getCategory();
} }
private String tag = null;
} }

View File

@ -14,7 +14,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import me.legrange.mikrotik.ApiConnection; import me.legrange.mikrotik.ApiConnection;
import me.legrange.mikrotik.ApiConnectionException; import me.legrange.mikrotik.ApiConnectionException;
@ -163,7 +162,8 @@ public final class ApiConnectionImpl extends ApiConnection {
} }
private synchronized String nextTag() { private synchronized String nextTag() {
return Integer.toHexString(_tag.incrementAndGet()); _tag++;
return Integer.toHexString(_tag);
} }
private Socket sock = null; private Socket sock = null;
@ -173,7 +173,7 @@ public final class ApiConnectionImpl extends ApiConnection {
private Reader reader; private Reader reader;
private Processor processor; private Processor processor;
private final Map<String, ResultListener> listeners; private final Map<String, ResultListener> listeners;
private final AtomicInteger _tag = new AtomicInteger(0); private Integer _tag = 0;
private int timeout = ApiConnection.DEFAULT_COMMAND_TIMEOUT; private int timeout = ApiConnection.DEFAULT_COMMAND_TIMEOUT;
/** /**
@ -186,7 +186,7 @@ public final class ApiConnectionImpl extends ApiConnection {
} }
private String take() throws ApiConnectionException, ApiDataException { private String take() throws ApiConnectionException, ApiDataException {
Object val; Object val = null;
try { try {
val = queue.take(); val = queue.take();
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
@ -223,7 +223,7 @@ public final class ApiConnectionImpl extends ApiConnection {
private void put(Object data) { private void put(Object data) {
try { try {
queue.put(data); queue.put(data);
} catch (InterruptedException ignored) { } catch (InterruptedException ex) {
} }
} }
@ -248,31 +248,27 @@ public final class ApiConnectionImpl extends ApiConnection {
} catch (ApiCommandException ex) { } catch (ApiCommandException ex) {
String tag = ex.getTag(); String tag = ex.getTag();
if (tag != null) { if (tag != null) {
res = new Error(tag, ex.getMessage(), ex.getCategory()); res = new Error(tag, ex.getMessage());
} else { } else {
continue; continue;
} }
} catch (MikrotikApiException ex) { } catch (MikrotikApiException ex) {
continue; continue;
} }
if (res.getTag() != null) { ResultListener l = listeners.get(res.getTag());
ResultListener l = listeners.get(res.getTag()); if (l != null) {
if (l != null) { if (res instanceof Result) {
if (res instanceof Result) { l.receive((Result) res);
l.receive((Result) res); } else if (res instanceof Done) {
} else if (res instanceof Done) { if (l instanceof SyncListener) {
if (l instanceof SyncListener) { ((SyncListener) l).completed((Done) res);
((SyncListener) l).completed((Done) res); } else {
} else { l.completed();
l.completed();
}
listeners.remove(res.getTag());
} else if (res instanceof Error) {
l.error(new ApiCommandException((Error) res));
} }
listeners.remove(res.getTag());
} else if (res instanceof Error) {
l.error(new ApiCommandException((Error) res));
} }
} else {
nextTag();
} }
} }
} }
@ -280,7 +276,7 @@ public final class ApiConnectionImpl extends ApiConnection {
private void nextLine() throws ApiConnectionException, ApiDataException { private void nextLine() throws ApiConnectionException, ApiDataException {
if (lines.isEmpty()) { if (lines.isEmpty()) {
String block = reader.take(); String block = reader.take();
String[] parts = block.split("\n"); String parts[] = block.split("\n");
lines.addAll(Arrays.asList(parts)); lines.addAll(Arrays.asList(parts));
} }
line = lines.remove(0); line = lines.remove(0);
@ -293,7 +289,7 @@ public final class ApiConnectionImpl extends ApiConnection {
private String peekLine() throws ApiConnectionException, ApiDataException { private String peekLine() throws ApiConnectionException, ApiDataException {
if (lines.isEmpty()) { if (lines.isEmpty()) {
String block = reader.take(); String block = reader.take();
String[] parts = block.split("\n"); String parts[] = block.split("\n");
lines.addAll(Arrays.asList(parts)); lines.addAll(Arrays.asList(parts));
} }
return lines.get(0); return lines.get(0);
@ -306,10 +302,10 @@ public final class ApiConnectionImpl extends ApiConnection {
switch (line) { switch (line) {
case "!re": case "!re":
return unpackRe(); return unpackRe();
case "!empty":
case "!done": case "!done":
return unpackDone(); return unpackDone();
case "!trap": case "!trap":
return unpackError();
case "!halt": case "!halt":
return unpackError(); return unpackError();
case "": case "":
@ -320,10 +316,12 @@ public final class ApiConnectionImpl extends ApiConnection {
private Result unpackRe() throws ApiDataException, ApiConnectionException { private Result unpackRe() throws ApiDataException, ApiConnectionException {
nextLine(); nextLine();
int l = 0;
Result res = new Result(); Result res = new Result();
while (!line.startsWith(("!"))) { while (!line.startsWith(("!"))) {
l++;
if (line.startsWith(("="))) { if (line.startsWith(("="))) {
String[] parts = line.split("=", 3); String parts[] = line.split("=", 3);
if (parts.length == 3) { if (parts.length == 3) {
if (!parts[2].endsWith("\r")) { if (!parts[2].endsWith("\r")) {
res.put(parts[1], unpackResult(parts[2])); res.put(parts[1], unpackResult(parts[2]));
@ -340,7 +338,7 @@ public final class ApiConnectionImpl extends ApiConnection {
throw new ApiDataException(String.format("Malformed line '%s'", line)); throw new ApiDataException(String.format("Malformed line '%s'", line));
} }
} else if (line.startsWith(".tag=")) { } else if (line.startsWith(".tag=")) {
String[] parts = line.split("=", 2); String parts[] = line.split("=", 2);
if (parts.length == 2) { if (parts.length == 2) {
res.setTag(parts[1]); res.setTag(parts[1]);
} }
@ -381,12 +379,12 @@ public final class ApiConnectionImpl extends ApiConnection {
while (!line.startsWith("!")) { while (!line.startsWith("!")) {
if (line.startsWith(".tag=")) { if (line.startsWith(".tag=")) {
String[] parts = line.split("=", 2); String parts[] = line.split("=", 2);
if (parts.length == 2) { if (parts.length == 2) {
done.setTag(parts[1]); done.setTag(parts[1]);
} }
} else if (line.startsWith(("=ret"))) { } else if (line.startsWith(("=ret"))) {
String[] parts = line.split("=", 3); String parts[] = line.split("=", 3);
if (parts.length == 3) { if (parts.length == 3) {
done.setHash(parts[2]); done.setHash(parts[2]);
} else { } else {
@ -410,16 +408,13 @@ public final class ApiConnectionImpl extends ApiConnection {
if (hasNextLine()) { if (hasNextLine()) {
while (!line.startsWith("!")) { while (!line.startsWith("!")) {
if (line.startsWith(".tag=")) { if (line.startsWith(".tag=")) {
String[] parts = line.split("=", 2); String parts[] = line.split("=", 2);
if (parts.length == 2) { if (parts.length == 2) {
err.setTag(parts[1]); err.setTag(parts[1]);
} }
} else if (line.startsWith("=message=")) { } else if (line.startsWith("=message=")) {
err.setMessage(line.split("=", 3)[2]); err.setMessage(line.split("=", 3)[2]);
} }
else if (line.startsWith("=category=")) {
err.setCategory(Integer.parseInt(line.split("=", 3)[2]));
}
if (hasNextLine()) { if (hasNextLine()) {
nextLine(); nextLine();
} else { } else {

View File

@ -1,16 +1,12 @@
package me.legrange.mikrotik.impl; package me.legrange.mikrotik.impl;
/** /**
* Used to encapsulate API error information. We need to pass both the message and the tag (if one was used). * Used to encapsulate API error information. We need to pass both the message and the tag (if one was used).
*
* @author GideonLeGrange * @author GideonLeGrange
*/ */
class Error extends Response { class Error extends Response {
private String message; Error(String tag, String message) {
private int category;
Error(String tag, String message, int category) {
super(tag); super(tag);
this.message = message; this.message = message;
} }
@ -22,16 +18,11 @@ class Error extends Response {
String getMessage() { String getMessage() {
return message; return message;
} }
void setMessage(String message) { void setMessage(String message) {
this.message = message; this.message = message;
} }
private String message;
int getCategory() {
return category;
}
void setCategory(int category) {
this.category = category;
}
} }

View File

@ -3,30 +3,24 @@ package me.legrange.mikrotik.impl;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import me.legrange.mikrotik.impl.Scanner.Token; import me.legrange.mikrotik.impl.Scanner.Token;
/** /**
* Parse the pseudo-command line into command objects. * Parse the pseudo-command line into command objects.
*
* @author GideonLeGrange * @author GideonLeGrange
*/ */
class Parser { class Parser {
/** /** parse the given bit of text into a Command object */
* parse the given bit of text into a Command object
*/
static Command parse(String text) throws ParseException { static Command parse(String text) throws ParseException {
Parser parser = new Parser(text); Parser parser = new Parser(text);
return parser.parse(); return parser.parse();
} }
/** /** run parse on the internal data and return the command object */
* run parse on the internal data and return the command object
*/
private Command parse() throws ParseException { private Command parse() throws ParseException {
command(); command();
while (!is(Token.WHERE, Token.RETURN, Token.EOL)) { while (!is(Token.WHERE, Token.RETURN, Token.EOL)) {
param(); param();
} }
if (token == Token.WHERE) { if (token == Token.WHERE) {
@ -38,7 +32,7 @@ class Parser {
expect(Token.EOL); expect(Token.EOL);
return cmd; return cmd;
} }
private void command() throws ParseException { private void command() throws ParseException {
StringBuilder path = new StringBuilder(); StringBuilder path = new StringBuilder();
do { do {
@ -73,7 +67,8 @@ class Parser {
next(); next();
} }
cmd.addParameter(new Parameter(name, val.toString())); cmd.addParameter(new Parameter(name, val.toString()));
} else { }
else {
cmd.addParameter(new Parameter(name)); cmd.addParameter(new Parameter(name));
} }
} }
@ -84,7 +79,7 @@ class Parser {
} }
private void expr() throws ParseException { private void expr() throws ParseException {
expect(Token.NOT, Token.TEXT, Token.LEFT_BRACKET); expect(Token.NOT, Token.TEXT);
switch (token) { switch (token) {
case NOT: case NOT:
notExpr(); notExpr();
@ -97,7 +92,7 @@ class Parser {
case EQUALS: case EQUALS:
eqExpr(name); eqExpr(name);
break; break;
case NOT_EQUALS: case NOT_EQUALS :
notExpr(name); notExpr(name);
break; break;
case LESS: case LESS:
@ -111,36 +106,23 @@ class Parser {
} }
} }
break; break;
case LEFT_BRACKET:
nestedExpr();
break;
} }
// if you get here, you had a expression, see if you want more. // if you get here, you had a expression, see if you want more.
switch (token) { switch (token) {
case AND: case AND : andExpr();
andExpr();
break; break;
case OR: case OR : orExpr();
orExpr();
break; break;
} }
} }
private void nestedExpr() throws ParseException {
expect(Token.LEFT_BRACKET);
next();
expr();
expect(Token.RIGHT_BRACKET);
next();
}
private void andExpr() throws ParseException { private void andExpr() throws ParseException {
next(); // eat and next(); // eat and
expr(); expr();
cmd.addQuery("?#&"); cmd.addQuery("?#&");
} }
private void orExpr() throws ParseException { private void orExpr() throws ParseException {
next(); // eat or next(); // eat or
expr(); expr();
cmd.addQuery("?#|"); cmd.addQuery("?#|");
@ -153,7 +135,7 @@ class Parser {
cmd.addQuery("?#!"); cmd.addQuery("?#!");
} }
private void eqExpr(String name) throws ParseException { private void eqExpr(String name) throws ParseException {
next(); // eat = next(); // eat =
expect(Token.TEXT); expect(Token.TEXT);
cmd.addQuery(String.format("?%s=%s", name, text)); cmd.addQuery(String.format("?%s=%s", name, text));
@ -172,7 +154,7 @@ class Parser {
cmd.addQuery("?#!"); cmd.addQuery("?#!");
next(); next();
} }
private void moreExpr(String name) throws ScanException { private void moreExpr(String name) throws ScanException {
next(); // eat > next(); // eat >
cmd.addQuery(String.format("?>%s=%s", name, text)); cmd.addQuery(String.format("?>%s=%s", name, text));
@ -196,21 +178,19 @@ class Parser {
cmd.addProperty(props.toArray(new String[props.size()])); cmd.addProperty(props.toArray(new String[props.size()]));
} }
private void expect(Token... tokens) throws ParseException { private void expect(Token...tokens) throws ParseException {
if (!is(tokens)) if (!is(tokens))
throw new ParseException(String.format("Expected %s but found %s at position %d", Arrays.asList(tokens), this.token, scanner.pos())); throw new ParseException(String.format("Expected %s but found %s at position %d", Arrays.asList(tokens), this.token, scanner.pos()));
} }
private boolean is(Token... tokens) { private boolean is(Token...tokens) {
for (Token want : tokens) { for (Token want : tokens) {
if (this.token == want) return true; if (this.token == want) return true;
} }
return false; return false;
} }
/** /** move to the next token returned by the scanner */
* move to the next token returned by the scanner
*/
private void next() throws ScanException { private void next() throws ScanException {
token = scanner.next(); token = scanner.next();
while (token == Token.WS) { while (token == Token.WS) {
@ -224,7 +204,7 @@ class Parser {
scanner = new Scanner(line); scanner = new Scanner(line);
next(); next();
} }
private final Scanner scanner; private final Scanner scanner;
private Token token; private Token token;
private String text; private String text;

View File

@ -28,7 +28,6 @@ class Scanner {
SLASH("/"), COMMA(","), EOL(), WS, TEXT, SLASH("/"), COMMA(","), EOL(), WS, TEXT,
LESS("<"), MORE(">"), EQUALS("="), NOT_EQUALS("!="), PIPE("!"), LESS("<"), MORE(">"), EQUALS("="), NOT_EQUALS("!="), PIPE("!"),
LEFT_BRACKET("("), RIGHT_BRACKET(")"),
WHERE, NOT, AND, OR, RETURN; WHERE, NOT, AND, OR, RETURN;
@Override @Override
@ -81,12 +80,6 @@ class Scanner {
case '=': case '=':
nextChar(); nextChar();
return EQUALS; return EQUALS;
case '(' :
nextChar();
return LEFT_BRACKET;
case ')' :
nextChar();
return RIGHT_BRACKET;
case '!': case '!':
return pipe(); return pipe();
case '"': case '"':
@ -121,7 +114,7 @@ class Scanner {
*/ */
private Token name() throws ScanException { private Token name() throws ScanException {
text = new StringBuilder(); text = new StringBuilder();
while (!in(c, "[ \t\r\n=<>!)]")) { while (!in(c, "[ \t\r\n=<>!]")) {
text.append(c); text.append(c);
nextChar(); nextChar();
} }