aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--schema/changelog-3.12.xml54
-rw-r--r--setup/default.xml60
-rw-r--r--src/org/traccar/BasePipelineFactory.java13
-rw-r--r--src/org/traccar/Context.java9
-rw-r--r--src/org/traccar/api/resource/AttributePermissionResource.java58
-rw-r--r--src/org/traccar/api/resource/AttributeResource.java111
-rw-r--r--src/org/traccar/api/resource/DeviceAttributeResource.java58
-rw-r--r--src/org/traccar/api/resource/GroupAttributeResource.java58
-rw-r--r--src/org/traccar/database/AttributesManager.java199
-rw-r--r--src/org/traccar/database/DataManager.java84
-rw-r--r--src/org/traccar/database/PermissionsManager.java12
-rw-r--r--src/org/traccar/model/Attribute.java71
-rw-r--r--src/org/traccar/model/AttributePermission.java41
-rw-r--r--src/org/traccar/model/DeviceAttribute.java40
-rw-r--r--src/org/traccar/model/GroupAttribute.java40
-rw-r--r--src/org/traccar/processing/ComputedAttributesHandler.java78
-rw-r--r--src/org/traccar/processing/CopyAttributesHandler.java (renamed from src/org/traccar/CopyAttributesHandler.java)8
-rw-r--r--test/org/traccar/processing/ComputedAttributesTest.java37
18 files changed, 1027 insertions, 4 deletions
diff --git a/schema/changelog-3.12.xml b/schema/changelog-3.12.xml
index 43c0e33f0..137273b49 100644
--- a/schema/changelog-3.12.xml
+++ b/schema/changelog-3.12.xml
@@ -22,6 +22,60 @@
<constraints nullable="false" />
</column>
</addColumn>
+
+ <createTable tableName="attributes">
+ <column name="id" type="INT" autoIncrement="true">
+ <constraints primaryKey="true" />
+ </column>
+ <column name="description" type="VARCHAR(4000)">
+ <constraints nullable="false" />
+ </column>
+ <column name="type" type="VARCHAR(128)">
+ <constraints nullable="false" />
+ </column>
+ <column name="attribute" type="VARCHAR(128)">
+ <constraints nullable="false" />
+ </column>
+ <column name="expression" type="VARCHAR(4000)">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+
+ <createTable tableName="user_attribute">
+ <column name="userid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ <column name="attributeid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+
+ <addForeignKeyConstraint baseTableName="user_attribute" baseColumnNames="userid" constraintName="fk_user_attribute_userid" referencedTableName="users" referencedColumnNames="id" onDelete="CASCADE" />
+ <addForeignKeyConstraint baseTableName="user_attribute" baseColumnNames="attributeid" constraintName="fk_user_attribute_attributeid" referencedTableName="attributes" referencedColumnNames="id" onDelete="CASCADE" />
+
+ <createTable tableName="group_attribute">
+ <column name="groupid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ <column name="attributeid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+
+ <addForeignKeyConstraint baseTableName="group_attribute" baseColumnNames="groupid" constraintName="fk_group_attribute_groupid" referencedTableName="groups" referencedColumnNames="id" onDelete="CASCADE" />
+ <addForeignKeyConstraint baseTableName="group_attribute" baseColumnNames="attributeid" constraintName="fk_group_attribute_attributeid" referencedTableName="attributes" referencedColumnNames="id" onDelete="CASCADE" />
+
+ <createTable tableName="device_attribute">
+ <column name="deviceid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ <column name="attributeid" type="INT">
+ <constraints nullable="false" />
+ </column>
+ </createTable>
+
+ <addForeignKeyConstraint baseTableName="device_attribute" baseColumnNames="deviceid" constraintName="fk_device_attribute_deviceid" referencedTableName="devices" referencedColumnNames="id" onDelete="CASCADE" />
+ <addForeignKeyConstraint baseTableName="device_attribute" baseColumnNames="attributeid" constraintName="fk_device_attribute_attributeid" referencedTableName="attributes" referencedColumnNames="id" onDelete="CASCADE" />
</changeSet>
diff --git a/setup/default.xml b/setup/default.xml
index 82f2798dd..0e43b210f 100644
--- a/setup/default.xml
+++ b/setup/default.xml
@@ -18,6 +18,7 @@
<entry key='logger.file'>./logs/tracker-server.log</entry>
<entry key='event.enable'>true</entry>
+ <entry key='processing.computedAttributes.enable'>true</entry>
<!-- DATABASE CONFIG -->
@@ -364,6 +365,65 @@
DELETE FROM user_user WHERE userId = :userId AND managedUserId = :managedUserId
</entry>
+ <entry key='database.selectAttributes'>
+ SELECT * FROM attributes
+ </entry>
+
+ <entry key='database.insertAttribute'>
+ INSERT INTO attributes (description, type, attribute, expression)
+ VALUES (:description, :type, :attribute, :expression)
+ </entry>
+
+ <entry key='database.updateAttribute'>
+ UPDATE attributes SET
+ description = :description,
+ type = :type,
+ attribute = :attribute,
+ expression = :expression
+ WHERE id = :id
+ </entry>
+
+ <entry key='database.deleteAttribute'>
+ DELETE FROM attributes WHERE id = :id
+ </entry>
+
+ <entry key='database.selectAttributePermissions'>
+ SELECT userId, attributeId FROM user_attribute
+ </entry>
+
+ <entry key='database.linkAttribute'>
+ INSERT INTO user_attribute (userId, attributeId) VALUES (:userId, :attributeId)
+ </entry>
+
+ <entry key='database.unlinkAttribute'>
+ DELETE FROM user_attribute WHERE userId = :userId AND attributeId = :attributeId
+ </entry>
+
+ <entry key='database.selectGroupAttributes'>
+ SELECT groupId, attributeId FROM group_attribute
+ </entry>
+
+ <entry key='database.linkGroupAttribute'>
+ INSERT INTO group_attribute (groupId, attributeId) VALUES (:groupId, :attributeId)
+ </entry>
+
+ <entry key='database.unlinkGroupAttribute'>
+ DELETE FROM group_attribute WHERE groupId = :groupId AND attributeId = :attributeId
+ </entry>
+
+ <entry key='database.selectDeviceAttributes'>
+ SELECT deviceId, attributeId FROM device_attribute
+ </entry>
+
+ <entry key='database.linkDeviceAttribute'>
+ INSERT INTO device_attribute (deviceId, attributeId) VALUES (:deviceId, :attributeId)
+ </entry>
+
+ <entry key='database.unlinkDeviceAttribute'>
+ DELETE FROM device_attribute WHERE deviceId = :deviceId AND attributeId = :attributeId
+ </entry>
+
+
<!-- PROTOCOL CONFIG -->
<entry key='gps103.port'>5001</entry>
diff --git a/src/org/traccar/BasePipelineFactory.java b/src/org/traccar/BasePipelineFactory.java
index 620c4729a..11457905d 100644
--- a/src/org/traccar/BasePipelineFactory.java
+++ b/src/org/traccar/BasePipelineFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,8 @@ import org.traccar.events.MotionEventHandler;
import org.traccar.events.OverspeedEventHandler;
import org.traccar.events.AlertEventHandler;
import org.traccar.helper.Log;
+import org.traccar.processing.ComputedAttributesHandler;
+import org.traccar.processing.CopyAttributesHandler;
import java.net.InetSocketAddress;
@@ -53,6 +55,7 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
private GeolocationHandler geolocationHandler;
private HemisphereHandler hemisphereHandler;
private CopyAttributesHandler copyAttributesHandler;
+ private ComputedAttributesHandler computedAttributesHandler;
private CommandResultEventHandler commandResultEventHandler;
private OverspeedEventHandler overspeedEventHandler;
@@ -153,6 +156,10 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
copyAttributesHandler = new CopyAttributesHandler();
}
+ if (Context.getConfig().getBoolean("processing.computedAttributes.enable")) {
+ computedAttributesHandler = new ComputedAttributesHandler();
+ }
+
if (Context.getConfig().getBoolean("event.enable")) {
commandResultEventHandler = new CommandResultEventHandler();
overspeedEventHandler = new OverspeedEventHandler();
@@ -209,6 +216,10 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
pipeline.addLast("copyAttributes", copyAttributesHandler);
}
+ if (computedAttributesHandler != null) {
+ pipeline.addLast("computedAttributes", computedAttributesHandler);
+ }
+
if (Context.getDataManager() != null) {
pipeline.addLast("dataHandler", new DefaultDataHandler());
}
diff --git a/src/org/traccar/Context.java b/src/org/traccar/Context.java
index bfef3807b..07ede4cad 100644
--- a/src/org/traccar/Context.java
+++ b/src/org/traccar/Context.java
@@ -27,6 +27,7 @@ import org.apache.velocity.app.VelocityEngine;
import org.eclipse.jetty.util.URIUtil;
import org.traccar.database.AliasesManager;
import org.traccar.database.CalendarManager;
+import org.traccar.database.AttributesManager;
import org.traccar.database.ConnectionManager;
import org.traccar.database.DataManager;
import org.traccar.database.DeviceManager;
@@ -173,6 +174,12 @@ public final class Context {
return aliasesManager;
}
+ private static AttributesManager attributesManager;
+
+ public static AttributesManager getAttributesManager() {
+ return attributesManager;
+ }
+
private static StatisticsManager statisticsManager;
public static StatisticsManager getStatisticsManager() {
@@ -310,6 +317,8 @@ public final class Context {
aliasesManager = new AliasesManager(dataManager);
+ attributesManager = new AttributesManager(dataManager);
+
statisticsManager = new StatisticsManager();
if (config.getBoolean("sms.smpp.enable")) {
diff --git a/src/org/traccar/api/resource/AttributePermissionResource.java b/src/org/traccar/api/resource/AttributePermissionResource.java
new file mode 100644
index 000000000..1924bcdf1
--- /dev/null
+++ b/src/org/traccar/api/resource/AttributePermissionResource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.api.resource;
+
+import java.sql.SQLException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.model.AttributePermission;
+
+@Path("permissions/attributes")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class AttributePermissionResource extends BaseResource {
+
+ @POST
+ public Response add(AttributePermission entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkUser(getUserId(), entity.getUserId());
+ Context.getPermissionsManager().checkAttribute(getUserId(), entity.getAttributeId());
+ Context.getDataManager().linkAttribute(entity.getUserId(), entity.getAttributeId());
+ Context.getAttributesManager().refreshUserAttributes();
+ return Response.ok(entity).build();
+ }
+
+ @DELETE
+ public Response remove(AttributePermission entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkUser(getUserId(), entity.getUserId());
+ Context.getPermissionsManager().checkAttribute(getUserId(), entity.getAttributeId());
+ Context.getDataManager().unlinkAttribute(entity.getUserId(), entity.getAttributeId());
+ Context.getAttributesManager().refreshUserAttributes();
+ return Response.noContent().build();
+ }
+
+}
diff --git a/src/org/traccar/api/resource/AttributeResource.java b/src/org/traccar/api/resource/AttributeResource.java
new file mode 100644
index 000000000..7751a9360
--- /dev/null
+++ b/src/org/traccar/api/resource/AttributeResource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.api.resource;
+
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.database.AttributesManager;
+import org.traccar.model.Attribute;
+
+@Path("attributes/computed")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class AttributeResource extends BaseResource {
+
+ @GET
+ public Collection<Attribute> get(
+ @QueryParam("all") boolean all, @QueryParam("userId") long userId, @QueryParam("groupId") long groupId,
+ @QueryParam("deviceId") long deviceId, @QueryParam("refresh") boolean refresh) throws SQLException {
+
+ AttributesManager attributesManager = Context.getAttributesManager();
+ if (refresh) {
+ attributesManager.refreshAttributes();
+ }
+
+ Set<Long> result = new HashSet<>();
+ if (all) {
+ if (Context.getPermissionsManager().isAdmin(getUserId())) {
+ result.addAll(attributesManager.getAllAttributes());
+ } else {
+ Context.getPermissionsManager().checkManager(getUserId());
+ result.addAll(attributesManager.getManagedAttributes(getUserId()));
+ }
+ } else {
+ if (userId == 0) {
+ userId = getUserId();
+ }
+ Context.getPermissionsManager().checkUser(getUserId(), userId);
+ result.addAll(attributesManager.getUserAttributes(userId));
+ }
+
+ if (groupId != 0) {
+ Context.getPermissionsManager().checkGroup(getUserId(), groupId);
+ result.retainAll(attributesManager.getGroupAttributes(groupId));
+ }
+
+ if (deviceId != 0) {
+ Context.getPermissionsManager().checkDevice(getUserId(), deviceId);
+ result.retainAll(attributesManager.getDeviceAttributes(deviceId));
+ }
+ return attributesManager.getAttributes(result);
+
+ }
+ @POST
+ public Response add(Attribute entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getAttributesManager().addAttribute(entity);
+ Context.getDataManager().linkAttribute(getUserId(), entity.getId());
+ Context.getAttributesManager().refreshUserAttributes();
+ return Response.ok(entity).build();
+ }
+
+ @Path("{id}")
+ @PUT
+ public Response update(Attribute entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkAttribute(getUserId(), entity.getId());
+ Context.getAttributesManager().updateAttribute(entity);
+ return Response.ok(entity).build();
+ }
+
+ @Path("{id}")
+ @DELETE
+ public Response remove(@PathParam("id") long id) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkAttribute(getUserId(), id);
+ Context.getAttributesManager().removeAttribute(id);
+ return Response.noContent().build();
+ }
+
+}
diff --git a/src/org/traccar/api/resource/DeviceAttributeResource.java b/src/org/traccar/api/resource/DeviceAttributeResource.java
new file mode 100644
index 000000000..8d80c9235
--- /dev/null
+++ b/src/org/traccar/api/resource/DeviceAttributeResource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.api.resource;
+
+import java.sql.SQLException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.model.DeviceAttribute;
+
+@Path("devices/attributes")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class DeviceAttributeResource extends BaseResource {
+
+ @POST
+ public Response add(DeviceAttribute entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkDevice(getUserId(), entity.getDeviceId());
+ Context.getPermissionsManager().checkAttribute(getUserId(), entity.getAttributeId());
+ Context.getDataManager().linkDeviceAttribute(entity.getDeviceId(), entity.getAttributeId());
+ Context.getAttributesManager().refresh();
+ return Response.ok(entity).build();
+ }
+
+ @DELETE
+ public Response remove(DeviceAttribute entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkDevice(getUserId(), entity.getDeviceId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), entity.getAttributeId());
+ Context.getDataManager().unlinkDeviceAttribute(entity.getDeviceId(), entity.getAttributeId());
+ Context.getAttributesManager().refresh();
+ return Response.noContent().build();
+ }
+
+}
diff --git a/src/org/traccar/api/resource/GroupAttributeResource.java b/src/org/traccar/api/resource/GroupAttributeResource.java
new file mode 100644
index 000000000..84b876d34
--- /dev/null
+++ b/src/org/traccar/api/resource/GroupAttributeResource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.api.resource;
+
+import java.sql.SQLException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.model.GroupAttribute;
+
+@Path("groups/attributes")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class GroupAttributeResource extends BaseResource {
+
+ @POST
+ public Response add(GroupAttribute entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkGroup(getUserId(), entity.getGroupId());
+ Context.getPermissionsManager().checkAttribute(getUserId(), entity.getAttributeId());
+ Context.getDataManager().linkGroupAttribute(entity.getGroupId(), entity.getAttributeId());
+ Context.getAttributesManager().refresh();
+ return Response.ok(entity).build();
+ }
+
+ @DELETE
+ public Response remove(GroupAttribute entity) throws SQLException {
+ Context.getPermissionsManager().checkReadonly(getUserId());
+ Context.getPermissionsManager().checkGroup(getUserId(), entity.getGroupId());
+ Context.getPermissionsManager().checkGeofence(getUserId(), entity.getAttributeId());
+ Context.getDataManager().unlinkGroupAttribute(entity.getGroupId(), entity.getAttributeId());
+ Context.getAttributesManager().refresh();
+ return Response.noContent().build();
+ }
+
+}
diff --git a/src/org/traccar/database/AttributesManager.java b/src/org/traccar/database/AttributesManager.java
new file mode 100644
index 000000000..362d6130f
--- /dev/null
+++ b/src/org/traccar/database/AttributesManager.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.database;
+
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.traccar.Context;
+import org.traccar.helper.Log;
+import org.traccar.model.AttributePermission;
+import org.traccar.model.Attribute;
+import org.traccar.model.Device;
+import org.traccar.model.DeviceAttribute;
+import org.traccar.model.GroupAttribute;
+
+public class AttributesManager {
+
+ private final DataManager dataManager;
+
+ private final Map<Long, Attribute> attributes = new ConcurrentHashMap<>();
+ private final Map<Long, Set<Long>> deviceAttributes = new ConcurrentHashMap<>();
+ private final Map<Long, Set<Long>> deviceAttributesWithGroups = new ConcurrentHashMap<>();
+ private final Map<Long, Set<Long>> groupAttributes = new ConcurrentHashMap<>();
+ private final Map<Long, Set<Long>> userAttributes = new ConcurrentHashMap<>();
+
+ public AttributesManager(DataManager dataManager) {
+ this.dataManager = dataManager;
+ refreshAttributes();
+ }
+
+ public Set<Long> getUserAttributes(long userId) {
+ if (!userAttributes.containsKey(userId)) {
+ userAttributes.put(userId, new HashSet<Long>());
+ }
+ return userAttributes.get(userId);
+ }
+
+ public Set<Long> getGroupAttributes(long groupId) {
+ if (!groupAttributes.containsKey(groupId)) {
+ groupAttributes.put(groupId, new HashSet<Long>());
+ }
+ return groupAttributes.get(groupId);
+ }
+
+ public Set<Long> getDeviceAttributes(long deviceId) {
+ return getDeviceAttributes(deviceAttributes, deviceId);
+ }
+
+ public Set<Long> getAllDeviceAttributes(long deviceId) {
+ return getDeviceAttributes(deviceAttributesWithGroups, deviceId);
+ }
+
+ private Set<Long> getDeviceAttributes(Map<Long, Set<Long>> deviceAttributes, long deviceId) {
+ if (!deviceAttributes.containsKey(deviceId)) {
+ deviceAttributes.put(deviceId, new HashSet<Long>());
+ }
+ return deviceAttributes.get(deviceId);
+ }
+
+ public final void refreshAttributes() {
+ if (dataManager != null) {
+ try {
+ attributes.clear();
+ for (Attribute attribute : dataManager.getAttributes()) {
+ attributes.put(attribute.getId(), attribute);
+ }
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+ }
+ refreshUserAttributes();
+ refresh();
+ }
+
+ public final void refreshUserAttributes() {
+ if (dataManager != null) {
+ try {
+ userAttributes.clear();
+ for (AttributePermission attributePermission : dataManager.getAttributePermissions()) {
+ getUserAttributes(attributePermission.getUserId()).add(attributePermission.getAttributeId());
+ }
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+ }
+ }
+
+ public final void refresh() {
+ if (dataManager != null) {
+ try {
+
+ Collection<GroupAttribute> databaseGroupAttributes = dataManager.getGroupAttributes();
+
+ groupAttributes.clear();
+ for (GroupAttribute groupAttribute : databaseGroupAttributes) {
+ getGroupAttributes(groupAttribute.getGroupId()).add(groupAttribute.getAttributeId());
+ }
+
+ Collection<DeviceAttribute> databaseDeviceAttributes = dataManager.getDeviceAttributes();
+ Collection<Device> allDevices = Context.getDeviceManager().getAllDevices();
+
+ deviceAttributes.clear();
+ deviceAttributesWithGroups.clear();
+
+ for (DeviceAttribute deviceAttribute : databaseDeviceAttributes) {
+ getDeviceAttributes(deviceAttribute.getDeviceId())
+ .add(deviceAttribute.getAttributeId());
+ getAllDeviceAttributes(deviceAttribute.getDeviceId())
+ .add(deviceAttribute.getAttributeId());
+ }
+
+ for (Device device : allDevices) {
+ long groupId = device.getGroupId();
+ while (groupId != 0) {
+ getAllDeviceAttributes(device.getId()).addAll(getGroupAttributes(groupId));
+ if (Context.getDeviceManager().getGroupById(groupId) != null) {
+ groupId = Context.getDeviceManager().getGroupById(groupId).getGroupId();
+ } else {
+ groupId = 0;
+ }
+ }
+ }
+
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+ }
+ }
+
+ public void addAttribute(Attribute attribute) throws SQLException {
+ dataManager.addAttribute(attribute);
+ attributes.put(attribute.getId(), attribute);
+ }
+
+ public void updateAttribute(Attribute attribute) throws SQLException {
+ dataManager.updateAttribute(attribute);
+ Attribute cachedAttribute = attributes.get(attribute.getId());
+ cachedAttribute.setDescription(attribute.getDescription());
+ cachedAttribute.setAttribute(attribute.getAttribute());
+ cachedAttribute.setExpression(attribute.getExpression());
+ cachedAttribute.setType(attribute.getType());
+ }
+
+ public void removeAttribute(long computedAttributeId) throws SQLException {
+ dataManager.removeAttribute(computedAttributeId);
+ attributes.remove(computedAttributeId);
+ refreshUserAttributes();
+ refresh();
+ }
+
+ public boolean checkAttribute(long userId, long attributeId) {
+ return getUserAttributes(userId).contains(attributeId);
+ }
+
+ public Attribute getAttribute(long id) {
+ return attributes.get(id);
+ }
+
+ public final Collection<Attribute> getAttributes(Set<Long> attributeIds) {
+ Collection<Attribute> result = new LinkedList<>();
+ for (long attributeId : attributeIds) {
+ result.add(getAttribute(attributeId));
+ }
+ return result;
+ }
+
+ public final Set<Long> getAllAttributes() {
+ return attributes.keySet();
+ }
+
+ public final Set<Long> getManagedAttributes(long userId) {
+ Set<Long> attributes = new HashSet<>();
+ attributes.addAll(getUserAttributes(userId));
+ for (long managedUserId : Context.getPermissionsManager().getUserPermissions(userId)) {
+ attributes.addAll(getUserAttributes(managedUserId));
+ }
+ return attributes;
+ }
+
+}
diff --git a/src/org/traccar/database/DataManager.java b/src/org/traccar/database/DataManager.java
index 0c5f458a2..acbaac17b 100644
--- a/src/org/traccar/database/DataManager.java
+++ b/src/org/traccar/database/DataManager.java
@@ -37,13 +37,17 @@ import liquibase.resource.ResourceAccessor;
import org.traccar.Config;
import org.traccar.helper.Log;
import org.traccar.model.AttributeAlias;
+import org.traccar.model.AttributePermission;
import org.traccar.model.Calendar;
import org.traccar.model.CalendarPermission;
+import org.traccar.model.Attribute;
import org.traccar.model.Device;
+import org.traccar.model.DeviceAttribute;
import org.traccar.model.DevicePermission;
import org.traccar.model.Event;
import org.traccar.model.Geofence;
import org.traccar.model.Group;
+import org.traccar.model.GroupAttribute;
import org.traccar.model.GroupGeofence;
import org.traccar.model.GroupPermission;
import org.traccar.model.Notification;
@@ -548,4 +552,84 @@ public class DataManager {
.executeUpdate();
}
+ public Collection<Attribute> getAttributes() throws SQLException {
+ return QueryBuilder.create(dataSource, getQuery("database.selectAttributes"))
+ .executeQuery(Attribute.class);
+ }
+
+ public void addAttribute(Attribute attribute) throws SQLException {
+ attribute.setId(QueryBuilder.create(dataSource, getQuery("database.insertAttribute"), true)
+ .setObject(attribute)
+ .executeUpdate());
+ }
+
+ public void updateAttribute(Attribute attribute) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.updateAttribute"))
+ .setObject(attribute)
+ .executeUpdate();
+ }
+
+ public void removeAttribute(long computedAttributeId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.deleteAttribute"))
+ .setLong("id", computedAttributeId)
+ .executeUpdate();
+ }
+
+ public Collection<AttributePermission> getAttributePermissions() throws SQLException {
+ return QueryBuilder.create(dataSource, getQuery("database.selectAttributePermissions"))
+ .executeQuery(AttributePermission.class);
+ }
+
+ public void linkAttribute(long userId, long attributeId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.linkAttribute"))
+ .setLong("userId", userId)
+ .setLong("attributeId", attributeId)
+ .executeUpdate();
+ }
+
+ public void unlinkAttribute(long userId, long attributeId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.unlinkAttribute"))
+ .setLong("userId", userId)
+ .setLong("attributeId", attributeId)
+ .executeUpdate();
+ }
+
+ public Collection<GroupAttribute> getGroupAttributes() throws SQLException {
+ return QueryBuilder.create(dataSource, getQuery("database.selectGroupAttributes"))
+ .executeQuery(GroupAttribute.class);
+ }
+
+ public void linkGroupAttribute(long groupId, long attributeId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.linkGroupAttribute"))
+ .setLong("groupId", groupId)
+ .setLong("attributeId", attributeId)
+ .executeUpdate();
+ }
+
+ public void unlinkGroupAttribute(long groupId, long attributeId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.unlinkGroupAttribute"))
+ .setLong("groupId", groupId)
+ .setLong("attributeId", attributeId)
+ .executeUpdate();
+ }
+
+ public Collection<DeviceAttribute> getDeviceAttributes() throws SQLException {
+ return QueryBuilder.create(dataSource, getQuery("database.selectDeviceAttributes"))
+ .executeQuery(DeviceAttribute.class);
+ }
+
+ public void linkDeviceAttribute(long deviceId, long attributeId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.linkDeviceAttribute"))
+ .setLong("deviceId", deviceId)
+ .setLong("attributeId", attributeId)
+ .executeUpdate();
+ }
+
+ public void unlinkDeviceAttribute(long deviceId, long attributeId) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.unlinkDeviceAttribute"))
+ .setLong("deviceId", deviceId)
+ .setLong("attributeId", attributeId)
+ .executeUpdate();
+ }
+
}
diff --git a/src/org/traccar/database/PermissionsManager.java b/src/org/traccar/database/PermissionsManager.java
index e4bd6f5db..11f147c7c 100644
--- a/src/org/traccar/database/PermissionsManager.java
+++ b/src/org/traccar/database/PermissionsManager.java
@@ -310,6 +310,18 @@ public class PermissionsManager {
}
}
+ public void checkAttribute(long userId, long attributeId) throws SecurityException {
+ if (!Context.getAttributesManager().checkAttribute(userId, attributeId) && !isAdmin(userId)) {
+ checkManager(userId);
+ for (long managedUserId : getUserPermissions(userId)) {
+ if (Context.getAttributesManager().checkAttribute(managedUserId, attributeId)) {
+ return;
+ }
+ }
+ throw new SecurityException("Attribute access denied");
+ }
+ }
+
public void checkCalendar(long userId, long calendarId) throws SecurityException {
if (!Context.getCalendarManager().checkCalendar(userId, calendarId) && !isAdmin(userId)) {
checkManager(userId);
diff --git a/src/org/traccar/model/Attribute.java b/src/org/traccar/model/Attribute.java
new file mode 100644
index 000000000..9c3b5e43b
--- /dev/null
+++ b/src/org/traccar/model/Attribute.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.model;
+
+public class Attribute {
+
+ private long id;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ private String description;
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ private String attribute;
+
+ public String getAttribute() {
+ return attribute;
+ }
+
+ public void setAttribute(String attribute) {
+ this.attribute = attribute;
+ }
+
+ private String expression;
+
+ public String getExpression() {
+ return expression;
+ }
+
+ public void setExpression(String expression) {
+ this.expression = expression;
+ }
+
+ private String type;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+}
diff --git a/src/org/traccar/model/AttributePermission.java b/src/org/traccar/model/AttributePermission.java
new file mode 100644
index 000000000..fe2fe7b6e
--- /dev/null
+++ b/src/org/traccar/model/AttributePermission.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.model;
+
+public class AttributePermission {
+
+ private long userId;
+
+ public long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(long userId) {
+ this.userId = userId;
+ }
+
+ private long attributeId;
+
+ public long getAttributeId() {
+ return attributeId;
+ }
+
+ public void setAttributeId(long attributeId) {
+ this.attributeId = attributeId;
+ }
+
+}
diff --git a/src/org/traccar/model/DeviceAttribute.java b/src/org/traccar/model/DeviceAttribute.java
new file mode 100644
index 000000000..e0ac6dd98
--- /dev/null
+++ b/src/org/traccar/model/DeviceAttribute.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.model;
+
+public class DeviceAttribute {
+
+ private long deviceId;
+
+ public long getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ private long attributeId;
+
+ public long getAttributeId() {
+ return attributeId;
+ }
+
+ public void setAttributeId(long attributeId) {
+ this.attributeId = attributeId;
+ }
+}
diff --git a/src/org/traccar/model/GroupAttribute.java b/src/org/traccar/model/GroupAttribute.java
new file mode 100644
index 000000000..a7e8a80bc
--- /dev/null
+++ b/src/org/traccar/model/GroupAttribute.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.model;
+
+public class GroupAttribute {
+
+ private long groupId;
+
+ public long getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(long groupId) {
+ this.groupId = groupId;
+ }
+
+ private long attributeId;
+
+ public long getAttributeId() {
+ return attributeId;
+ }
+
+ public void setAttributeId(long attributeId) {
+ this.attributeId = attributeId;
+ }
+}
diff --git a/src/org/traccar/processing/ComputedAttributesHandler.java b/src/org/traccar/processing/ComputedAttributesHandler.java
new file mode 100644
index 000000000..ea7c0aa1d
--- /dev/null
+++ b/src/org/traccar/processing/ComputedAttributesHandler.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.processing;
+
+import java.util.Collection;
+
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.JexlException;
+import org.apache.commons.jexl2.MapContext;
+import org.traccar.BaseDataHandler;
+import org.traccar.Context;
+import org.traccar.helper.Log;
+import org.traccar.model.Attribute;
+import org.traccar.model.Position;
+
+public class ComputedAttributesHandler extends BaseDataHandler {
+
+ private JexlEngine engine;
+
+ public ComputedAttributesHandler() {
+ engine = new JexlEngine();
+ engine.setStrict(true);
+ }
+
+ public Object computeAttribute(Attribute attribute, Position position) throws JexlException {
+ MapContext expressionContext = new MapContext();
+ expressionContext.set("position", position);
+ return engine.createExpression(attribute.getExpression()).evaluate(expressionContext);
+ }
+
+ @Override
+ protected Position handlePosition(Position position) {
+ Collection<Attribute> attributes = Context.getAttributesManager().getAttributes(
+ Context.getAttributesManager().getAllDeviceAttributes(position.getDeviceId()));
+ for (Attribute attribute : attributes) {
+ if (attribute.getAttribute() != null) {
+ Object result = null;
+ try {
+ result = computeAttribute(attribute, position);
+ } catch (JexlException error) {
+ Log.warning(error);
+ }
+ if (result != null) {
+ try {
+ switch (attribute.getType()) {
+ case "number":
+ position.getAttributes().put(attribute.getAttribute(), (Number) result);
+ break;
+ case "boolean":
+ position.getAttributes().put(attribute.getAttribute(), (Boolean) result);
+ break;
+ default:
+ position.getAttributes().put(attribute.getAttribute(), result.toString());
+ }
+ } catch (ClassCastException error) {
+ Log.warning(error);
+ }
+ }
+ }
+ }
+ return position;
+ }
+
+}
diff --git a/src/org/traccar/CopyAttributesHandler.java b/src/org/traccar/processing/CopyAttributesHandler.java
index 18052d0ea..3a96ca98d 100644
--- a/src/org/traccar/CopyAttributesHandler.java
+++ b/src/org/traccar/processing/CopyAttributesHandler.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2016 Anton Tananaev (anton@traccar.org)
- * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar;
+package org.traccar.processing;
+import org.traccar.BaseDataHandler;
+import org.traccar.Context;
import org.traccar.model.Position;
public class CopyAttributesHandler extends BaseDataHandler {
diff --git a/test/org/traccar/processing/ComputedAttributesTest.java b/test/org/traccar/processing/ComputedAttributesTest.java
new file mode 100644
index 000000000..432f1b9ad
--- /dev/null
+++ b/test/org/traccar/processing/ComputedAttributesTest.java
@@ -0,0 +1,37 @@
+package org.traccar.processing;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.traccar.model.Attribute;
+import org.traccar.model.Position;
+
+public class ComputedAttributesTest {
+
+ @Test
+ public void testComputedAttributes() {
+ Position position = new Position();
+ ComputedAttributesHandler computedAttributesHandler = new ComputedAttributesHandler();
+ position.set("adc1", 128);
+ position.set("booleanFlag", true);
+ position.set("adc2", 100);
+ position.set("bitFlag", 7);
+ position.set("event", 42);
+ Attribute attribute = new Attribute();
+
+ attribute.setExpression("position.getInteger(\"adc1\")");
+ Assert.assertEquals(128, computedAttributesHandler.computeAttribute(attribute, position));
+
+ attribute.setExpression("!position.getBoolean(\"booleanFlag\")");
+ Assert.assertEquals(false, computedAttributesHandler.computeAttribute(attribute, position));
+
+ attribute.setExpression("position.getInteger(\"adc2\") * 2 + 50");
+ Assert.assertEquals(250, computedAttributesHandler.computeAttribute(attribute, position));
+
+ attribute.setExpression("(position.getLong(\"bitFlag\") & 4) != 0");
+ Assert.assertEquals(true, computedAttributesHandler.computeAttribute(attribute, position));
+
+ attribute.setExpression("if (position.getLong(\"event\") == 42) \"lowBattery\"");
+ Assert.assertEquals("lowBattery", computedAttributesHandler.computeAttribute(attribute, position));
+ }
+
+}