Compare commits

...

62 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
Gideon le Grange 7aac1b95e2
Merge pull request #67 from Metori/master
Fixed issue with special chars in login credentials
2019-10-04 15:04:51 +02:00
Artem Pinchuk 20a5f3fdf6 Fixed issue with special chars in login credentials 2019-04-18 16:16:05 +10:00
Gideon le Grange 7e9c0000a5
Removed obsolete documentation 2018-11-13 08:56:37 +02:00
Gideon le Grange d33f798510
Change order in documentation 2018-11-13 08:55:02 +02:00
Gideon le Grange af9fc52336 Release 3.0.5 2018-11-08 06:39:24 +02:00
Gideon le Grange acefe40f25 Release 3.0.5 2018-11-08 06:38:39 +02:00
Gideon le Grange 8cba90814f
Merge pull request #60 from GideonLeGrange/login-change
Updated login code to support RouterOS 6.43+
2018-11-08 06:35:57 +02:00
Gideon le Grange 81efcd6c73 Implemented support for new login method used from 6.43 2018-11-08 06:30:58 +02:00
Gideon le Grange afad1e6d30 Implemented support for new login method used from 6.43 2018-11-08 06:27:35 +02:00
Gideon le Grange bd969f0d69
Fixed LICENCE.md in README 2018-03-20 12:04:05 +02:00
Gideon le Grange 6d9141a6d2 Updated README for 3.0.4 2017-08-07 21:09:18 +02:00
Gideon le Grange b7a162099f Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2017-08-07 21:02:04 +02:00
Gideon le Grange 80947044c6 Removed more stray Kotlin. v3.0.4 2017-08-07 21:01:48 +02:00
Gideon le Grange 3b45c541db Merge pull request #51 from GideonLeGrange/3.0.4-cleanup
Code cleanups
2017-08-07 20:59:36 +02:00
Gideon le Grange 4060e73131 Added travis 2017-08-07 20:58:21 +02:00
Gideon le Grange 0116183b7d Update CONTRIBUTING.md 2017-08-07 17:26:23 +02:00
Gideon le Grange 2f679ecebf Changed README on help & contribution 2017-08-07 17:20:42 +02:00
Gideon le Grange 0deb1df41e Create CONTRIBUTING.md 2017-08-07 17:11:55 +02:00
Gideon le Grange 293c9b36fb Code cleanups 2017-08-07 15:23:06 +02:00
Gideon le Grange 9e90653bc8 Removed stray kotlin plugin from POM 2017-08-03 04:59:49 +02:00
Gideon le Grange 5ab42ddf80 Added un-set with value-time example 2017-06-19 17:47:44 +02:00
Gideon le Grange 1f9eef7d02 Fixed README typos 2017-01-05 15:23:52 +02:00
Gideon le Grange 6440ca0290 Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2017-01-05 15:20:37 +02:00
Gideon le Grange aae18ec248 Fixed typo in README 2017-01-05 15:19:49 +02:00
Gideon le Grange f9c7db8ae5 Added try with resources example 2017-01-05 15:19:49 +02:00
Matthew Buckett 6b1b397704 Fixed example code to use close() 2017-01-05 15:19:49 +02:00
Gideon le Grange 36820ec411 Removed stray example 2017-01-05 15:08:42 +02:00
Gideon le Grange ee8d1ba609 Added thread names for easier debug 2017-01-05 15:07:48 +02:00
Gideon le Grange b52c80b600 Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2016-10-08 20:03:12 +02:00
Gideon le Grange 40ebed669a Added CharSets example with reference to #40 2016-10-08 20:02:54 +02:00
Gideon le Grange 56bd38672e Update README.md 2016-10-08 19:34:25 +02:00
Gideon le Grange b606fa9574 Preparing for 3.0.3 release 2016-10-08 19:32:40 +02:00
Gideon le Grange e6b714b23a Removed debug example 2016-10-08 19:28:36 +02:00
Gideon le Grange 7f5423dcec Merge branch 'master' of github.com:GideonLeGrange/mikrotik-java 2016-10-08 19:05:41 +02:00
Gideon le Grange a6230a82bf Fix #40. Changed string encoding to UTF8 when sending bytes accross the network 2016-10-08 19:05:09 +02:00
Gideon le Grange 6fe6aeb511 Update README.md 2016-10-07 16:21:15 +02:00
Gideon le Grange e627fc7212 Fix of README for 3.0.2 2016-10-06 22:43:35 +02:00
21 changed files with 559 additions and 336 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

