diff options
author | Anton Tananaev <anton.tananaev@gmail.com> | 2017-05-13 11:01:48 +1200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-13 11:01:48 +1200 |
commit | 713d6c55a007aa80850810f447308976516bfa63 (patch) | |
tree | 057e9147e724a56b6e2f3820fcf3d9ab67561fdf | |
parent | 51920cae6438f8888090f177761a82afff33067f (diff) | |
parent | 6c17f85d04b224ff2a09265918765c9f4fc8cf94 (diff) | |
download | trackermap-server-713d6c55a007aa80850810f447308976516bfa63.tar.gz trackermap-server-713d6c55a007aa80850810f447308976516bfa63.tar.bz2 trackermap-server-713d6c55a007aa80850810f447308976516bfa63.zip |
Merge pull request #3156 from Abyss777/computed_attributes
Implement computed attributes
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 eed2584bd..146cba80b 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 --> @@ -368,6 +369,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 2f2cf1275..840a8931c 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; @@ -552,4 +556,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)); + } + +} |