diff options
-rw-r--r-- | src/org/traccar/BaseProtocolDecoder.java | 27 | ||||
-rw-r--r-- | src/org/traccar/ExtendedObjectDecoder.java | 5 | ||||
-rw-r--r-- | src/org/traccar/web/WebServer.java | 2 | ||||
-rw-r--r-- | swagger.json | 1042 | ||||
-rw-r--r-- | test/org/traccar/protocol/UproProtocolDecoderTest.java | 3 | ||||
-rwxr-xr-x | tools/swagger2html.py | 354 |
6 files changed, 751 insertions, 682 deletions
diff --git a/src/org/traccar/BaseProtocolDecoder.java b/src/org/traccar/BaseProtocolDecoder.java index ea0905bf2..8748a9be6 100644 --- a/src/org/traccar/BaseProtocolDecoder.java +++ b/src/org/traccar/BaseProtocolDecoder.java @@ -23,6 +23,7 @@ import org.traccar.model.Position; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -165,13 +166,31 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { } @Override - protected void onMessageEvent(Channel channel, SocketAddress remoteAddress, Object msg) { + protected void onMessageEvent( + Channel channel, SocketAddress remoteAddress, Object originalMessage, Object decodedMessage) { if (Context.getStatisticsManager() != null) { Context.getStatisticsManager().registerMessageReceived(); } - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); - if (deviceSession != null) { - Context.getConnectionManager().updateDevice(deviceSession.getDeviceId(), Device.STATUS_ONLINE, new Date()); + Position position = null; + if (decodedMessage != null) { + if (decodedMessage instanceof Position) { + position = (Position) decodedMessage; + } else if (decodedMessage instanceof Collection) { + Collection positions = (Collection) decodedMessage; + if (!positions.isEmpty()) { + position = (Position) positions.iterator().next(); + } + } + } + if (position != null) { + Context.getConnectionManager().updateDevice( + position.getDeviceId(), Device.STATUS_ONLINE, new Date()); + } else { + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession != null) { + Context.getConnectionManager().updateDevice( + deviceSession.getDeviceId(), Device.STATUS_ONLINE, new Date()); + } } } diff --git a/src/org/traccar/ExtendedObjectDecoder.java b/src/org/traccar/ExtendedObjectDecoder.java index ec03afa60..268e6f688 100644 --- a/src/org/traccar/ExtendedObjectDecoder.java +++ b/src/org/traccar/ExtendedObjectDecoder.java @@ -56,7 +56,7 @@ public abstract class ExtendedObjectDecoder implements ChannelUpstreamHandler { MessageEvent e = (MessageEvent) evt; Object originalMessage = e.getMessage(); Object decodedMessage = decode(e.getChannel(), e.getRemoteAddress(), originalMessage); - onMessageEvent(e.getChannel(), e.getRemoteAddress(), originalMessage); // call after decode + onMessageEvent(e.getChannel(), e.getRemoteAddress(), originalMessage, decodedMessage); if (originalMessage == decodedMessage) { ctx.sendUpstream(evt); } else { @@ -77,7 +77,8 @@ public abstract class ExtendedObjectDecoder implements ChannelUpstreamHandler { } } - protected void onMessageEvent(Channel channel, SocketAddress remoteAddress, Object msg) { + protected void onMessageEvent( + Channel channel, SocketAddress remoteAddress, Object originalMessage, Object decodedMessage) { } protected Object handleEmptyMessage(Channel channel, SocketAddress remoteAddress, Object msg) { diff --git a/src/org/traccar/web/WebServer.java b/src/org/traccar/web/WebServer.java index 8201f8d16..4dd37f4cc 100644 --- a/src/org/traccar/web/WebServer.java +++ b/src/org/traccar/web/WebServer.java @@ -102,7 +102,7 @@ public class WebServer { ResourceHandler resourceHandler = new ResourceHandler(); resourceHandler.setResourceBase(config.getString("web.path")); if (config.getBoolean("web.debug")) { - resourceHandler.setWelcomeFiles(new String[] {"debug.html"}); + resourceHandler.setWelcomeFiles(new String[] {"debug.html", "index.html"}); resourceHandler.setMinMemoryMappedContentLength(-1); // avoid locking files on Windows } else { resourceHandler.setWelcomeFiles(new String[] {"release.html", "index.html"}); diff --git a/swagger.json b/swagger.json index 25b76963c..0f298f161 100644 --- a/swagger.json +++ b/swagger.json @@ -1,23 +1,29 @@ { "swagger": "2.0", "info": { - "version": "3.8", + "version": "3.9", "title": "traccar" }, - "host": "traccar.org", + "host": "demo.traccar.org", "basePath": "/api", "schemes": [ "http" ], + "security": [ + { + "basicAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], "paths": { "/commands": { "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Dispatch commands to device", "parameters": [ { "name": "body", @@ -31,70 +37,53 @@ "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Command" } + }, + "400": { + "description": "Could happen when dispatching to a device that is offline, the user doesn't have permission or an incorrect command _type_ for the device" } } } }, "/devices": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of Devices", + "description": "Without any params, returns a list of the user's devices", "parameters": [ { - "name": "all", - "in": "query", - "required": true, - "type": "boolean" + "$ref": "#/parameters/all" }, { - "name": "userId", - "in": "query", - "required": true, - "type": "integer" + "$ref": "#/parameters/userId" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { "$ref": "#/definitions/Device" } } + }, + "400": { + "description": "No permission" } } }, "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Create a Device", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/Device" - } + "$ref": "#/parameters/Device" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Device" } @@ -104,32 +93,18 @@ }, "/devices/{id}": { "put": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Update a Device", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" }, { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/Device" - } + "$ref": "#/parameters/Device" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Device" } @@ -137,42 +112,25 @@ } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Update a Device", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/devices/{id}/distance": { "put": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Update the distance counter of the Device", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" }, { "name": "body", @@ -185,34 +143,22 @@ ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/devices/geofences": { "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Link a Geofence to a Device", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/DeviceGeofence" - } + "$ref": "#/parameters/DeviceGeofence" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/DeviceGeofence" } @@ -220,56 +166,34 @@ } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Remove a Geofence from a Device", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/DeviceGeofence" - } + "$ref": "#/parameters/DeviceGeofence" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/groups": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of Groups", + "description": "Without any params, returns a list of the Groups the user belongs to", "parameters": [ { - "name": "all", - "in": "query", - "required": true, - "type": "boolean" + "$ref": "#/parameters/all" }, { - "name": "userId", - "in": "query", - "required": true, - "type": "integer" + "$ref": "#/parameters/userId" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { @@ -280,61 +204,39 @@ } }, "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Create a Group", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/Group" - } + "$ref": "#/parameters/Group" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Group" } + }, + "400": { + "description": "No permission" } } } }, "/groups/{id}": { "put": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Update a Group", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" }, { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/Group" - } + "$ref": "#/parameters/Group" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Group" } @@ -342,50 +244,30 @@ } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Delete a Group", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/groups/geofences": { "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Link a Geofence to a Group", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/GroupGeofence" - } + "$ref": "#/parameters/GroupGeofence" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/GroupGeofence" } @@ -393,105 +275,64 @@ } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Remove a Geofence from a Group", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/GroupGeofence" - } + "$ref": "#/parameters/GroupGeofence" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/permissions/devices": { "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Link a Device to a User", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/DevicePermission" - } + "$ref": "#/parameters/DevicePermission" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/DevicePermission" } + }, + "400": { + "description": "No permission" } } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Remove a Device from a User", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/DevicePermission" - } + "$ref": "#/parameters/DevicePermission" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/permissions/groups": { "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Link a Group to a User", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/GroupPermission" - } + "$ref": "#/parameters/GroupPermission" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/GroupPermission" } @@ -499,52 +340,30 @@ } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Remove a Group from a User", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/GroupPermission" - } + "$ref": "#/parameters/GroupPermission" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/permissions/geofences": { "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Link a Geofence to a User", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/GeofencePermission" - } + "$ref": "#/parameters/GeofencePermission" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/GeofencePermission" } @@ -552,26 +371,15 @@ } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Remove a Geofence from a User", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/GeofencePermission" - } + "$ref": "#/parameters/GeofencePermission" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } @@ -579,13 +387,7 @@ "/positions": { "get": { "summary" : "Fetches a list of Positions", - "description" : "Without any params, it returns a list of last known positions for all the user's devices", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "description" : "Without any params, it returns a list of last known positions for all the user's Devices. _from_ and _to_ fields are not required with _id_", "parameters": [ { "name": "deviceId", @@ -597,17 +399,20 @@ { "name": "from", "in": "query", - "description": "Not required with _id_", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`", "required": false, - "type": "string" + "type": "string", + "format": "date-time" }, { "name": "to", "in": "query", - "description": "Not required with _id_", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`", "required": false, - "type": "string" - }, { + "type": "string", + "format": "date-time" + }, + { "name" : "id", "in" : "query", "description" : "To fetch one or more positions. Multiple params can be passed like `id=31&id=42`", @@ -631,17 +436,10 @@ }, "/server": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "parameters": [], + "summary": "Fetch Server information", "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Server" } @@ -649,12 +447,7 @@ } }, "put": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Update Server information", "parameters": [ { "name": "body", @@ -668,7 +461,6 @@ "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Server" } @@ -678,34 +470,27 @@ }, "/session": { "get": { + "summary": "Fetch Session information", "consumes": [ "application/x-www-form-urlencoded" ], - "produces": [ - "application/json" - ], - "parameters": [], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/User" } }, "404": { - "description": "Not Found", - "headers": {} + "description": "Not Found" } } }, "post": { + "summary": "Create a new Session", "consumes": [ "application/x-www-form-urlencoded" ], - "produces": [ - "application/json" - ], "parameters": [ { "name": "email", @@ -717,82 +502,63 @@ "name": "password", "in": "formData", "required": true, - "type": "string" + "type": "string", + "format": "password" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/User" } }, "401": { - "description": "Unauthorized", - "headers": {} + "description": "Unauthorized" } } }, "delete": { + "summary": "Close the Session", "consumes": [ "application/x-www-form-urlencoded" ], - "produces": [ - "application/json" - ], "parameters": [], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/users": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "parameters": [], + "summary": "Fetch a list of Users", "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { "$ref": "#/definitions/User" } } + }, + "400": { + "description": "No Permission" } } }, "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Create a User", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/User" - } + "$ref": "#/parameters/User" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/User" } @@ -802,32 +568,18 @@ }, "/users/{id}": { "put": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Update a User", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" }, { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/User" - } + "$ref": "#/parameters/User" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/User" } @@ -835,54 +587,37 @@ } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Delete a User", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/users/notifications": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of Notification types", + "description": "Without params, it returns a list of the user's enabled Notifications", "parameters": [ { "name": "all", "in": "query", - "required": true, + "description": "To fetch a list of all available Notifications", "type": "boolean" }, { - "name": "userId", - "in": "query", - "required": true, - "type": "integer" + "$ref": "#/parameters/userId" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { @@ -893,12 +628,7 @@ } }, "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Set or unset a Notification", "parameters": [ { "name": "body", @@ -912,7 +642,6 @@ "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Notification" } @@ -922,12 +651,7 @@ }, "/commandtypes": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of available Commands for the Device", "parameters": [ { "name": "deviceId", @@ -939,49 +663,37 @@ "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { "$ref": "#/definitions/CommandType" } } + }, + "400": { + "description": "Could happen when trying to fetch from an offline device or the user does not have permission" } } } }, "/geofences": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of Geofences", + "description": "Without params, it returns a list of Geofences the user has access to", "parameters": [ { - "name": "all", - "in": "query", - "required": true, - "type": "boolean" + "$ref": "#/parameters/all" }, { - "name": "userId", - "in": "query", - "required": true, - "type": "integer" + "$ref": "#/parameters/userId" }, { "name": "groupId", "in": "query", - "required": true, "type": "integer" }, { - "name": "deviceId", - "in": "query", - "required": true, - "type": "integer" + "$ref": "#/parameters/deviceId" }, { "name": "refresh", @@ -993,7 +705,6 @@ "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { @@ -1004,26 +715,15 @@ } }, "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Create a Geofence", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/Geofence" - } + "$ref": "#/parameters/Geofence" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Geofence" } @@ -1033,32 +733,18 @@ }, "/geofences/{id}": { "put": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Update a Geofence", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" }, { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/Geofence" - } + "$ref": "#/parameters/Geofence" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Geofence" } @@ -1066,48 +752,29 @@ } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Delete a Geofence", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } }, "/events/{id}": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/Event" } @@ -1117,50 +784,25 @@ }, "/reports/route": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of Positions within the time period for the Devices or Groups", + "description": "At least one _deviceId_ or one _groupId_ must be passed", "parameters": [ { - "name": "deviceId", - "in": "query", - "description": "at least one deviceId or one groupId must be passed", - "required": true, - "type": "array", - "items": { - "type": "integer" - } + "$ref": "#/parameters/deviceIdArray" }, { - "name": "groupId", - "in": "query", - "description": "at least one deviceId or one groupId must be passed", - "required": true, - "type": "array", - "items": { - "type": "integer" - } + "$ref": "#/parameters/groupIdArray" }, { - "name": "from", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/fromTime" }, { - "name": "to", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/toTime" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { @@ -1173,60 +815,34 @@ }, "/reports/events": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of Events within the time period for the Devices or Groups", + "description": "At least one _deviceId_ or one _groupId_ must be passed", "parameters": [ { - "name": "deviceId", - "in": "query", - "description": "at least one deviceId or one groupId must be passed", - "required": true, - "type": "array", - "items": { - "type": "integer" - } + "$ref": "#/parameters/deviceIdArray" }, { - "name": "groupId", - "in": "query", - "description": "at least one deviceId or one groupId must be passed", - "required": true, - "type": "array", - "items": { - "type": "integer" - } + "$ref": "#/parameters/groupIdArray" }, { "name": "type", "in": "query", "description": "% can be used to return events of all types", - "required": true, "type": "array", "items": { "type": "string" } }, { - "name": "from", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/fromTime" }, { - "name": "to", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/toTime" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { @@ -1239,50 +855,25 @@ }, "/reports/summary": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of ReportSummary within the time period for the Devices or Groups", + "description": "At least one _deviceId_ or one _groupId_ must be passed", "parameters": [ { - "name": "deviceId", - "in": "query", - "description": "at least one deviceId or one groupId must be passed", - "required": true, - "type": "array", - "items": { - "type": "integer" - } + "$ref": "#/parameters/deviceIdArray" }, { - "name": "groupId", - "in": "query", - "description": "at least one deviceId or one groupId must be passed", - "required": true, - "type": "array", - "items": { - "type": "integer" - } + "$ref": "#/parameters/groupIdArray" }, { - "name": "from", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/fromTime" }, { - "name": "to", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/toTime" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { @@ -1295,50 +886,25 @@ }, "/reports/trips": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of ReportTrips within the time period for the Devices or Groups", + "description": "At least one _deviceId_ or one _groupId_ must be passed", "parameters": [ { - "name": "deviceId", - "in": "query", - "description": "at least one deviceId or one groupId must be passed", - "required": true, - "type": "array", - "items": { - "type": "integer" - } + "$ref": "#/parameters/deviceIdArray" }, { - "name": "groupId", - "in": "query", - "description": "at least one deviceId or one groupId must be passed", - "required": true, - "type": "array", - "items": { - "type": "integer" - } + "$ref": "#/parameters/groupIdArray" }, { - "name": "from", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/fromTime" }, { - "name": "to", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/toTime" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { @@ -1351,30 +917,18 @@ }, "/statistics": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch server Statistics", "parameters": [ { - "name": "from", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/fromTime" }, { - "name": "to", - "in": "query", - "required": true, - "type": "string" + "$ref": "#/parameters/toTime" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { @@ -1387,24 +941,16 @@ }, "/attributes/aliases": { "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Fetch a list of AttributeAlias", + "description": "Without params, it returns a list of AttributeAlias from all the user's Devices", "parameters": [ { - "name": "deviceId", - "in": "query", - "required": false, - "type": "integer" + "$ref": "#/parameters/deviceId" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "type": "array", "items": { @@ -1415,26 +961,15 @@ } }, "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Set an AttributeAlias", "parameters": [ { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/AttributeAlias" - } + "$ref": "#/parameters/AttributeAlias" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/AttributeAlias" } @@ -1444,32 +979,18 @@ }, "/attributes/aliases/{id}": { "put": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Update an AttributeAlias", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" }, { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/AttributeAlias" - } + "$ref": "#/parameters/AttributeAlias" } ], "responses": { "200": { "description": "OK", - "headers": {}, "schema": { "$ref": "#/definitions/AttributeAlias" } @@ -1477,24 +998,15 @@ } }, "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], + "summary": "Delete an AttributeAlias", "parameters": [ { - "name": "id", - "in": "path", - "required": true, - "type": "integer" + "$ref": "#/parameters/entityId" } ], "responses": { "204": { - "description": "No Content", - "headers": {} + "description": "No Content" } } } @@ -1513,10 +1025,19 @@ "type": "string" }, "deviceTime": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`" }, "fixTime": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`" + }, + "serverTime": { + "type": "string", + "format": "date-time", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`" }, "outdated": { "type": "boolean" @@ -1534,7 +1055,8 @@ "type": "number" }, "speed": { - "type": "number" + "type": "number", + "description": "in knots" }, "course": { "type": "number" @@ -1593,7 +1115,9 @@ "type": "boolean" }, "expirationTime": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`" }, "deviceLimit": { "type": "integer" @@ -1680,7 +1204,9 @@ "type": "string" }, "lastUpdate": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`" }, "positionId": { "type": "integer" @@ -1700,7 +1226,12 @@ "category": { "type": "string" }, - "geofenceIds": {}, + "geofenceIds": { + "type": "array", + "items": { + "type": "integer" + } + }, "attributes": {} } }, @@ -1815,7 +1346,9 @@ "type": "string" }, "serverTime": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`" }, "deviceId": { "type": "integer" @@ -1838,13 +1371,16 @@ "type": "string" }, "maxSpeed": { - "type": "number" + "type": "number", + "description": "in knots" }, "averageSpeed": { - "type": "number" + "type": "number", + "description": "in knots" }, "distance": { - "type": "number" + "type": "number", + "description": "in meters" }, "engineHours": { "type": "integer" @@ -1860,19 +1396,24 @@ "type": "string" }, "maxSpeed": { - "type": "number" + "type": "number", + "description": "in knots" }, "averageSpeed": { - "type": "number" + "type": "number", + "description": "in knots" }, "distance": { - "type": "number" + "type": "number", + "description": "in meters" }, "duration": { "type": "integer" }, "startTime": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`" }, "startAddress": { "type": "string" @@ -1884,7 +1425,9 @@ "type": "number" }, "endTime": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`" }, "endAddress": { "type": "string" @@ -1900,7 +1443,9 @@ "Statistics": { "properties": { "captureTime": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`" }, "activeUsers": { "type": "integer" @@ -1941,9 +1486,156 @@ "type": "integer" }, "totalDistance": { - "type": "number" + "type": "number", + "description": "in meters" } } } + }, + "parameters": { + "entityId": { + "name": "id", + "in": "path", + "required": true, + "type": "integer" + }, + "all": { + "name": "all", + "in": "query", + "description": "Can only be used by admin users to fetch all entities", + "type": "boolean" + }, + "userId": { + "name": "userId", + "in": "query", + "description": "Standard users can use this only with their own _userId_", + "type": "integer" + }, + "deviceId": { + "name": "deviceId", + "in": "query", + "description": "Standard users can use this only with _userId_s, they have access to", + "type": "integer" + }, + "Device": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Device" + } + }, + "DeviceGeofence": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/DeviceGeofence" + } + }, + "Group": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Group" + } + }, + "GroupGeofence": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/GroupGeofence" + } + }, + "DevicePermission": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/DevicePermission" + } + }, + "GroupPermission": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/GroupPermission" + } + }, + "GeofencePermission": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/GeofencePermission" + } + }, + "User": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + }, + "Geofence": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Geofence" + } + }, + "AttributeAlias": { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/AttributeAlias" + } + }, + "deviceIdArray": { + "name": "deviceId", + "in": "query", + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "multi" + }, + "groupIdArray": { + "name": "groupId", + "in": "query", + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "multi" + }, + "fromTime": { + "name": "from", + "in": "query", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`", + "required": true, + "type": "string", + "format": "date-time" + }, + "toTime": { + "name": "to", + "in": "query", + "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`", + "required": true, + "type": "string", + "format": "date-time" + } + }, + "securityDefinitions": { + "basicAuth": { + "type": "basic", + "description": "Basic HTTP authorization with _email_ and _password_" + } } } diff --git a/test/org/traccar/protocol/UproProtocolDecoderTest.java b/test/org/traccar/protocol/UproProtocolDecoderTest.java index 3af62da08..270caeab5 100644 --- a/test/org/traccar/protocol/UproProtocolDecoderTest.java +++ b/test/org/traccar/protocol/UproProtocolDecoderTest.java @@ -11,6 +11,9 @@ public class UproProtocolDecoderTest extends ProtocolTest { UproProtocolDecoder decoder = new UproProtocolDecoder(new UproProtocol()); verifyPosition(decoder, binary( + "2a4d473230313836383530303032303030343836372c414226413035303032343138313438373536303636303131373732323030303031313132313626583331302c3236302c34383837322c353639312c37333b34383837322c3732322c38363b34383837322c353639332c38383b34383837322c323336332c39303b34383837322c323336322c393726423030303030303030303026573030264e3230265a31342659313430303323")); + + verifyPosition(decoder, binary( "2a4d473230303639333530323030303033353537332c42412641303834313237333332363334353230373033383933373630303030303235313131362642303130303030303030302647303036323030264d393930264e3235264f3035303026433030313a363b363926510411058c0c125c0d0a2fff4237ee614d66454c140826555f50000000000300000000000000000026543139333723")); verifyPosition(decoder, buffer( diff --git a/tools/swagger2html.py b/tools/swagger2html.py new file mode 100755 index 000000000..a3488835c --- /dev/null +++ b/tools/swagger2html.py @@ -0,0 +1,354 @@ +#!/usr/bin/python + +import sys, argparse, json, re + +def handleException(etype, e=None): + if etype == 'KeyError': + print "Error: Required property {} not found".format(e) + elif etype == 'IOError': + print "Error ({}): {}".format(e.errno, e.strerror) + elif etype == 'ValueError': + print "Error: Unable to parse input as JSON" + elif etype == 'Custom': + print e + sys.exit(1) + +def get_json(filename): + try: + with open(filename) as json_file: + json_data = json.load(json_file) + return json_data + except IOError as e: + handleException('IOError', e) + except ValueError: + handleException('ValueError') + except: + print "Unexpected error: {}".format(sys.exc_info()[0]) + raise + +def write_file(filename, body): + try: + with open(filename, 'w') as md_file: + md_file.write(body) + except IOError as e: + handleException('IOError', e) + +def make_header(json_data): + try: + if not 'swagger' in json_data: + raise KeyError + info = json_data['info'] + md = "<h1>{}</h1>\n".format(info['title']) + md += "<p>Version: {}</p>\n".format(info['version']) + if 'license' in info: + md += "<p>License: " + license = info['license'] + if 'url' in license: + md += '<a href="{}">{}</a>'.format(license['url'], license['name']) + else: + md += license['name'] + md += '</p>\n' + if 'contact' in info: + contact = info['contact'] + if 'name' in contact or 'email' in contact: + md += '<p>Contact: ' + if not 'name' in contact: + md += '<a href="mailto:{0}">{0}</a>'.format(contact['email']) + elif not 'email' in contact: + md += contact['name'] + else: + md += '{0} <<a href="mailto:{1}">{1}</a>'.format(contact['name'], contact['email']) + md += ' \n' + if 'url' in contact: + md += "<p>Website: {}</p>\n".format(contact['url']) + if 'termsOfService' in info: + md += '<p>Terms of Service: {}</p>\n'.format(info['termsOfService']) + if 'host' in json_data: + md += '<p>Base URL: ' + base = json_data['host'] + if 'basePath' in json_data: + base += json_data['basePath'] + else: + base += '/' + if 'schemes' in json_data: + md += (', ').join(map( + lambda x: '<a href="{0}://{1}">{0}://{1}</a>'.format(x, base), + json_data['schemes'] + )) + else: + md += '<a href="{0}">{0}</a>'.format(base) + md += '</p>\n' + if 'description' in info: + md += '<p>Description: {}</p>\n'.format(info['description']) + md += '\n' + return md + except KeyErrori as e: + handleException('KeyError', e) + +def make_ref(ref): + href = ref.split('/')[1:] + return '<a href="#{}">{}</a>'.format('_'.join(href), href[-1]) + +def get_ref(ref, raw): + keys = ref.split('/') + return raw[keys[1]][keys[2]] + +def make_html(s): + reg = re.compile(r"[*_]{3}(.+?)[*_]{3}") + s = reg.sub(r"<strong><em>\1</em></strong>", s) + reg = re.compile(r"[*_]{2}(.+?)[*_]{2}") + s = reg.sub(r"<strong>\1</strong>", s) + reg = re.compile(r"[*_](.+?)[*_]") + s = reg.sub(r"<em>\1</em>", s) + reg = re.compile(r"\`(.+?)\`") + s = reg.sub(r"<code>\1</code>", s) + return s + +def make_table(data): + md = '<table class="table-bordered">\n' + md += ' <thead>\n' + md += ' <tr>\n' + for col in data[0]: + md += ' <td>{}</td>\n'.format(col) + md += ' </tr>\n' + md += ' </thead>\n' + md += ' <tbody>\n' + for row in data[1:]: + md += ' <tr>\n' + for cell in row: + md += ' <td>{}</td>\n'.format(cell) + md += ' </tr>\n' + md += ' </tbody>\n' + md += '</table>\n' + return md + +def make_params_table(itemsraw, raw): + items = [] + for item in itemsraw: + if '$ref' in item: + items.append(get_ref(item['$ref'], raw)) + else: + items.append(item) + try: + fields = list(set([]).union(*map(lambda x: x.keys(), items))) + row = [ 'Name', 'ParamType' ] + if 'description' in fields: + row.append('Description') + if 'required' in fields: + row.append('Required') + if 'type' in fields: + row.append('DataType') + if 'schema' in fields: + row.append('Schema') + table = [ row ] + for item in items: + row = [ "<em>{}</em>".format(item['name']), item['in'] ] + if 'description' in fields: + if 'description' in item: + row.append(make_html(item['description'])) + else: + row.append('') + if 'required' in fields: + required = 'False' + if 'required' in item and item['required']: + required = "<strong>True</strong>" + row.append(required) + if 'type' in fields: + type = '' + if 'type' in item: + if item['type'] == 'array': + type = "[ <em>{}</em> ]".format(item['items']['type']) + else: + type = item['type'] + if 'format' in item: + type += " ({})".format(item['format']) + type = "<em>{}</em>".format(type) + row.append(type) + if 'schema' in fields: + if 'schema' in item: + if '$ref' in item['schema']: + row.append(make_ref(item['schema']['$ref'])) + else: + row.append('') + table.append(row) + return make_table(table) + except KeyError as e: + handleException('KeyError', e) + +def make_responses_table(responses): + try: + fields = list( + set([]).union(*map(lambda x: x.keys(), + map(lambda x: responses[x], responses.keys()) + )) + ) + row = [ 'Status Code', 'Description' ] + if 'headers' in fields: + row.append('Headers') + if 'schema' in fields: + row.append('Schema') + if 'examples' in fields: + row.append('Examples') + table = [ row ] + for key in sorted(responses): + response = responses[key] + row = [ "<em>{}</em>".format(key), make_html(response['description']) ] + if 'headers' in fields: + header = '' + if 'headers' in response: + hrow = [] + for header, h_obj in response['headers'].iteritems(): + hrow += "{} ({})".format(header, h_obj['type']) + if 'description' in h_obj: + hrow += ": {}".format(h_obj['description']) + header = ' \n'.join(hrow) + row.append(header) + if 'schema' in fields: + schema = '' + if 'schema' in response: + if '$ref' in response['schema']: + schema += make_ref(response['schema']['$ref']) + if 'type' in response['schema']: + if response['schema']['type'] == 'array': + if '$ref' in response['schema']['items']: + schema += make_ref(response['schema']['items']['$ref']) + schema = "[ {} ]".format(schema) + row.append(schema) + if 'examples' in fields: + if 'examples' in response: + row.append(response['examples']) + else: + row.append('') + table.append(row) + return make_table(table) + except KeyError as e: + handleException('KeyError', e) + +def make_paths(sections, json_data): + md = '<h2><a name="paths"></a>Paths</h2>\n' + for key in sorted(sections): + md += '<h3><a name="paths_{0}"></a>{0}</h3>\n'.format(key) + for section in sections[key]: + md += '<h4><a name="{}"></a><code>{}</code></h4>\n'.format( + section['href'], section['title'] + ) + operation = section['operation'] + if 'summary' in operation: + md += '<p>Summary: {}</p>\n'.format(make_html(operation['summary'])) + if 'description' in operation: + md += '<p>Description: {}</p>\n'.format(make_html(operation['description'])) + md += '<h5>Parameters</h5>\n' + if 'parameters' in operation and len(operation['parameters']) > 0: + md += make_params_table(operation['parameters'], json_data) + else: + md += "<p><em>None</em></p>\n" + md += '<h5>Responses</h5>\n' + md += make_responses_table(operation['responses']) + md += '\n' + md += '\n' + return md + +def make_contents(path_section, json_data): + md = '<h3>Contents</h3>\n' + md += '<ul>\n' + md += ' <li><a href="#paths">Paths</a>\n' + md += ' <ul>\n' + for key in sorted(path_section): + md += ' <li><a href="#paths_{0}">{0}</a>\n'.format(key) + md += ' <ul>\n' + for section in path_section[key]: + md += ' <li><a href="#{}">{}</a></li>\n'.format( + section['href'], section['title'] + ) + md += ' </ul>\n' + md += ' </li>\n' + md += ' </ul>\n' + md += ' </li>\n' + md += ' <li><a href="#definitions">Models</a>\n' + md += ' <ul>\n' + for key in sorted(json_data['definitions']): + md += ' <li><a href="#definitions_{0}">{0}</a></li>\n'.format(key) + md += ' </ul>\n' + md += ' </li>\n' + md += '</ul>\n' + return md + +def make_definitions(json_data): + md = '<h2><a name="definitions"></a>Models</h2>\n' + for name in sorted(json_data['definitions']): + md += '<h3><a name="definitions_{0}"></a>{0}</h3>\n'.format(name) + model = json_data['definitions'][name] + if 'properties' in model: + fields = list( + set(['type']).union( + *map(lambda x: x.keys(), + map(lambda x: model['properties'][x], model['properties'].keys()) + ) + ) + ) + row = [ 'Property', 'Type' ] + if 'description' in fields: + row.append('Description') + table = [ row ] + for key in sorted(model['properties']): + property = model['properties'][key] + row = [ "<em>{}</em>".format(key) ] + if 'type' in property: + type = property['type'] + if 'format' in property: + type += " ({})".format(property['format']) + row.append("<em>{}</em>".format(type)) + elif '$ref' in property: + row.append(make_ref(property['$ref'])) + else: + row.append('') + if 'description' in fields: + if 'description' in property: + row.append(make_html(property['description'])) + else: + row.append('') + table.append(row) + md += make_table(table) + return md + +def make_markdown(json_data): + path_sections = {} + for endpoint in json_data['paths']: + path_split = endpoint.split('/') + path_key = path_split[1] + if not path_key in path_sections: + path_sections[path_key] = [] + for method, operation in json_data['paths'][endpoint].iteritems(): + if 'operationId' in operation: + link = operation['operationId'] + else: + link = ''.join([ + c for c in endpoint if c not in ['/', '{', '}'] + ]) + path_sections[path_key].append({ + 'title': '{} {}'.format(method.upper(), endpoint), + 'href': 'paths_{}_{}'.format(link, method.upper()), + 'operation': operation + }) + md = make_header(json_data) + md += make_contents(path_sections, json_data) + md += make_paths(path_sections, json_data) + md += make_definitions(json_data) + return md + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('SPECFILE', help="path to swagger.json file") + parser.add_argument('OUTFILE', help="path to output HTML file") + args = parser.parse_args() + + marked_down = make_markdown(get_json(args.SPECFILE)) + + if args.OUTFILE: + write_file(args.OUTFILE, marked_down) + print " success: {}".format(args.OUTFILE) + else: + print marked_down + +if __name__ == '__main__': + main() |