3
.gitignore vendored
View File

@ -22,3 +22,6 @@ nbactions.xml
# Maven # Maven
pom.xml.versionsBackup pom.xml.versionsBackup
# MacOS
.DS_Store

1
.travis.yml Normal file
View File

@ -0,0 +1 @@
language: java

54
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,54 @@
Thank you for considering contributing to this project. This library is one tiny way I am giving back to the open source community, and I am
always amazed and humbled when users contribute back.
There are several ways of contributing to this small open source library:
* You can ask for help
* You can log a bug
* You can contribute code
# Ask for help
If you experience a problem using the mikrotik-java library, the best way to get in touch with me is to either send me an
email or open an issue on GitHub. While you can seek help on StackOverflow or the Mikrotik Forums (both of which I've seen
people do), I do not read those sites often. If you wish to log your question there (it makes sense for it to be searchable or
in a place where other interested people can find it) and want me to take a look at it, please send me an email pointing to
the question.
I find that it is very easy to help users of this library if they include the following things which are usually quite relevant:
* A snipped of sample code showing what they're doing
* The stack trace (if there is one) when it goes wrong
* Version of the library they're using
# Log a bug
If you are pretty sure you have found a bug in the library, please log a bug by opening an issue on GithHub. When things get buggy,
the following information (mostly the same as for asking help) becomes important:
* A snipped of sample code showing what they're doing
* The stack trace (if there is one) when it goes wrong
* Version of the library they're using
* The version of RouterOS they're connecting to
I take bug reports very seriously, and will usually bring out immediate minor versions to fix show stoppers.
# Contribute code
If you want to contribute code to this library, you're welcome. Please read this section before submitting pull requests.
## If you're fixing a bug
If you've fixed a bug, please submit the pull request on GitHub. If there is an already existing issue being fixed, please
reference that issue so I can look at what you're fixing, and if not, please explain the issue you're fixing in the pull request.
## If you want new functionality or API changes
Please contact me via email before you create a pull request for feature additions or API changes. The mikrotik-java library is stable and,
for it's current purpose, feature complete. The situations in which I will add features to the library or change it's interface are:
* If Mikrotik adds new RouterOS functionality that requires an API change to remain feature complete
* If some very bad design flaw in the interface is discovered
* If somebody has a truly compelling reason for adding to the library. To be compelling means the added functionality must be useful
for more users than just the submitter
* If some new pattern or convention arises in the Java standard libraries that is useful
I *really* hate declining people's PRs, but I will do so if I don't think they add value as explained above, or if I can't figure out why
they want the change. For this reason, if you are at all unsure if your PR will be accepted, please contact me and discuss what you want
before submitting a PR.
Gideon

View File

