Compare commits

...

25 Commits

Author SHA1 Message Date
kirillius a3d9be54f7 bump javadoc plugin 2026-07-05 07:29:16 +00:00
kirillius 8d056a7cea hotfix: bump compiler target version 2026-07-05 07:20:10 +00:00
kirillius 60fe180b81 Merge pull request 'added support for !empty response' (#1) from kirillius-patch-1 into master
Reviewed-on: #1
2026-07-05 07:16:16 +00:00
kirillius 0243318870 Обновить pom.xml 2026-07-05 07:15:58 +00:00
kirillius 4939c65f57 added support for !empty response 2026-07-05 07:14:30 +00:00
Gideon Le Grange ec5f665081 Updated README 2024-09-26 14:41:43 +02:00
Gideon Le Grange e3cf6a933a Fixed NPE (Bumped to 3.0.8) 2024-09-26 14:36:54 +02:00
Gideon le Grange ed0468dbf9
Merge pull request #88 from unlimmitted/master
Fix NullPointerException
2024-09-26 14:32:03 +02:00
unlimmitted bfbe50732d Fix NullPointerException 2024-09-21 16:50:56 +03:00
Gideon Le Grange 75a140f79f Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2021-01-18 09:31:58 +02:00
Gideon Le Grange 3424482aab Backup 2021-01-18 09:31:34 +02:00
Gideon le Grange 7a629d3b85
Merge pull request #80 from GideonLeGrange/dependabot/maven/junit-junit-4.13.1
Bump junit from 3.8.1 to 4.13.1
2020-10-13 08:06:54 +02:00
dependabot[bot] 603f34ca6e
Bump junit from 3.8.1 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 3.8.1 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/commits/r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 03:31:10 +00:00
Gideon le Grange 0f312c2232
Update README.md 2020-08-24 17:35:19 +02:00
Gideon le Grange 5bc5263aaf
Create maven.yml 2020-08-24 17:34:42 +02:00
Gideon Le Grange 283b3f68ef Updated pom.xml for 3.0.7 2020-01-08 10:57:34 +02:00
Gideon Le Grange 6654c1f629 Updated README for 3.0.7 2020-01-08 10:16:42 +02:00
Gideon le Grange 15e3f75ca0
Update Config.java 2020-01-04 20:16:15 +02:00
Gideon Le Grange 42c281ab88 Fixed bug in handling nested expressions. Added simple excample class 2020-01-04 20:10:14 +02:00
Gideon Le Grange 8fa8711ec5 Added support for error category to ApiCommandException which was weirdly missing. Not complete 2020-01-03 09:03:22 +02:00
Gideon Le Grange 1f31a6f3b5 Cleanup to parser 2020-01-03 08:52:01 +02:00
Gideon le Grange 7d29f9265e
Update grammar.txt 2020-01-03 08:49:58 +02:00
Gideon Le Grange 2fea5e3a6b Added support for parentheses around expressions as per #72 2020-01-03 08:46:09 +02:00
Gideon le Grange 95c0abfec4
Update README.md 2020-01-03 08:14:04 +02:00
Gideon Le Grange 223cb9ef66 Bumped version to 3.0.6 2020-01-03 08:12:35 +02:00
12 changed files with 212 additions and 77 deletions

24
.github/workflows/maven.yml vendored Normal file
View File

@ -0,0 +1,24 @@
# 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,12 +6,11 @@ This project provides a Java client to manipulate Mikrotik routers using the rem
## Versions
[![Build Status](https://travis-ci.org/GideonLeGrange/mikrotik-java.svg?branch=master)](https://travis-ci.org/GideonLeGrange/mikrotik-java)
![Java CI with Maven](https://github.com/GideonLeGrange/mikrotik-java/workflows/Java%20CI%20with%20Maven/badge.svg)
**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.
**The current stable version is 3.0.8**
Version 3.0.8 fixes a null pointer error
### Changes in version 3.0:
@ -36,7 +35,7 @@ Maven users can use the artifact from Maven Central with this dependency:
<dependency>
<groupId>me.legrange</groupId>
<artifactId>mikrotik</artifactId>
<version>3.0.5</version>
<version>3.0.8</version>
</dependency>
```

10
pom.xml
View File

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

View File

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

View File

@ -0,0 +1,33 @@
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

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

View File

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

View File

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

View File

@ -2,11 +2,15 @@ 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).
*
* @author GideonLeGrange
*/
class Error extends Response {
Error(String tag, String message) {
private String message;
private int category;
Error(String tag, String message, int category) {
super(tag);
this.message = message;
}
@ -18,11 +22,16 @@ class Error extends Response {
String getMessage() {
return message;
}
void setMessage(String message) {
this.message = message;
}
private String message;
int getCategory() {
return category;
}
void setCategory(int category) {
this.category = category;
}
}

View File

@ -3,21 +3,27 @@ package me.legrange.mikrotik.impl;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import me.legrange.mikrotik.impl.Scanner.Token;
/**
* Parse the pseudo-command line into command objects.
*
* @author GideonLeGrange
*/
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 {
Parser parser = new Parser(text);
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 {
command();
while (!is(Token.WHERE, Token.RETURN, Token.EOL)) {
@ -67,8 +73,7 @@ class Parser {
next();
}
cmd.addParameter(new Parameter(name, val.toString()));
}
else {
} else {
cmd.addParameter(new Parameter(name));
}
}
@ -79,7 +84,7 @@ class Parser {
}
private void expr() throws ParseException {
expect(Token.NOT, Token.TEXT);
expect(Token.NOT, Token.TEXT, Token.LEFT_BRACKET);
switch (token) {
case NOT:
notExpr();
@ -92,7 +97,7 @@ class Parser {
case EQUALS:
eqExpr(name);
break;
case NOT_EQUALS :
case NOT_EQUALS:
notExpr(name);
break;
case LESS:
@ -106,23 +111,36 @@ class Parser {
}
}
break;
case LEFT_BRACKET:
nestedExpr();
break;
}
// if you get here, you had a expression, see if you want more.
switch (token) {
case AND : andExpr();
case AND:
andExpr();
break;
case OR : orExpr();
case OR:
orExpr();
break;
}
}
private void nestedExpr() throws ParseException {
expect(Token.LEFT_BRACKET);
next();
expr();
expect(Token.RIGHT_BRACKET);
next();
}
private void andExpr() throws ParseException {
next(); // eat and
expr();
cmd.addQuery("?#&");
}
private void orExpr() throws ParseException {
private void orExpr() throws ParseException {
next(); // eat or
expr();
cmd.addQuery("?#|");
@ -135,7 +153,7 @@ class Parser {
cmd.addQuery("?#!");
}
private void eqExpr(String name) throws ParseException {
private void eqExpr(String name) throws ParseException {
next(); // eat =
expect(Token.TEXT);
cmd.addQuery(String.format("?%s=%s", name, text));
@ -178,19 +196,21 @@ class Parser {
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))
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) {
if (this.token == want) return true;
}
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 {
token = scanner.next();
while (token == Token.WS) {

View File

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