@ -6,10 +6,11 @@ This project provides a Java client to manipulate Mikrotik routers using the rem
## Versions ## Versions
**The current stable version is 3.0.2** ![Java CI with Maven](https://github.com/GideonLeGrange/mikrotik-java/workflows/Java%20CI%20with%20Maven/badge.svg)
Version 3.0.2 fixes a bug that where file download and some other multi-line content results caused the API call to time out rather than return a result. This **The current stable version is 3.0.8**
was reported in #43 and fixed by #37.
Version 3.0.8 fixes a null pointer error
### Changes in version 3.0: ### Changes in version 3.0:
@ -22,13 +23,9 @@ Version 3.0 addresses the problems the API had around TLS encryption. The way se
Further changes include: Further changes include:
* The previously deprecated `disconnect()` method is removed. * The previously deprecated `disconnect()` method is removed.
#### Version 2.x #### Versions 1.x and 2.x
Version 2.2 is the last version 2.x release and will be supported for a limited time. Versions 1 and 2 are considered *obsolete* and will no longer be supported or patched.
#### Version 1.x
Version 1 is considered *obsolete* and will no longer be supported or patched.
## Getting the API ## Getting the API
@ -38,15 +35,17 @@ 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.1</version> <version>3.0.8</version>
</dependency> </dependency>
``` ```
You can also download the pre-built jar file, or a zip or tar.gz file with the source for the latest release [here](https://github.com/GideonLeGrange/mikrotik-java/releases/latest) You can also download the pre-built jar file, or a zip or tar.gz file with the source for the latest release [here](https://github.com/GideonLeGrange/mikrotik-java/releases/latest)
## Contributing ## Asking for help or contributing
I welcome contributions, be it bug fixes or other improvements. If you fix or change something, please submit a pull request. If you want to report a bug, please open an issue. General questions are also welcome. I welcome contributions, be it bug fixes or other improvements.
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute to this project.
# Using the API # Using the API
@ -66,7 +65,7 @@ Here is a simple example: Connect to a router and reboot it.
ApiConnection con = ApiConnection.connect("10.0.1.1"); // connect to router ApiConnection con = ApiConnection.connect("10.0.1.1"); // connect to router
con.login("admin","password"); // log in to router con.login("admin","password"); // log in to router
con.execute("/system/reboot"); // execute a command con.execute("/system/reboot"); // execute a command
con.disconnect(); // disconnect from router con.close(); // disconnect from router
``` ```
The above example shows a easy way of creating an unencrypted connection using the default API port and timeout, which is useful for development and testing. The above example shows a easy way of creating an unencrypted connection using the default API port and timeout, which is useful for development and testing.
@ -82,7 +81,6 @@ Above an instance of the default SSL socket factory is passed to the API. This w
RouterOS also supports anonymous TLS. An example showing how to create a socket factory for anonymous TLS is `AnonymousSocketFactory` in the examples directory. RouterOS also supports anonymous TLS. An example showing how to create a socket factory for anonymous TLS is `AnonymousSocketFactory` in the examples directory.
### Connection timeouts ### Connection timeouts
By default, the API will generate an exception if it cannot connect to the specified router. This can take place immediately (typically if the OS returns a 'Connection refused' error), but can also take up to 60 seconds if the router host is firewalled or if there are other network problems. This 60 seconds is the 'default connection timeout' an can be overridded by passing the preferred timeout to the APi as last parameter in a ```connect()``` call. For example: By default, the API will generate an exception if it cannot connect to the specified router. This can take place immediately (typically if the OS returns a 'Connection refused' error), but can also take up to 60 seconds if the router host is firewalled or if there are other network problems. This 60 seconds is the 'default connection timeout' an can be overridded by passing the preferred timeout to the APi as last parameter in a ```connect()``` call. For example:
@ -100,12 +98,22 @@ DEFAULT_PORT | Default TCP `port` value for unencrypyted connections | 8728
DEFAULT_TLS_PORT | Default TCP `port` value for encrypyted connections | 8729 DEFAULT_TLS_PORT | Default TCP `port` value for encrypyted connections | 8729
DEFAULT_CONNECTION_TIMEOUT | Default connection `timeout` value (ms) | 60000 DEFAULT_CONNECTION_TIMEOUT | Default connection `timeout` value (ms) | 60000
### Try with resources
The API can also be used in a "try with resources" statement which will ensure that the connection is closed:
```java
try (ApiConnection con = ApiConnection.connect(SocketFactory.getDefault(), Config.HOST, ApiConnection.DEFAULT_PORT, 2000)) {
con.login(Config.USERNAME, Config.PASSWORD);
con.execute("/user/add name=eric");
}
```
In following examples the connection, login and disconnection code will not be repeated. In all cases it is assumed that an `ApiConnection` has been established, `login()` has been called, and that the connection is called `con`. In following examples the connection, login and disconnection code will not be repeated. In all cases it is assumed that an `ApiConnection` has been established, `login()` has been called, and that the connection is called `con`.
## Reading data ## Reading data
A simple example that returns a result: Print all interfaces. A simple example that returns a result - Print all interfaces:
```java ```java
@ -133,7 +141,7 @@ List<Map<String, String>> rs = con.execute("/interface/print where type=vlan");
### Selecting returned fields ### Selecting returned fields
The same query, but we only want certain result fields names: Print all interfaces of type 'vlan' and return just their name. The same query, but we only want certain result fields names: Print all interfaces of type 'vlan' and return just their name:
```java ```java
List<Map<String, String>> rs = con.execute("/interface/print where type=vlan return name"); List<Map<String, String>> rs = con.execute("/interface/print where type=vlan return name");
@ -167,6 +175,19 @@ And now remove the object:
con.execute("/interface/gre/remove .id=gre1"); con.execute("/interface/gre/remove .id=gre1");
``` ```
### Un-setting a variable on an object
Un-setting a variable is a bit different, and you need to use a parameter called `value-name`. This isn't well documented. Let's say you have a firewall rule that was set up like this:
```java
con.execute("/ip/firewall/filter/add action=accept chain=forward time=00:00:01-01,mon")
```
Assuming the rule can be accessed as `.id=*1`, you un-set it by using `value-name` as seen below:
```java
con.execute("/ip/firewall/filter/unset .id=*1 value-name=time");
```
## Asynchronous commands ## Asynchronous commands
We can run some commands asynchronously in order to continue receiving updates: We can run some commands asynchronously in order to continue receiving updates:
@ -204,8 +225,6 @@ The above command will run and send results asynchronously as they become availa
con.cancel(tag); con.cancel(tag);
``` ```
From version 2.0.0 of the API the error() and completed() methods are part of the ResultListener interface.
## Command timeouts ## Command timeouts
Command timeouts can be used to make sure that synchronous commands either return or fail within a specific time. Command timeouts are separate from the connection timeout used in ```connect()```, and can be set using ```setTimeout()```. Here is an example: Command timeouts can be used to make sure that synchronous commands either return or fail within a specific time. Command timeouts are separate from the connection timeout used in ```connect()```, and can be set using ```setTimeout()```. Here is an example:
@ -227,5 +246,5 @@ The RouterOS API is documented here: http://wiki.mikrotik.com/wiki/Manual:API
# Licence # Licence
This library is released under the Apache 2.0 licence. See the [Licence.md](Licence.md) file This library is released under the Apache 2.0 licence. See the [LICENCE.md](LICENCE.md) file

333
pom.xml
View File

@ -1,173 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<groupId>org.sonatype.oss</groupId> <groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId> <artifactId>oss-parent</artifactId>
<version>9</version> <version>9</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion>
<modelVersion>4.0.0</modelVersion> <groupId>me.legrange</groupId>
<artifactId>mikrotik</artifactId>
<groupId>me.legrange</groupId> <version>3.0.8.1</version>
<artifactId>mikrotik</artifactId> <packaging>jar</packaging>
<version>3.0.2</version> <name>Mikrotik API Java Client Library</name>
<packaging>jar</packaging> <url>https://github.com/GideonLeGrange/mikrotik-java</url>
<licenses>
<name>Mikrotik API Java Client Library</name> <license>
<url>https://github.com/GideonLeGrange/mikrotik-java</url> <name>The Apache Software License, Version 2.0</name>
<licenses> <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<license> <distribution>repo</distribution>
<name>The Apache Software License, Version 2.0</name> </license>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> </licenses>
<distribution>repo</distribution> <scm>
</license> <url>git@github.com:GideonLeGrange/mikrotik-java.git</url>
</licenses> <connection>scm:git:git@github.com:GideonLeGrange/mikrotik-java.git</connection>
<scm> <developerConnection>scm:git:git@github.com:GideonLeGrange/mikrotik-java.git</developerConnection>
<url>git@github.com:GideonLeGrange/mikrotik-java.git</url> </scm>
<connection>scm:git:git@github.com:GideonLeGrange/mikrotik-java.git</connection> <build>
<developerConnection>scm:git:git@github.com:GideonLeGrange/mikrotik-java.git</developerConnection> <plugins>
</scm> <plugin>
<build> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<showDeprecation>true</showDeprecation>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.2</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<excludes>
<exclude>**/examples</exclude>
<exclude>**/examples/*</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<excludePackageNames>example;*.impl</excludePackageNames>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>2.3.2</version> <version>2.4</version>
<configuration> <executions>
<showDeprecation>true</showDeprecation> <execution>
<source>1.7</source> <id>attach-sources</id>
<target>1.7</target> <goals>
</configuration> <goal>jar</goal>
</plugin> </goals>
</execution>
<plugin> </executions>
<groupId>org.apache.maven.plugins</groupId> </plugin>
<artifactId>maven-jar-plugin</artifactId> <plugin>
<version>2.3.2</version> <groupId>org.apache.maven.plugins</groupId>
<executions> <artifactId>maven-javadoc-plugin</artifactId>
<execution> <version>2.10.1</version>
<id>default-jar</id> <executions>
<phase>package</phase> <execution>
<goals> <id>attach-javadocs</id>
<goal>jar</goal> <goals>
</goals> <goal>jar</goal>
<configuration> </goals>
<excludes> </execution>
<exclude>**/examples</exclude> </executions>
<exclude>**/examples/*</exclude> </plugin>
</excludes> <plugin>
</configuration> <groupId>org.apache.maven.plugins</groupId>
</execution> <artifactId>maven-gpg-plugin</artifactId>
</executions> <version>1.6</version>
</plugin> <executions>
<plugin> <execution>
<groupId>org.apache.maven.plugins</groupId> <id>sign-artifacts</id>
<artifactId>maven-source-plugin</artifactId> <phase>verify</phase>
<version>2.2.1</version> <goals>
<executions> <goal>sign</goal>
<execution> </goals>
<id>attach-sources</id> </execution>
<goals> </executions>
<goal>jar</goal> </plugin>
</goals> <plugin>
</execution> <groupId>org.sonatype.plugins</groupId>
</executions> <artifactId>nexus-staging-maven-plugin</artifactId>
</plugin> <version>1.6.5</version>
<plugin> <extensions>true</extensions>
<groupId>org.apache.maven.plugins</groupId> <configuration>
<artifactId>maven-javadoc-plugin</artifactId> <serverId>ossrh</serverId>
<version>2.9.1</version> <nexusUrl>https://oss.sonatype.org/</nexusUrl>
<configuration> <autoReleaseAfterClose>true</autoReleaseAfterClose>
<excludePackageNames>example;*.impl</excludePackageNames> </configuration>
</configuration> </plugin>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins> </plugins>
</build> </build>
<profiles> <distributionManagement>
<profile> <snapshotRepository>
<id>release</id> <id>ossrh</id>
<build> <url>https://oss.sonatype.org/content/repositories/snapshots</url>
<plugins> </snapshotRepository>
<plugin> </distributionManagement>
<groupId>org.apache.maven.plugins</groupId> </profile>
<artifactId>maven-source-plugin</artifactId> </profiles>
<version>2.4</version> <properties>
<executions> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<execution> </properties>
<id>attach-sources</id> <dependencies>
<goals> <dependency>
<goal>jar</goal> <groupId>junit</groupId>
</goals> <artifactId>junit</artifactId>
</execution> <version>4.13.1</version>
</executions> <scope>test</scope>
</plugin> </dependency>
<plugin> </dependencies>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.5</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
</profile>
</profiles>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project> </project>

View File

@ -0,0 +1,34 @@
package examples;
import java.util.List;
import java.util.Map;
import me.legrange.mikrotik.MikrotikApiException;
/**
* Example to show that different character sets may work some times.
*
* @author gideon
*/
public class CharacterSets extends Example {
private static final String JAPANESE = "事報ハヤ久送とゅ歳用ト候新放すルドう二5春園ユヲロ然納レ部悲と被状クヘ芸一ーあぽだ野健い産隊ず";
private static final String CRYLLIC = "Лорем ипсум долор сит амет, легере елояуентиам хис ид. Елигенди нолуиссе вих ут. Нихил";
private static final String ARABIC = "تجهيز والمانيا تم قام. وحتّى المتاخمة ما وقد. أسر أمدها تكبّد عل. فقد بسبب ترتيب استدعى أم, مما مع غرّة، لأداء. الشتاء، عسكرياً";
public static void main(String... args) throws Exception {
CharacterSets ex = new CharacterSets();
ex.connect();
ex.test();
ex.disconnect();
}
private void test() throws MikrotikApiException {
con.execute("/ip/hotspot/user/add name=userJ comment='" + JAPANESE + "'");
con.execute("/ip/hotspot/user/add name=userC comment='" + CRYLLIC + "'");
con.execute("/ip/hotspot/user/add name=userA comment='" + ARABIC + "'");
for (Map<String, String> res : con.execute("/ip/hotspot/user/print return name,comment")) {
System.out.printf("%s : %s\n", res.get("name"), res.get("comment"));
}
}
}

View File

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

View File

@ -23,7 +23,7 @@ public class DownloadConfig extends Example {
List<Map<String, String>> res = con.execute("/file/print detail where name=conf.rsc"); List<Map<String, String>> res = con.execute("/file/print detail where name=conf.rsc");
con.execute("/file/remove .id=conf.rsc"); con.execute("/file/remove .id=conf.rsc");
String text = res.get(0).get("contents"); String text = res.get(0).get("contents");
for (String line : text.split("\r")) { for (String line : text.split("\r")) {
System.out.println(line); System.out.println(line);
} }
} }

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

@ -6,10 +6,20 @@ package me.legrange.mikrotik;
*/ */
public class ApiConnectionException extends MikrotikApiException { public class ApiConnectionException extends MikrotikApiException {
/**
* Create a new exception.
*
* @param msg The message
*/
public ApiConnectionException(String msg) { public ApiConnectionException(String msg) {
super(msg); super(msg);
} }
/**
* Create a new exception
* @param msg The message
* @param err The underlying cause
*/
public ApiConnectionException(String msg, Throwable err) { public ApiConnectionException(String msg, Throwable err) {
super(msg, err); super(msg, err);
} }

View File

@ -7,10 +7,21 @@ package me.legrange.mikrotik;
*/ */
public class MikrotikApiException extends Exception { public class MikrotikApiException extends Exception {
/**
* Create a new exception
* @param msg The message
*/
public MikrotikApiException(String msg) { public MikrotikApiException(String msg) {
super(msg); super(msg);
} }
/**
* Create a new exception
* @param msg The message
* @param err The underlying cause
*/
public MikrotikApiException(String msg, Throwable err) { public MikrotikApiException(String msg, Throwable err) {
super(msg, err); super(msg, err);
} }

View File

@ -1,12 +1,13 @@
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 expr = expr "and" expr | expr "or" expr | "not" expr | hasExpr | eqExpr | lessExpr | moreExpr | notExpr | nestedExpr
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,31 +4,40 @@ 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. 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() { 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,6 +14,7 @@ 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;
@ -31,9 +32,9 @@ public final class ApiConnectionImpl extends ApiConnection {
/** /**
* Create a new API connection to the give device on the supplied port * Create a new API connection to the give device on the supplied port
* *
* @param fact The socket factory used to construct the connection socket.
* @param host The host to which to connect. * @param host The host to which to connect.
* @param port The TCP port to use. * @param port The TCP port to use.
* @param secure Is TLS required
* @param timeOut The connection timeout * @param timeOut The connection timeout
* @return The ApiConnection * @return The ApiConnection
* @throws me.legrange.mikrotik.ApiConnectionException Thrown if there is a * @throws me.legrange.mikrotik.ApiConnectionException Thrown if there is a
@ -55,12 +56,19 @@ public final class ApiConnectionImpl extends ApiConnection {
if (username.trim().isEmpty()) { if (username.trim().isEmpty()) {
throw new ApiConnectionException("API username cannot be empty"); throw new ApiConnectionException("API username cannot be empty");
} }
List<Map<String, String>> list = execute("/login"); Command cmd = new Command("/login");
Map<String, String> res = list.get(0); cmd.addParameter("name", username);
String hash = res.get("ret"); cmd.addParameter("password", password);
String chal = Util.hexStrToStr("00") + new String(password.toCharArray()) + Util.hexStrToStr(hash); List<Map<String, String>> list = execute(cmd, timeout);
chal = Util.hashMD5(chal); if (!list.isEmpty()) {
execute("/login name=" + username + " response=00" + chal); Map<String, String> res = list.get(0);
if (res.containsKey("ret")) {
String hash = res.get("ret");
String chal = Util.hexStrToStr("00") + new String(password.toCharArray()) + Util.hexStrToStr(hash);
chal = Util.hashMD5(chal);
execute("/login name=" + username + " response=00" + chal);
}
}
} }
@Override @Override
@ -155,8 +163,7 @@ public final class ApiConnectionImpl extends ApiConnection {
} }
private synchronized String nextTag() { private synchronized String nextTag() {
_tag++; return Integer.toHexString(_tag.incrementAndGet());
return Integer.toHexString(_tag);
} }
private Socket sock = null; private Socket sock = null;
@ -166,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 Integer _tag = 0; private final AtomicInteger _tag = new AtomicInteger(0);
private int timeout = ApiConnection.DEFAULT_COMMAND_TIMEOUT; private int timeout = ApiConnection.DEFAULT_COMMAND_TIMEOUT;
/** /**
@ -174,8 +181,12 @@ public final class ApiConnectionImpl extends ApiConnection {
*/ */
private class Reader extends Thread { private class Reader extends Thread {
private Reader() {
super("Mikrotik API Reader");
}
private String take() throws ApiConnectionException, ApiDataException { private String take() throws ApiConnectionException, ApiDataException {
Object val = null; Object val;
try { try {
val = queue.take(); val = queue.take();
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
@ -198,9 +209,7 @@ public final class ApiConnectionImpl extends ApiConnection {
while (connected) { while (connected) {
try { try {
String s = Util.decode(in); String s = Util.decode(in);
if (s != null) { put(s);
put(s);
}
} catch (ApiDataException ex) { } catch (ApiDataException ex) {
put(ex); put(ex);
} catch (ApiConnectionException ex) { } catch (ApiConnectionException ex) {
@ -214,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 ex) { } catch (InterruptedException ignored) {
} }
} }
@ -226,6 +235,10 @@ public final class ApiConnectionImpl extends ApiConnection {
*/ */
private class Processor extends Thread { private class Processor extends Thread {
private Processor() {
super("Mikrotik API Result Processor");
}
@Override @Override
public void run() { public void run() {
while (connected) { while (connected) {
@ -235,27 +248,31 @@ 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()); res = new Error(tag, ex.getMessage(), ex.getCategory());
} else { } else {
continue; continue;
} }
} catch (MikrotikApiException ex) { } catch (MikrotikApiException ex) {
continue; continue;
} }
ResultListener l = listeners.get(res.getTag()); if (res.getTag() != null) {
if (l != null) { ResultListener l = listeners.get(res.getTag());
if (res instanceof Result) { if (l != null) {
l.receive((Result) res); if (res instanceof Result) {
} else if (res instanceof Done) { l.receive((Result) res);
if (l instanceof SyncListener) { } else if (res instanceof Done) {
((SyncListener) l).completed((Done) res); if (l instanceof SyncListener) {
} else { ((SyncListener) l).completed((Done) res);
l.completed(); } 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();
} }
} }
} }
@ -263,7 +280,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);
@ -276,7 +293,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);
@ -289,10 +306,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 "":
@ -303,12 +320,10 @@ 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]));
@ -325,7 +340,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]);
} }
@ -366,12 +381,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 {
@ -395,13 +410,16 @@ 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 {
@ -413,35 +431,22 @@ public final class ApiConnectionImpl extends ApiConnection {
return err; return err;
} }
private void queue(Response res) {
String tag = res.getTag();
if (tag != null) {
ResultListener rl = listeners.get(tag);
if (rl != null) {
if (res instanceof Result) {
rl.receive((Result) res);
} else {
// rl.error((Error)res);
}
}
}
}
private final List<String> lines = new LinkedList<>(); private final List<String> lines = new LinkedList<>();
private String line; private String line;
} }
private class SyncListener implements ResultListener { private static class SyncListener implements ResultListener {
@Override @Override
public synchronized void error(MikrotikApiException ex) { public synchronized void error(MikrotikApiException ex) {
this.err = ex; this.err = ex;
notify(); notifyAll();
} }
@Override @Override
public synchronized void completed() { public synchronized void completed() {
complete = true; complete = true;
notify(); notifyAll();
} }
synchronized void completed(Done done) { synchronized void completed(Done done) {
@ -451,7 +456,7 @@ public final class ApiConnectionImpl extends ApiConnection {
results.add(res); results.add(res);
} }
complete = true; complete = true;
notify(); notifyAll();
} }
@Override @Override

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). * 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 {
Error(String tag, String message) { private String message;
private int category;
Error(String tag, String message, int category) {
super(tag); super(tag);
this.message = message; this.message = message;
} }
@ -18,11 +22,16 @@ 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,21 +3,27 @@ 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)) {
@ -67,8 +73,7 @@ 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));
} }
} }
@ -79,7 +84,7 @@ class Parser {
} }
private void expr() throws ParseException { private void expr() throws ParseException {
expect(Token.NOT, Token.TEXT); expect(Token.NOT, Token.TEXT, Token.LEFT_BRACKET);
switch (token) { switch (token) {
case NOT: case NOT:
notExpr(); notExpr();
@ -92,7 +97,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:
@ -106,23 +111,36 @@ 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 : andExpr(); case AND:
andExpr();
break; break;
case OR : orExpr(); case OR:
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("?#|");
@ -135,7 +153,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));
@ -175,22 +193,24 @@ class Parser {
} }
next(); next();
} }
cmd.addProperty(props.toArray(new String[]{})); 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) {

View File

@ -15,6 +15,7 @@
*/ */
package me.legrange.mikrotik.impl; package me.legrange.mikrotik.impl;
import java.util.Locale;
import static me.legrange.mikrotik.impl.Scanner.Token.*; import static me.legrange.mikrotik.impl.Scanner.Token.*;
/** /**
* A simple scanner. * A simple scanner.
@ -27,6 +28,7 @@ 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
@ -79,6 +81,12 @@ 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 '"':
@ -113,11 +121,11 @@ 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();
} }
String val = text.toString().toLowerCase(); String val = text.toString().toLowerCase(Locale.getDefault());
switch (val) { switch (val) {
case "where": case "where":
return WHERE; return WHERE;
@ -184,17 +192,6 @@ class Scanner {
} }
} }
/**
* look ahead one character
*/
private char peek() {
if (pos < line.length()) {
return line.charAt(pos);
} else {
return '\n';
}
}
/** /**
* check if the character matches the give expression * check if the character matches the give expression
*/ */

View File

@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
@ -71,7 +72,7 @@ final class Util {
} }
buf[i] = (byte) (c & 0xFF); buf[i] = (byte) (c & 0xFF);
} }
String res = new String(buf); String res = new String(buf, Charset.forName("UTF-8"));
if (result.length() > 0) { if (result.length() > 0) {
result.append("\n"); result.append("\n");
} }
@ -121,11 +122,11 @@ final class Util {
* @return - converted string. * @return - converted string.
*/ */
static String hexStrToStr(String s) { static String hexStrToStr(String s) {
String ret = ""; StringBuilder ret = new StringBuilder();
for (int i = 0; i < s.length(); i += 2) { for (int i = 0; i < s.length(); i += 2) {
ret += (char) Integer.parseInt(s.substring(i, i + 2), 16); ret.append((char) Integer.parseInt(s.substring(i, i + 2), 16));
} }
return ret; return ret.toString();
} }
/** /**
@ -133,7 +134,7 @@ final class Util {
* stream. * stream.
*/ */
private static void encode(String word, OutputStream out) throws UnsupportedEncodingException, IOException { private static void encode(String word, OutputStream out) throws UnsupportedEncodingException, IOException {
byte bytes[] = word.getBytes("US-ASCII"); byte bytes[] = word.getBytes("UTF-8");
int len = bytes.length; int len = bytes.length;
if (len < 0x80) { if (len < 0x80) {
out.write(len); out.write(len);

View File

@ -1,31 +0,0 @@
package me.legrange.mikrotik;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
public class ApiConnectionTest extends TestCase {
private static final String HOSTNAME = "192.168.88.1";
private static final String ADMIN = "admin";
private static final String PASSWORD = "plasma";
public void testConnectTLSStringInt() throws MikrotikApiException, InterruptedException {
/* ApiConnection connect = ApiConnection.connectTLS(HOSTNAME, 8729);
connect.login(ADMIN, PASSWORD);
List<Map<String, String>> result = connect.execute("/system/resource/print");
assertEquals(1, result.size());
assertEquals("MikroTik", result.get(0).get("platform")); */
}
public void testConnectStringInt() throws MikrotikApiException, InterruptedException {
/* ApiConnection connect = ApiConnection.connect(HOSTNAME, 8728);
connect.login(ADMIN, PASSWORD);
List<Map<String, String>> result = connect.execute("/system/resource/print");
assertEquals(1, result.size());
assertEquals("MikroTik", result.get(0).get("platform")); */
}
}