From 1334c110ff77256529728b117905ac214a58e8c3 Mon Sep 17 00:00:00 2001 From: Mark Milligan Date: Thu, 14 Jan 2021 16:28:24 -0600 Subject: [PATCH] Initial Commit --- .gitignore | 9 + currentmonitor/lantern-currentmonitor/pom.xml | 105 ++ .../currentmonitor/BluetoothConfig.java | 39 + .../currentmonitor/BreakerSamples.java | 49 + .../currentmonitor/CurrentListener.java | 7 + .../currentmonitor/CurrentMonitor.java | 241 ++++ .../currentmonitor/MonitorApp.java | 409 ++++++ .../currentmonitor/MonitorConfig.java | 97 ++ .../currentmonitor/PowerListener.java | 7 + .../currentmonitor/PowerSample.java | 6 + .../bluetooth/AbstractProperties.java | 90 ++ .../bluetooth/BleAdvertisement.java | 69 + .../bluetooth/BleApplication.java | 90 ++ .../bluetooth/BleCharacteristic.java | 121 ++ .../bluetooth/BleCharacteristicListener.java | 6 + .../bluetooth/BleDescriptor.java | 32 + .../currentmonitor/bluetooth/BleHelper.java | 115 ++ .../currentmonitor/bluetooth/BleService.java | 55 + .../dao/MonitorConfigSerializer.java | 56 + .../currentmonitor/led/LEDFlasher.java | 40 + .../currentmonitor/util/NetworkMonitor.java | 53 + .../currentmonitor/wifi/WifiConfig.java | 44 + .../main/java/org/bluez/GattApplication1.java | 6 + ...om.lanternsoftware.util.dao.IDaoSerializer | 1 + .../src/main/resources/logback.xml | 28 + .../currentmonitor/CreateConfig.java | 15 + .../currentmonitor/CreateSSIDKey.java | 9 + .../CurrentMonitorAppSerializers.java | 11 + .../currentmonitor/NetworkTest.java | 10 + .../currentmonitor/ReleaseCurrentMonitor.java | 38 + .../lantern-dataaccess-currentmonitor/pom.xml | 83 ++ .../currentmonitor/CurrentMonitorDao.java | 45 + .../currentmonitor/DirtyMinute.java | 57 + .../MongoCurrentMonitorDao.java | 273 ++++ .../dao/DirtyMinuteSerializer.java | 44 + ...om.lanternsoftware.util.dao.IDaoSerializer | 1 + .../lantern-datamodel-currentmonitor/pom.xml | 73 ++ .../datamodel/currentmonitor/Account.java | 46 + .../datamodel/currentmonitor/AuthCode.java | 49 + .../datamodel/currentmonitor/Breaker.java | 232 ++++ .../currentmonitor/BreakerConfig.java | 162 +++ .../currentmonitor/BreakerGroup.java | 192 +++ .../currentmonitor/BreakerGroupEnergy.java | 337 +++++ .../currentmonitor/BreakerGroupSummary.java | 139 ++ .../datamodel/currentmonitor/BreakerHub.java | 44 + .../currentmonitor/BreakerPanel.java | 52 + .../currentmonitor/BreakerPolarity.java | 6 + .../currentmonitor/BreakerPower.java | 92 ++ .../currentmonitor/BreakerPowerMinute.java | 40 + .../datamodel/currentmonitor/BreakerType.java | 36 + .../currentmonitor/CharacteristicFlag.java | 31 + .../datamodel/currentmonitor/EnergyBlock.java | 60 + .../currentmonitor/EnergyBlockType.java | 7 + .../currentmonitor/EnergyBlockViewMode.java | 144 ++ .../HubConfigCharacteristic.java | 44 + .../currentmonitor/HubConfigService.java | 39 + .../currentmonitor/HubPowerMinute.java | 58 + .../datamodel/currentmonitor/Meter.java | 34 + .../currentmonitor/NetworkAdapter.java | 42 + .../currentmonitor/NetworkStatus.java | 44 + .../datamodel/currentmonitor/Sequence.java | 28 + .../currentmonitor/SignupResponse.java | 45 + .../currentmonitor/UUIDFormatter.java | 17 + .../currentmonitor/dao/AccountSerializer.java | 50 + .../dao/AuthCodeSerializer.java | 46 + .../dao/BreakerConfigSerializer.java | 52 + .../dao/BreakerGroupEnergySerializer.java | 98 ++ .../dao/BreakerGroupSerializer.java | 49 + .../dao/BreakerGroupSummarySerializer.java | 55 + .../dao/BreakerHubSerializer.java | 45 + .../dao/BreakerPanelSerializer.java | 47 + .../dao/BreakerPowerMinuteSerializer.java | 48 + .../dao/BreakerPowerSerializer.java | 54 + .../currentmonitor/dao/BreakerSerializer.java | 63 + .../dao/EnergyBlockSerializer.java | 43 + .../dao/HubPowerMinuteSerializer.java | 46 + .../currentmonitor/dao/MeterSerializer.java | 43 + .../dao/SequenceSerializer.java | 42 + .../dao/SignupResponseSerializer.java | 41 + ...om.lanternsoftware.util.dao.IDaoSerializer | 16 + .../lantern-service-currentmonitor/pom.xml | 89 ++ .../currentmonitor/context/Globals.java | 23 + .../currentmonitor/servlet/AuthServlet.java | 57 + .../currentmonitor/servlet/CMServlet.java | 101 ++ .../servlet/CommandServlet.java | 49 + .../currentmonitor/servlet/ConfigServlet.java | 34 + .../servlet/GroupEnergyServlet.java | 48 + .../servlet/GroupPowerServlet.java | 26 + .../currentmonitor/servlet/PowerServlet.java | 49 + .../currentmonitor/servlet/SecureServlet.java | 35 + .../currentmonitor/servlet/SignupServlet.java | 45 + .../currentmonitor/servlet/UpdateServlet.java | 22 + .../src/main/resources/logback.xml | 17 + .../src/main/webapp/WEB-INF/web.xml | 9 + .../currentmonitor/CreateAccount.java | 25 + .../currentmonitor/CreateAuthCode.java | 15 + .../currentmonitor/CreateAuthKey.java | 11 + .../currentmonitor/CreateBreakers.java | 241 ++++ .../currentmonitor/CreateMongoConfig.java | 10 + .../CurrentMonitorSerializers.java | 13 + .../currentmonitor/MigrateSummaries.java | 55 + .../currentmonitor/RebuildSummaries.java | 99 ++ .../src/test/resources/logback.xml | 17 + currentmonitor/pom.xml | 20 + pom.xml | 19 + util/lantern-util-common/pom.xml | 65 + .../lanternsoftware/util/CollectionUtils.java | 935 +++++++++++++ .../com/lanternsoftware/util/DateUtils.java | 658 ++++++++++ .../com/lanternsoftware/util/DebugTimer.java | 147 +++ .../com/lanternsoftware/util/IAggregator.java | 7 + .../com/lanternsoftware/util/IEditor.java | 5 + .../com/lanternsoftware/util/IEquals.java | 5 + .../com/lanternsoftware/util/IFilter.java | 5 + .../com/lanternsoftware/util/IQualifier.java | 5 + .../com/lanternsoftware/util/ISupplier.java | 5 + .../lanternsoftware/util/ITransformer.java | 5 + .../lanternsoftware/util/LanternFiles.java | 6 + .../com/lanternsoftware/util/MapUtils.java | 23 + .../com/lanternsoftware/util/MemoryStats.java | 84 ++ .../com/lanternsoftware/util/NullUtils.java | 397 ++++++ .../lanternsoftware/util/ResourceLoader.java | 147 +++ .../com/lanternsoftware/util/ZipUtils.java | 51 + .../util/concurrency/ConcurrencyUtils.java | 132 ++ .../util/concurrency/Execution.java | 16 + .../util/concurrency/ExecutionResult.java | 13 + .../util/concurrency/ExecutionUtil.java | 88 ++ .../util/concurrency/ExecutorUtil.java | 13 + .../util/cryptography/AESTool.java | 284 ++++ .../util/cryptography/RSAUtils.java | 220 ++++ .../com/lanternsoftware/util/csv/CSV.java | 102 ++ .../com/lanternsoftware/util/csv/CSVCell.java | 59 + .../lanternsoftware/util/csv/CSVReader.java | 132 ++ .../util/email/EmailValidator.java | 528 ++++++++ .../util/hash/AbstractHashTool.java | 116 ++ .../util/hash/MD5HashTool.java | 27 + .../util/hash/SHA1HashTool.java | 27 + .../util/hash/SHA512HashTool.java | 27 + .../lanternsoftware/util/tracing/ITracer.java | 10 + .../util/tracing/TraceContext.java | 32 + .../util/tracing/TraceDuration.java | 39 + .../util/tracing/TraceFrequencyType.java | 8 + .../util/tracing/TraceLog.java | 21 + .../util/tracing/TraceSpan.java | 5 + .../util/tracing/TraceTags.java | 16 + .../util/tracing/TracerConfig.java | 77 ++ .../com/lanternsoftware/util/xml/XmlNode.java | 99 ++ .../lanternsoftware/util/xml/XmlParser.java | 83 ++ util/lantern-util-dao-ephemeral/pom.xml | 60 + .../util/dao/ephemeral/EphemeralProxy.java | 284 ++++ util/lantern-util-dao-mongo/pom.xml | 65 + .../util/dao/mongo/BsonUtils.java | 108 ++ .../util/dao/mongo/MongoConfig.java | 149 +++ .../util/dao/mongo/MongoProxy.java | 464 +++++++ .../dao/mongo/dao/MongoConfigSerializer.java | 51 + ...om.lanternsoftware.util.dao.IDaoSerializer | 1 + .../src/main/resources/ca.jks | Bin 0 -> 19752 bytes util/lantern-util-dao/pom.xml | 60 + .../util/dao/AbstractDaoProxy.java | 323 +++++ .../util/dao/AbstractDaoSerializer.java | 253 ++++ .../util/dao/AnnotationFinder.java | 79 ++ .../lanternsoftware/util/dao/DaoEntity.java | 100 ++ .../com/lanternsoftware/util/dao/DaoPage.java | 32 + .../util/dao/DaoProxyType.java | 10 + .../lanternsoftware/util/dao/DaoQuery.java | 211 +++ .../util/dao/DaoSerializer.java | 913 +++++++++++++ .../util/dao/DaoSerializerException.java | 15 + .../com/lanternsoftware/util/dao/DaoSort.java | 72 + .../util/dao/DaoSortField.java | 53 + .../util/dao/EntityPreparer.java | 16 + .../lanternsoftware/util/dao/IDaoProxy.java | 66 + .../util/dao/IDaoSerializer.java | 16 + .../util/dao/QueryExecution.java | 38 + .../util/dao/QueryFinalizer.java | 7 + .../util/dao/QueryFinalizerExecution.java | 28 + .../util/dao/QueryOneExecution.java | 37 + .../util/dao/QueryPreparer.java | 50 + .../util/dao/annotations/CaseFormat.java | 7 + .../util/dao/annotations/DBClob.java | 11 + .../util/dao/annotations/DBIgnore.java | 12 + .../util/dao/annotations/DBIndex.java | 14 + .../util/dao/annotations/DBName.java | 12 + .../util/dao/annotations/DBSerializable.java | 16 + .../util/dao/annotations/DBType.java | 12 + .../util/dao/annotations/Important.java | 11 + .../util/dao/annotations/NeverUpdate.java | 11 + .../util/dao/annotations/PrimaryKey.java | 11 + .../util/dao/annotations/StringDates.java | 12 + .../util/dao/annotations/TimestampDates.java | 11 + .../util/dao/annotations/Unimportant.java | 11 + .../util/dao/csv/CSVStream.java | 137 ++ .../dao/generator/DaoSerializerGenerator.java | 379 ++++++ .../util/dao/generator/SchemaGenerator.java | 91 ++ .../generator/SerializerGenerationResult.java | 21 + .../dao/generator/SwiftModelGenerator.java | 200 +++ .../util/dao/jdbc/AbstractJdbcProxy.java | 1166 +++++++++++++++++ .../util/dao/jdbc/DataSourceProxy.java | 79 ++ .../util/dao/jdbc/DatabaseType.java | 8 + .../util/dao/jdbc/JdbcConfig.java | 91 ++ .../util/dao/jdbc/JdbcProxy.java | 62 + .../util/dao/jdbc/OracleTestProxy.java | 7 + .../dao/jdbc/dao/JdbcConfigSerializer.java | 51 + .../jdbc/preparedinstatement/BatchBucket.java | 89 ++ .../jdbc/preparedinstatement/InClause.java | 148 +++ .../InClauseBatchedParameter.java | 90 ++ .../preparedinstatement/InClauseBuilder.java | 148 +++ .../preparedinstatement/InClauseColumn.java | 87 ++ .../InClauseStatement.java | 211 +++ .../PreparedInStatement.java | 507 +++++++ .../preparedparameter/PreparedBoolean.java | 55 + .../jdbc/preparedparameter/PreparedByte.java | 61 + .../jdbc/preparedparameter/PreparedBytes.java | 61 + .../preparedparameter/PreparedDouble.java | 61 + .../jdbc/preparedparameter/PreparedEnum.java | 75 ++ .../jdbc/preparedparameter/PreparedFloat.java | 61 + .../PreparedInBatchedParameter.java | 46 + .../jdbc/preparedparameter/PreparedInt.java | 85 ++ .../jdbc/preparedparameter/PreparedLong.java | 96 ++ .../jdbc/preparedparameter/PreparedNull.java | 53 + .../preparedparameter/PreparedObject.java | 43 + .../preparedparameter/PreparedParameter.java | 22 + .../jdbc/preparedparameter/PreparedShort.java | 61 + .../preparedparameter/PreparedString.java | 101 ++ ...om.lanternsoftware.util.dao.IDaoSerializer | 1 + util/lantern-util-http/pom.xml | 70 + .../lanternsoftware/util/http/HttpPool.java | 107 ++ util/lantern-util-servlet/pom.xml | 66 + .../util/servlet/BasicAuth.java | 48 + .../util/servlet/FreemarkerConfigUtil.java | 36 + .../util/servlet/FreemarkerServlet.java | 125 ++ .../util/servlet/FreemarkerUtil.java | 28 + .../util/servlet/SchemaUtils.java | 193 +++ util/pom.xml | 25 + zwave/lantern-datamodel-zwave/pom.xml | 73 ++ .../datamodel/zwave/Switch.java | 146 +++ .../datamodel/zwave/SwitchSchedule.java | 118 ++ .../datamodel/zwave/SwitchTransition.java | 27 + .../datamodel/zwave/ThermostatMode.java | 24 + .../datamodel/zwave/ZWaveConfig.java | 29 + .../zwave/dao/SwitchScheduleSerializer.java | 46 + .../datamodel/zwave/dao/SwitchSerializer.java | 62 + .../zwave/dao/ZWaveConfigSerializer.java | 43 + ...om.lanternsoftware.util.dao.IDaoSerializer | 3 + zwave/lantern-service-thermometer/pom.xml | 79 ++ .../thermometer/TestThermo.java | 16 + .../thermometer/context/Globals.java | 22 + .../thermometer/context/ThermometerApp.java | 131 ++ .../thermometer/servlet/TempServlet.java | 18 + .../thermometer/servlet/ThermoServlet.java | 45 + .../src/main/resources/logback.xml | 28 + .../src/main/webapp/WEB-INF/web.xml | 9 + .../thermometer/TestStartup.java | 16 + zwave/lantern-service-zwave/pom.xml | 94 ++ .../zwave/context/Globals.java | 30 + .../zwave/context/ZWaveApp.java | 334 +++++ .../zwave/context/ZWaveSpring.java | 230 ++++ .../zwave/dao/MongoZWaveDao.java | 29 + .../lanternsoftware/zwave/dao/ZWaveDao.java | 8 + .../zwave/servlet/ConfigServlet.java | 18 + .../zwave/servlet/SecureServlet.java | 35 + .../zwave/servlet/SwitchServlet.java | 53 + .../zwave/servlet/TemperatureServlet.java | 22 + .../zwave/servlet/ZWaveServlet.java | 87 ++ .../src/main/resources/logback.xml | 17 + .../main/resources/mitsubishi_ir_codes.csv | 59 + .../src/main/webapp/WEB-INF/web.xml | 9 + .../lanternsoftware/zwave/CreateConfig.java | 74 ++ .../zwave/GenerateSerializers.java | 11 + .../lanternsoftware/zwave/TestStartup.java | 16 + zwave/lantern-uirt/pom.xml | 78 ++ .../com/lanternsoftware/uirt/UsbUirt.java | 94 ++ .../uirt/model/UIRTConfig.java | 51 + .../lanternsoftware/uirt/model/UIRTEvent.java | 26 + zwave/lantern-zwave/pom.xml | 65 + .../zwave/controller/Controller.java | 237 ++++ .../zwave/message/CommandClass.java | 119 ++ .../zwave/message/ControllerMessageType.java | 92 ++ .../zwave/message/IMessageSubscriber.java | 6 + .../zwave/message/Message.java | 126 ++ .../zwave/message/MessageEngine.java | 63 + .../zwave/message/MessageType.java | 12 + .../zwave/message/MessageUtil.java | 39 + .../message/NoCommandRequestMessage.java | 11 + .../message/NoCommandResponseMessage.java | 7 + .../zwave/message/RequestMessage.java | 11 + .../zwave/message/ResponseMessage.java | 11 + .../zwave/message/SendDataRequestMessage.java | 11 + .../impl/ApplicationUpdateRequest.java | 42 + .../message/impl/BinarySwitchSetRequest.java | 39 + .../zwave/message/impl/ByteMessage.java | 30 + .../impl/ControllerCapabilitiesRequest.java | 10 + .../impl/ControllerCapabilitiesResponse.java | 75 ++ .../impl/ControllerInitialDataRequest.java | 10 + .../impl/ControllerInitialDataResponse.java | 50 + .../impl/DeviceManufacturerActionRequest.java | 15 + .../message/impl/GetControllerIdRequest.java | 10 + .../message/impl/GetControllerIdResponse.java | 39 + .../impl/MultilevelSensorGetRequest.java | 20 + .../impl/MultilevelSensorReportRequest.java | 34 + .../impl/MultilevelSwitchReportRequest.java | 28 + .../impl/MultilevelSwitchSetRequest.java | 31 + .../zwave/message/impl/NodeInfoRequest.java | 14 + .../zwave/message/impl/NodeInfoResponse.java | 10 + .../zwave/message/impl/SendDataRequest.java | 60 + .../zwave/message/impl/SendDataResponse.java | 33 + .../impl/ThermostatModeGetRequest.java | 19 + .../impl/ThermostatModeSetRequest.java | 28 + .../impl/ThermostatSetPointGetRequest.java | 28 + .../impl/ThermostatSetPointReportRequest.java | 41 + .../impl/ThermostatSetPointSetRequest.java | 34 + ...ThermostatSetPointSupportedGetRequest.java | 19 + ...rmostatSetPointSupportedReportRequest.java | 46 + .../message/thermostat/ThermostatMode.java | 23 + .../thermostat/ThermostatSetPointIndex.java | 48 + .../com/lanternsoftware/zwave/node/Node.java | 34 + .../zwave/node/NodeManager.java | 108 ++ .../com.lanternsoftware.zwave.message.Message | 24 + .../com/lanternsoftware/zwave/TestBits.java | 8 + zwave/pom.xml | 21 + 318 files changed, 24160 insertions(+) create mode 100644 .gitignore create mode 100644 currentmonitor/lantern-currentmonitor/pom.xml create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BreakerSamples.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentListener.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentMonitor.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerListener.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerSample.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/AbstractProperties.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleAdvertisement.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleApplication.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristic.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristicListener.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleDescriptor.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleHelper.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleService.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/led/LEDFlasher.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/util/NetworkMonitor.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/wifi/WifiConfig.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/java/org/bluez/GattApplication1.java create mode 100644 currentmonitor/lantern-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer create mode 100644 currentmonitor/lantern-currentmonitor/src/main/resources/logback.xml create mode 100644 currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateConfig.java create mode 100644 currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateSSIDKey.java create mode 100644 currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorAppSerializers.java create mode 100644 currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/NetworkTest.java create mode 100644 currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/ReleaseCurrentMonitor.java create mode 100644 currentmonitor/lantern-dataaccess-currentmonitor/pom.xml create mode 100644 currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java create mode 100644 currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/DirtyMinute.java create mode 100644 currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java create mode 100644 currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/dao/DirtyMinuteSerializer.java create mode 100644 currentmonitor/lantern-dataaccess-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/pom.xml create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/AuthCode.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Breaker.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerConfig.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroup.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupSummary.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerHub.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPanel.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPolarity.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPower.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPowerMinute.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerType.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/CharacteristicFlag.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlock.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockType.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockViewMode.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigCharacteristic.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigService.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubPowerMinute.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Meter.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkAdapter.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkStatus.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Sequence.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/SignupResponse.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/UUIDFormatter.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AuthCodeSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerConfigSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSummarySerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerHubSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPanelSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerMinuteSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/EnergyBlockSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/HubPowerMinuteSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/MeterSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SequenceSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SignupResponseSerializer.java create mode 100644 currentmonitor/lantern-datamodel-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer create mode 100644 currentmonitor/lantern-service-currentmonitor/pom.xml create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/context/Globals.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CMServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CommandServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/ConfigServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupEnergyServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupPowerServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/PowerServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SecureServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/UpdateServlet.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/resources/logback.xml create mode 100644 currentmonitor/lantern-service-currentmonitor/src/main/webapp/WEB-INF/web.xml create mode 100644 currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAccount.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthCode.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthKey.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateBreakers.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateMongoConfig.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorSerializers.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/MigrateSummaries.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java create mode 100644 currentmonitor/lantern-service-currentmonitor/src/test/resources/logback.xml create mode 100644 currentmonitor/pom.xml create mode 100644 pom.xml create mode 100644 util/lantern-util-common/pom.xml create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/CollectionUtils.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/DateUtils.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/DebugTimer.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/IAggregator.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/IEditor.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/IEquals.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/IFilter.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/IQualifier.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/ISupplier.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/ITransformer.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/LanternFiles.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/MapUtils.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/MemoryStats.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/NullUtils.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/ResourceLoader.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/ZipUtils.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ConcurrencyUtils.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/Execution.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutionResult.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutionUtil.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutorUtil.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/cryptography/AESTool.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/cryptography/RSAUtils.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSV.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSVCell.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSVReader.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/email/EmailValidator.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/AbstractHashTool.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/MD5HashTool.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/SHA1HashTool.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/SHA512HashTool.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/ITracer.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceContext.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceDuration.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceFrequencyType.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceLog.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceSpan.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceTags.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TracerConfig.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/xml/XmlNode.java create mode 100644 util/lantern-util-common/src/main/java/com/lanternsoftware/util/xml/XmlParser.java create mode 100644 util/lantern-util-dao-ephemeral/pom.xml create mode 100644 util/lantern-util-dao-ephemeral/src/main/java/com/lanternsoftware/util/dao/ephemeral/EphemeralProxy.java create mode 100644 util/lantern-util-dao-mongo/pom.xml create mode 100644 util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/BsonUtils.java create mode 100644 util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/MongoConfig.java create mode 100644 util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/MongoProxy.java create mode 100644 util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/dao/MongoConfigSerializer.java create mode 100644 util/lantern-util-dao-mongo/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer create mode 100644 util/lantern-util-dao-mongo/src/main/resources/ca.jks create mode 100644 util/lantern-util-dao/pom.xml create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AbstractDaoProxy.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AbstractDaoSerializer.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AnnotationFinder.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoEntity.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoPage.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoProxyType.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoQuery.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSerializer.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSerializerException.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSort.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSortField.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/EntityPreparer.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/IDaoProxy.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/IDaoSerializer.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryExecution.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryFinalizer.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryFinalizerExecution.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryOneExecution.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryPreparer.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/CaseFormat.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBClob.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBIgnore.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBIndex.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBName.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBSerializable.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBType.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/Important.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/NeverUpdate.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/PrimaryKey.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/StringDates.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/TimestampDates.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/Unimportant.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/csv/CSVStream.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/DaoSerializerGenerator.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SchemaGenerator.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SerializerGenerationResult.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SwiftModelGenerator.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/AbstractJdbcProxy.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/DataSourceProxy.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/DatabaseType.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/JdbcConfig.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/JdbcProxy.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/OracleTestProxy.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/dao/JdbcConfigSerializer.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/BatchBucket.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClause.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseBatchedParameter.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseBuilder.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseColumn.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseStatement.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/PreparedInStatement.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedBoolean.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedByte.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedBytes.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedDouble.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedEnum.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedFloat.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedInBatchedParameter.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedInt.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedLong.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedNull.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedObject.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedParameter.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedShort.java create mode 100644 util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedString.java create mode 100644 util/lantern-util-dao/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer create mode 100644 util/lantern-util-http/pom.xml create mode 100644 util/lantern-util-http/src/main/java/com/lanternsoftware/util/http/HttpPool.java create mode 100644 util/lantern-util-servlet/pom.xml create mode 100644 util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/BasicAuth.java create mode 100644 util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerConfigUtil.java create mode 100644 util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerServlet.java create mode 100644 util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerUtil.java create mode 100644 util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/SchemaUtils.java create mode 100644 util/pom.xml create mode 100644 zwave/lantern-datamodel-zwave/pom.xml create mode 100644 zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/Switch.java create mode 100644 zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/SwitchSchedule.java create mode 100644 zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/SwitchTransition.java create mode 100644 zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/ThermostatMode.java create mode 100644 zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/ZWaveConfig.java create mode 100644 zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/SwitchScheduleSerializer.java create mode 100644 zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/SwitchSerializer.java create mode 100644 zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/ZWaveConfigSerializer.java create mode 100644 zwave/lantern-datamodel-zwave/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer create mode 100644 zwave/lantern-service-thermometer/pom.xml create mode 100644 zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/TestThermo.java create mode 100644 zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/context/Globals.java create mode 100644 zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/context/ThermometerApp.java create mode 100644 zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/servlet/TempServlet.java create mode 100644 zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/servlet/ThermoServlet.java create mode 100644 zwave/lantern-service-thermometer/src/main/resources/logback.xml create mode 100644 zwave/lantern-service-thermometer/src/main/webapp/WEB-INF/web.xml create mode 100644 zwave/lantern-service-thermometer/src/test/java/com/lanternsoftware/thermometer/TestStartup.java create mode 100644 zwave/lantern-service-zwave/pom.xml create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/Globals.java create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveApp.java create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveSpring.java create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/dao/MongoZWaveDao.java create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/dao/ZWaveDao.java create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/ConfigServlet.java create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/SecureServlet.java create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/SwitchServlet.java create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/TemperatureServlet.java create mode 100644 zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/ZWaveServlet.java create mode 100644 zwave/lantern-service-zwave/src/main/resources/logback.xml create mode 100644 zwave/lantern-service-zwave/src/main/resources/mitsubishi_ir_codes.csv create mode 100644 zwave/lantern-service-zwave/src/main/webapp/WEB-INF/web.xml create mode 100644 zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/CreateConfig.java create mode 100644 zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/GenerateSerializers.java create mode 100644 zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/TestStartup.java create mode 100644 zwave/lantern-uirt/pom.xml create mode 100644 zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/UsbUirt.java create mode 100644 zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/model/UIRTConfig.java create mode 100644 zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/model/UIRTEvent.java create mode 100644 zwave/lantern-zwave/pom.xml create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/controller/Controller.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/CommandClass.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/ControllerMessageType.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/IMessageSubscriber.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/Message.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageEngine.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageType.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageUtil.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/NoCommandRequestMessage.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/NoCommandResponseMessage.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/RequestMessage.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/ResponseMessage.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/SendDataRequestMessage.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ApplicationUpdateRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/BinarySwitchSetRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ByteMessage.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerCapabilitiesRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerCapabilitiesResponse.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerInitialDataRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerInitialDataResponse.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/DeviceManufacturerActionRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/GetControllerIdRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/GetControllerIdResponse.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSensorGetRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSensorReportRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSwitchReportRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSwitchSetRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/NodeInfoRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/NodeInfoResponse.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/SendDataRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/SendDataResponse.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatModeGetRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatModeSetRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointGetRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointReportRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSetRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSupportedGetRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSupportedReportRequest.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/thermostat/ThermostatMode.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/thermostat/ThermostatSetPointIndex.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/node/Node.java create mode 100644 zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/node/NodeManager.java create mode 100644 zwave/lantern-zwave/src/main/resources/META-INF/services/com.lanternsoftware.zwave.message.Message create mode 100644 zwave/lantern-zwave/src/test/java/com/lanternsoftware/zwave/TestBits.java create mode 100644 zwave/pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa2bdf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +*.project +*.classpath +.idea/ +.gradle/ +*.settings/ +*target/ +build/ +release/ \ No newline at end of file diff --git a/currentmonitor/lantern-currentmonitor/pom.xml b/currentmonitor/lantern-currentmonitor/pom.xml new file mode 100644 index 0000000..4127a67 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/pom.xml @@ -0,0 +1,105 @@ + + 4.0.0 + com.lanternsoftware.currentmonitor + lantern-currentmonitor + jar + 1.0.0 + lantern-currentmonitor + + + 1.8 + 1.8 + + + + + org.slf4j + slf4j-api + 1.7.29 + + + ch.qos.logback + logback-classic + 1.2.3 + + + com.pi4j + pi4j-gpio-extension + 1.2 + + + com.github.hypfvieh + bluez-dbus + 0.1.3 + + + com.lanternsoftware.currentmonitor + lantern-datamodel-currentmonitor + 1.0.0 + + + com.lanternsoftware.util + lantern-util-http + 1.0.0 + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + false + + + + package + + shade + + + lantern-currentmonitor + + + + + com.lanternsoftware.currentmonitor.MonitorApp + Lantern Power Monitor + ${project.version} + Lantern Software, Inc. + + + + + + + + + + diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java new file mode 100644 index 0000000..c95a8dc --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java @@ -0,0 +1,39 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.currentmonitor.bluetooth.BleApplication; +import com.lanternsoftware.currentmonitor.bluetooth.BleCharacteristic; +import com.lanternsoftware.currentmonitor.bluetooth.BleCharacteristicListener; +import com.lanternsoftware.currentmonitor.bluetooth.BleHelper; +import com.lanternsoftware.currentmonitor.bluetooth.BleService; +import com.lanternsoftware.datamodel.currentmonitor.HubConfigService; +import com.lanternsoftware.util.CollectionUtils; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class BluetoothConfig implements Runnable { + private final AtomicBoolean running = new AtomicBoolean(true); + private final BleApplication app; + + public BluetoothConfig(String _hubName, BleCharacteristicListener _listener) { + BleHelper.getAdapter().setPowered(true); + BleHelper.requestBusName("com.lanternsoftware"); + BleHelper.setBasePath("/com/lanternsoftware"); + HubConfigService service = new HubConfigService(); + List chars = CollectionUtils.transform(service.getCharacteristics(), _c->new BleCharacteristic("HubConfig", _c.getUUID(), _c.name(), _c.getFlags())); + chars.forEach(_c->_c.setListener(_listener)); + app = new BleApplication("Lantern", _hubName, new BleService("HubConfig", service.getServiceUUID(), chars)); + } + + @Override + public void run() { + app.start(); + } + + public void stop() { + synchronized (running) { + running.set(false); + } + app.stop(); + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BreakerSamples.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BreakerSamples.java new file mode 100644 index 0000000..8b171e4 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BreakerSamples.java @@ -0,0 +1,49 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.datamodel.currentmonitor.Breaker; +import com.pi4j.io.gpio.GpioPinAnalogInput; + +import java.util.List; + +public class BreakerSamples { + private final Breaker breaker; + private final GpioPinAnalogInput voltagePin; + private final GpioPinAnalogInput currentPin; + private final List samples; + private int sampleCnt; + + public BreakerSamples(Breaker _breaker, GpioPinAnalogInput _voltagePin, GpioPinAnalogInput _currentPin, List _samples) { + breaker = _breaker; + voltagePin = _voltagePin; + currentPin = _currentPin; + samples = _samples; + } + + public Breaker getBreaker() { + return breaker; + } + + public GpioPinAnalogInput getVoltagePin() { + return voltagePin; + } + + public GpioPinAnalogInput getCurrentPin() { + return currentPin; + } + + public List getSamples() { + return samples; + } + + public PowerSample getSample(int _sample) { + return samples.get(_sample); + } + + public int getSampleCnt() { + return sampleCnt; + } + + public void setSampleCnt(int _sampleCnt) { + sampleCnt = _sampleCnt; + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentListener.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentListener.java new file mode 100644 index 0000000..c820f41 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentListener.java @@ -0,0 +1,7 @@ +package com.lanternsoftware.currentmonitor; + +import java.util.Date; + +public interface CurrentListener { + void onCurrentEvent(int _chip, int _pin, double _currentAmps, Date _start); +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentMonitor.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentMonitor.java new file mode 100644 index 0000000..6f180ed --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentMonitor.java @@ -0,0 +1,241 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.datamodel.currentmonitor.Breaker; +import com.lanternsoftware.datamodel.currentmonitor.BreakerHub; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.concurrency.ConcurrencyUtils; +import com.pi4j.gpio.extension.base.AdcGpioProvider; +import com.pi4j.gpio.extension.mcp.MCP3008GpioProvider; +import com.pi4j.gpio.extension.mcp.MCP3008Pin; +import com.pi4j.io.gpio.GpioController; +import com.pi4j.io.gpio.GpioFactory; +import com.pi4j.io.gpio.GpioPinAnalogInput; +import com.pi4j.io.spi.SpiChannel; +import com.pi4j.io.spi.SpiDevice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class CurrentMonitor { + private static final Logger LOG = LoggerFactory.getLogger(CurrentMonitor.class); + private static final int BATCH_CNT = 4; + private GpioController gpio; + private final ExecutorService executor = Executors.newCachedThreadPool(); + private final Map chips = new HashMap<>(); + private final Map pins = new HashMap<>(); + private Sampler sampler; + private PowerListener listener; + private boolean debug = false; + + public void start() { + try { + gpio = GpioFactory.getInstance(); + LOG.info("Current Monitor Started"); + } + catch (Throwable t) { + LOG.info("Failed to get gpio factory", t); + } + } + + public void stop() { + stopMonitoring(); + ConcurrencyUtils.sleep(1000); + executor.shutdownNow(); + chips.clear(); + pins.clear(); + gpio.shutdown(); + LOG.info("Current Monitor Stopped"); + } + + public void setDebug(boolean _debug) { + debug = _debug; + } + + public void monitorPower(BreakerHub _hub, List _breakers, int _intervalMs, PowerListener _listener) { + stopMonitoring(); + listener = _listener; + sampler = new Sampler(_hub, _breakers, _intervalMs, 2); + LOG.info("Starting to monitor ports {}", CollectionUtils.transformToCommaSeparated(_breakers, _b->String.valueOf(_b.getPort()))); + executor.submit(sampler); + } + + private GpioPinAnalogInput getPin(int _chip, int _pin) { + GpioPinAnalogInput pin; + synchronized (pins) { + AdcGpioProvider chip = chips.get(_chip); + if (chip == null) { + SpiChannel channel = SpiChannel.getByNumber(_chip); + if (channel == null) + return null; + try { + chip = new MCP3008GpioProvider(channel, 1250000, SpiDevice.DEFAULT_SPI_MODE, false); + chips.put(_chip, chip); + } catch (IOException _e) { + LOG.error("Failed to connect to chip {}", _chip, _e); + return null; + } + } + int pinKey = pinKey(_chip, _pin); + pin = pins.get(pinKey); + if (pin == null) { + pin = gpio.provisionAnalogInputPin(chip, MCP3008Pin.ALL[_pin], String.valueOf(pinKey)); + pins.put(pinKey, pin); + } + } + return pin; + } + + private Integer pinKey(int _chip, int _pin) { + return (_chip*8)+_pin; + } + + public void submit(Runnable _runnable) { + executor.submit(_runnable); + } + + public void stopMonitoring() { + if (sampler != null) { + sampler.stop(); + sampler = null; + } + } + + private class Sampler implements Runnable { + private boolean running = true; + private final BreakerHub hub; + private final List> breakers; + private final int intervalNs; + private final int concurrentBreakerCnt; + + public Sampler(BreakerHub _hub, List _breakers, int _intervalMs, int _concurrentBreakerCnt) { + hub = _hub; + GpioPinAnalogInput voltagePin = getPin(0, 0); + breakers = CollectionUtils.transform(_breakers, _b->{ + LOG.info("Getting Chip {}, Pin {} for port {}", _b.getChip(), _b.getPin(), _b.getPort()); + GpioPinAnalogInput currentPin = getPin(_b.getChip(), _b.getPin()); + List batches = new ArrayList<>(BATCH_CNT); + for (int i=0; i samples = new ArrayList<>(30000/_breakers.size()); + for (int j=0; j<30000/_breakers.size(); j++) { + samples.add(new PowerSample()); + } + batches.add(new BreakerSamples(_b, voltagePin, currentPin, samples)); + } + return batches; + }); + intervalNs = _intervalMs*1000000; + concurrentBreakerCnt = Math.min(_breakers.size(), _concurrentBreakerCnt); + } + + @Override + public void run() { + long start = System.nanoTime(); + long interval = 0; + int cycle; + BreakerSamples[] cycleBreakers = new BreakerSamples[concurrentBreakerCnt]; + try { + while (true) { + synchronized (this) { + if (!running) + break; + } + final Date readTime = new Date(); + final long intervalStart = (interval * intervalNs) + start; + long intervalEnd = intervalStart + intervalNs; + cycle = 0; + final int batch = (int) (interval % BATCH_CNT); + int curBreaker; + for (curBreaker = 0; curBreaker < breakers.size(); curBreaker++) { + breakers.get(curBreaker).get(batch).setSampleCnt(0); + } + while (System.nanoTime() < intervalEnd) { + for (curBreaker = 0; curBreaker < concurrentBreakerCnt; curBreaker++) { + cycleBreakers[curBreaker] = breakers.get(((cycle * concurrentBreakerCnt) + curBreaker) % breakers.size()).get(batch); + } + cycle++; + long cycleEnd = intervalStart + (cycle * (intervalNs / hub.getFrequency())); + while (System.nanoTime() < cycleEnd) { + for (curBreaker = 0; curBreaker < concurrentBreakerCnt; curBreaker++) { + PowerSample sample = cycleBreakers[curBreaker].getSample(cycleBreakers[curBreaker].getSampleCnt()); + sample.voltage = cycleBreakers[curBreaker].getVoltagePin().getValue(); + sample.current = cycleBreakers[curBreaker].getCurrentPin().getValue(); + cycleBreakers[curBreaker].setSampleCnt(cycleBreakers[curBreaker].getSampleCnt()+1); + } + } + } + interval++; + executor.submit(() -> { + for (List breaker : breakers) { + double vOffset = 0.0; + double iOffset = 0.0; + BreakerSamples samples = breaker.get(batch); + List validSamples = samples.getSamples().subList(0, samples.getSampleCnt()); + for (PowerSample sample : validSamples) { + vOffset += sample.voltage; + iOffset += sample.current; + } + vOffset /= samples.getSampleCnt(); + iOffset /= samples.getSampleCnt(); + int lowSamples = 0; + double pSum = 0.0; + double vRms = 0.0; + double lowPassFilter = samples.getBreaker().getLowPassFilter(); + for (PowerSample sample : validSamples) { + sample.current -= iOffset; + if (Math.abs(sample.current) < lowPassFilter) + lowSamples++; + sample.voltage -= vOffset; + pSum += sample.current * sample.voltage; + vRms += sample.voltage * sample.voltage; + } + vRms /= validSamples.size(); + vRms = hub.getVoltageCalibrationFactor() * Math.sqrt(vRms); + int lowSampleRatio = (lowSamples * 100) / samples.getSampleCnt(); + double realPower = Math.abs((hub.getVoltageCalibrationFactor() * samples.getBreaker().getFinalCalibrationFactor() * pSum) / samples.getSampleCnt()); + if ((lowSampleRatio > 75) && realPower < 13.0) + realPower = 0.0; + if (samples.getBreaker().getPolarity() == BreakerPolarity.SOLAR) + realPower = -realPower; + if (debug) { + synchronized (CurrentMonitor.this) { + LOG.info("===========================Start Port {}", samples.getBreaker().getPort()); + LOG.info("Samples: {}", samples.getSampleCnt()); + LOG.info("vMin: {}, vMax: {}, vOffset: {}", String.format("%.3f", CollectionUtils.getSmallest(validSamples, Comparator.comparing(_v -> _v.voltage)).voltage), String.format("%.3f", CollectionUtils.getLargest(validSamples, Comparator.comparing(_v -> _v.voltage)).voltage), String.format("%.3f", vOffset)); + LOG.info("iMin: {}, iMax: {}, iOffset: {}", String.format("%.3f", CollectionUtils.getSmallest(validSamples, Comparator.comparing(_v -> _v.current)).current), String.format("%.3f", CollectionUtils.getLargest(validSamples, Comparator.comparing(_v -> _v.current)).current), String.format("%.3f", iOffset)); + double iRms = samples.getBreaker().getFinalCalibrationFactor() * Math.sqrt(CollectionUtils.mean(CollectionUtils.transform(validSamples, _p -> _p.current * _p.current))); + LOG.info("vRms: {}", String.format("%.3f", vRms)); + LOG.info("iRms: {}", String.format("%.3f", iRms)); + double apparentPower = vRms * iRms; + LOG.info("Apparent Power: {} watts", String.format("%.3f", apparentPower)); + LOG.info("Real Power: {} watts", String.format("%.3f", realPower)); + double powerFactor = realPower / apparentPower; + LOG.info("Power Factor: {}", String.format("%.3f", powerFactor)); + LOG.info("===========================End Port {}", samples.getBreaker().getPort()); + } + } + listener.onPowerEvent(new BreakerPower(samples.getBreaker().getPanel(), samples.getBreaker().getSpace(), readTime, realPower, vRms)); + } + }); + } + } + catch (Throwable t) { + LOG.error("Exception while monitoring power", t); + } + } + + synchronized void stop() { + running = false; + } + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java new file mode 100644 index 0000000..c586080 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java @@ -0,0 +1,409 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.currentmonitor.bluetooth.BleCharacteristicListener; +import com.lanternsoftware.currentmonitor.led.LEDFlasher; +import com.lanternsoftware.currentmonitor.util.NetworkMonitor; +import com.lanternsoftware.currentmonitor.wifi.WifiConfig; +import com.lanternsoftware.datamodel.currentmonitor.Breaker; +import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig; +import com.lanternsoftware.datamodel.currentmonitor.BreakerHub; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPowerMinute; +import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic; +import com.lanternsoftware.datamodel.currentmonitor.HubConfigService; +import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.DateUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.concurrency.ConcurrencyUtils; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.http.HttpPool; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.Console; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +public class MonitorApp { + private static final Logger LOG = LoggerFactory.getLogger(MonitorApp.class); + private static final String WORKING_DIR = "/opt/currentmonitor/"; + private static String authCode; + private static MonitorConfig config; + private static BreakerConfig breakerConfig; + private static String host; + private static Date lastUpdateCheck = new Date(); + private static HttpPool pool; + private static LEDFlasher flasher = null; + private static final AtomicBoolean running = new AtomicBoolean(true); + private static final CurrentMonitor monitor = new CurrentMonitor(); + private static final List readings = new ArrayList<>(); + private static final String version = getVersionNumber(); + private static final PowerListener logger = _p -> { + if (!config.isDebug()) { + _p.setHubVersion(version); + if (breakerConfig != null) + _p.setAccountId(breakerConfig.getAccountId()); + synchronized (readings) { + readings.add(_p); + } + } else + LOG.info("Panel{} - Space{} Power: {}W", _p.getPanel(), Breaker.toSpaceDisplay(_p.getSpace()), String.format("%.3f", _p.getPower())); + }; + + public static void main(String[] args) { + config = DaoSerializer.parse(ResourceLoader.loadFileAsString(WORKING_DIR + "config.json"), MonitorConfig.class); + if (config == null) { + LOG.error("Failed to load config file from {}", WORKING_DIR + "config.json"); + return; + } + pool = new HttpPool(10, 10, config.getSocketTimeout(), config.getConnectTimeout(), config.getSocketTimeout()); + host = NullUtils.terminateWith(config.getHost(), "/"); + monitor.setDebug(config.isDebug()); + monitor.start(); + LEDFlasher.setLEDOn(false); + final BluetoothConfig bluetoothConfig = new BluetoothConfig("Lantern Hub", new BleCharacteristicListener() { + @Override + public void write(String _name, byte[] _value) { + HubConfigCharacteristic ch = NullUtils.toEnum(HubConfigCharacteristic.class, _name); + LOG.info("Char Received, Name: {} Value: {}", _name, _value); + monitor.submit(()->{ + switch (ch) { + case HubIndex: + if ((_value.length > 0)) { + config.setHub(_value[0]); + ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config)); + } + break; + case AuthCode: + String value = NullUtils.toString(_value); + if (NullUtils.isNotEmpty(value)) { + authCode = value; + config.setAuthCode(value); + ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config)); + } + break; + case WifiCredentials: + String ssid = HubConfigService.decryptWifiSSID(_value); + String pwd = HubConfigService.decryptWifiPassword(_value); + if (NullUtils.isNotEmpty(ssid) && NullUtils.isNotEmpty(pwd)) + WifiConfig.setCredentials(ssid, pwd); + break; + case Flash: + if ((CollectionUtils.length(_value) == 0) || (_value[0] == 0)) { + if (flasher != null) { + flasher.stop(); + flasher = null; + } + else + LEDFlasher.setLEDOn(false); + } + else { + if (flasher == null) { + flasher = new LEDFlasher(); + monitor.submit(flasher); + } + } + break; + case Restart: + LOG.info("Restarting Current Monitor..."); + try { + Runtime.getRuntime().exec("service currentmonitor restart"); + } catch (IOException _e) { + LOG.error("Exception occurred while trying to restart", _e); + } + break; + case Reboot: + LOG.info("Rebooting Pi..."); + try { + Runtime.getRuntime().exec("reboot now"); + } catch (IOException _e) { + LOG.error("Exception occurred while trying to reboot", _e); + } + break; + } + }); + } + + @Override + public byte[] read(String _name) { + HubConfigCharacteristic ch = NullUtils.toEnum(HubConfigCharacteristic.class, _name); + if (HubConfigCharacteristic.HubIndex == ch) + return new byte[]{(byte)(config == null?0:config.getHub())}; + if (HubConfigCharacteristic.AccountId == ch) + return ByteBuffer.allocate(4).putInt(breakerConfig == null?0:breakerConfig.getAccountId()).array(); + if (HubConfigCharacteristic.NetworkState == ch) + return new byte[]{NetworkMonitor.getNetworkStatus().toMask()}; + return null; + } + }); + monitor.submit(bluetoothConfig); + if (NullUtils.isNotEmpty(config.getAuthCode())) { + authCode = config.getAuthCode(); + //TODO: check auth code validity + } + else { + HttpGet auth = new HttpGet(host + "auth"); + HttpPool.addBasicAuthHeader(auth, config.getUsername(), config.getPassword()); + authCode = DaoSerializer.getString(DaoSerializer.parse(pool.executeToString(auth)), "auth_code"); + } + while (true) { + HttpGet get = new HttpGet(host + "config"); + get.addHeader("auth_code", authCode); + breakerConfig = DaoSerializer.parse(pool.executeToString(get), BreakerConfig.class); + if (breakerConfig != null) + break; + LOG.error("Failed to load breaker config. Retrying in 5 seconds..."); + ConcurrencyUtils.sleep(5000); + } + LOG.info("Breaker Config loaded"); + LOG.debug(DaoSerializer.toJson(breakerConfig)); + BreakerHub hub = breakerConfig.getHub(config.getHub()); + if (hub != null) { + List breakers = breakerConfig.getBreakersForHub(config.getHub()); + LOG.info("Monitoring {} breakers for hub {}", CollectionUtils.size(breakers), hub.getHub()); + monitor.monitorPower(hub, breakers, 1000, logger); + } + monitor.submit(new PowerPoster()); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + synchronized (running) { + running.set(false); + } + bluetoothConfig.stop(); + monitor.stop(); + pool.shutdown(); + }, "Monitor Shutdown")); + Console c = System.console(); + BufferedReader reader = (c == null)?new BufferedReader(new InputStreamReader(System.in)):null; + while (running.get()) { + try { + String command = c != null ? c.readLine() : reader.readLine(); + if (NullUtils.isEqual("exit", command)) + break; + } + catch (Exception _e) { + LOG.error("Exception while reading from console input", _e); + break; + } + } + } + + private static final class PowerPoster implements Runnable { + private final long firstPost; + private long lastPost; + private int lastMinute; + private final Map breakers = new HashMap<>(); + + public PowerPoster() { + firstPost = (new Date().getTime()/1000)*1000; + lastPost = new Date().getTime(); + lastMinute = (int)(new Date().getTime()/60000); + } + + @Override + public void run() { + try { + while (true) { + synchronized (running) { + if (!running.get()) + break; + } + DaoEntity post = null; + DaoEntity minutePost = null; + int curMinute = (int) (new Date().getTime() / 60000); + synchronized (readings) { + if (!readings.isEmpty()) { + post = new DaoEntity("readings", DaoSerializer.toDaoEntities(readings)); + if (curMinute != lastMinute) { + HubPowerMinute minute = new HubPowerMinute(); + minute.setAccountId(breakerConfig.getAccountId()); + minute.setHub(config.getHub()); + minute.setMinute(lastMinute); + minute.setBreakers(CollectionUtils.transform(breakers.entrySet(), _e -> { + BreakerPowerMinute breaker = new BreakerPowerMinute(); + breaker.setPanel(Breaker.toPanel(_e.getKey())); + breaker.setSpace(Breaker.toSpace(_e.getKey())); + breaker.setReadings(CollectionUtils.asArrayList(_e.getValue())); + return breaker; + })); + breakers.clear(); + minutePost = DaoSerializer.toDaoEntity(minute); + lastMinute = curMinute; + } + for (BreakerPower power : readings) { + Float[] breakerReadings = breakers.computeIfAbsent(Breaker.toId(power.getPanel(), power.getSpace()), _i -> new Float[60]); + breakerReadings[(int) ((power.getReadTime().getTime() / 1000)%60)] = (float) power.getPower(); + } + readings.clear(); + } + } + if (minutePost != null) { + byte[] payload = DaoSerializer.toZipBson(minutePost); + if (!post(payload, "power/hub")) { + LOG.info("Failed Posting HubPowerMinute, writing cache"); + ResourceLoader.writeFile(WORKING_DIR + "cache/" + UUID.randomUUID().toString() + ".min", payload); + } + } + if (post != null) { + byte[] payload = DaoSerializer.toZipBson(post); + if (post(payload, "power/batch")) { + File[] files = new File(WORKING_DIR + "cache").listFiles(); + if (files != null) { + for (File file : files) { + payload = ResourceLoader.loadFile(file.getAbsolutePath()); + if (post(payload, file.getName().endsWith("dat") ? "power/batch" : "power/hub")) + file.delete(); + else + break; + } + } + } + } + if (DateUtils.diffInSeconds(new Date(), lastUpdateCheck) >= config.getUpdateInterval()) { + lastUpdateCheck = new Date(); + monitor.submit(new UpdateChecker()); + monitor.submit(new CommandChecker()); + } + long now = new Date().getTime(); + long duration = (now - firstPost)%1000; + if (now - lastPost < 1000) { + ConcurrencyUtils.sleep(1000 - duration); + } + lastPost = now; + } + } + catch (Throwable t) { + LOG.error("Exception in PowerPoster", t); + } + } + } + + private static void uploadLog() { + LOG.info("Commanded to upload log file, preparing..."); + String log = ResourceLoader.loadFileAsString(WORKING_DIR + "log/log.txt"); + if (NullUtils.isNotEmpty(log)) { + DaoEntity payload = new DaoEntity("command", "log").and("payload", log); + post(DaoSerializer.toZipBson(payload), "command"); + } + } + + private static boolean post(byte[] _payload, String _path) { + HttpPost post = new HttpPost(host + _path); + post.addHeader("auth_code", authCode); + post.setEntity(new ByteArrayEntity(_payload, ContentType.APPLICATION_OCTET_STREAM)); + CloseableHttpResponse resp = pool.execute(post); + try { + return ((resp != null) && (resp.getStatusLine() != null) && (resp.getStatusLine().getStatusCode() == 200)); + } finally { + IOUtils.closeQuietly(resp); + } + } + + + private static final class UpdateChecker implements Runnable { + @Override + public void run() { + DaoEntity meta = DaoSerializer.fromZipBson(pool.executeToByteArray(new HttpGet(host + "update/version"))); + String newVersion = DaoSerializer.getString(meta, "version"); + if (NullUtils.isNotEqual(newVersion, version)) { + LOG.info("New version found, {}, downloading...", newVersion); + byte[] jar = pool.executeToByteArray(new HttpGet(host + "update")); + if (CollectionUtils.length(jar) == DaoSerializer.getInteger(meta, "size") && NullUtils.isEqual(DigestUtils.md5Hex(jar), DaoSerializer.getString(meta, "checksum"))) { + LOG.info("Update downloaded, writing jar and restarting..."); + ResourceLoader.writeFile(WORKING_DIR + "lantern-currentmonitor.jar", jar); + try { + Runtime.getRuntime().exec("service currentmonitor restart"); + } catch (IOException _e) { + LOG.error("Exception occurred while trying to restart", _e); + } + } + } + } + } + + private static final class CommandChecker implements Runnable { + @Override + public void run() { + HttpGet get = new HttpGet(host + "command"); + get.addHeader("auth_code", authCode); + DaoEntity meta = DaoSerializer.fromZipBson(pool.executeToByteArray(get)); + for (String command : DaoSerializer.getList(meta, "commands", String.class)) { + if (NullUtils.isEqual(command, "log")) { + uploadLog(); + } + else if (NullUtils.makeNotNull(command).startsWith("timeout")) { + LOG.info("Updating timeouts..."); + String[] timeouts = NullUtils.cleanSplit(command, "-"); + if (CollectionUtils.size(timeouts) != 3) + continue; + config.setConnectTimeout(DaoSerializer.toInteger(timeouts[1])); + config.setSocketTimeout(DaoSerializer.toInteger(timeouts[2])); + HttpPool old = pool; + pool = new HttpPool(10, 10, config.getSocketTimeout(), config.getConnectTimeout(), config.getSocketTimeout()); + old.shutdown(); + ResourceLoader.writeFile(WORKING_DIR+"config.json", DaoSerializer.toJson(config)); + } + else if (NullUtils.isEqual(command, "extend_filesystem")) { + LOG.info("Extending filesystem and rebooting"); + try { + Runtime.getRuntime().exec("raspi-config --expand-rootfs"); + ConcurrencyUtils.sleep(3000); + Runtime.getRuntime().exec("reboot"); + } catch (IOException _e) { + LOG.error("Exception occurred while trying to extend filesystem", _e); + } + + } + else if (NullUtils.isEqual(command, "restart")) { + LOG.info("Restarting..."); + try { + Runtime.getRuntime().exec("service currentmonitor restart"); + } catch (IOException _e) { + LOG.error("Exception occurred while trying to restart", _e); + } + } + } + } + } + + private static String getVersionNumber() { + InputStream is = null; + try { + is = MonitorApp.class.getResourceAsStream("/META-INF/MANIFEST.MF"); + Manifest manifest = new Manifest(is); + Attributes attr = manifest.getMainAttributes(); + String version = attr.getValue("Specification-Version"); + LOG.info("Current Version: {}", version); + return version; + } + catch (Exception _e) { + LOG.error("Failed to get current version number", _e); + return ""; + } + finally { + IOUtils.closeQuietly(is); + } + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java new file mode 100644 index 0000000..0d0d405 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java @@ -0,0 +1,97 @@ +package com.lanternsoftware.currentmonitor; + + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +@DBSerializable +public class MonitorConfig { + private String host; + private String authCode; + private String username; + private String password; + private int hub; + private boolean debug; + private int connectTimeout; + private int socketTimeout; + private int updateInterval; + + public MonitorConfig() { + } + + public MonitorConfig(int _hub, String _host) { + hub = _hub; + host = _host; + } + + public String getHost() { + return host; + } + + public void setHost(String _host) { + host = _host; + } + + public String getAuthCode() { + return authCode; + } + + public void setAuthCode(String _authCode) { + authCode = _authCode; + } + + public String getUsername() { + return username; + } + + public void setUsername(String _username) { + username = _username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String _password) { + password = _password; + } + + public int getHub() { + return hub; + } + + public void setHub(int _hub) { + hub = _hub; + } + + public boolean isDebug() { + return debug; + } + + public void setDebug(boolean _debug) { + debug = _debug; + } + + public int getConnectTimeout() { + return connectTimeout == 0?3000:connectTimeout; + } + + public void setConnectTimeout(int _connectTimeout) { + connectTimeout = _connectTimeout; + } + + public int getSocketTimeout() { + return socketTimeout == 0?5000:socketTimeout; + } + + public void setSocketTimeout(int _socketTimeout) { + socketTimeout = _socketTimeout; + } + + public int getUpdateInterval() { + return updateInterval == 0?300:updateInterval; + } + + public void setUpdateInterval(int _updateInterval) { + updateInterval = _updateInterval; + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerListener.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerListener.java new file mode 100644 index 0000000..a3a0a5a --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerListener.java @@ -0,0 +1,7 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; + +public interface PowerListener { + void onPowerEvent(BreakerPower _power); +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerSample.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerSample.java new file mode 100644 index 0000000..5efb1f4 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerSample.java @@ -0,0 +1,6 @@ +package com.lanternsoftware.currentmonitor; + +public class PowerSample { + public double voltage; + public double current; +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/AbstractProperties.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/AbstractProperties.java new file mode 100644 index 0000000..69c4232 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/AbstractProperties.java @@ -0,0 +1,90 @@ +package com.lanternsoftware.currentmonitor.bluetooth; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import org.freedesktop.dbus.DBusPath; +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.interfaces.Properties; +import org.freedesktop.dbus.types.Variant; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class AbstractProperties implements Properties { + protected final String interfaceName; + protected final Map> properties = new HashMap<>(); + + public AbstractProperties(Class _bleClass) { + interfaceName = _bleClass.getCanonicalName(); + } + + public String getInterfaceName() { + return interfaceName; + } + + public abstract DBusPath getPath(); + + @SuppressWarnings("unchecked") + @Override + public A Get(String _interface, String _propertyName) { + if (NullUtils.isNotEqual(_interface, interfaceName)) + return null; + Variant var = properties.get(_propertyName); + try { + return (A) var.getValue(); + } + catch (ClassCastException _e) { + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + public void Set(String _interface, String _propertyName, A _value) { + if ((_value == null) || NullUtils.isNotEqual(_interface, interfaceName)) + return; + properties.put(_propertyName, new Variant(_value)); + } + + @Override + public Map> GetAll(String _interfaceName) { + if (NullUtils.isNotEqual(_interfaceName, getInterfaceName())) + return new HashMap<>(); + return getProperties(); + } + + public Map> getProperties() { + return properties; + } + + public List getAllObjects() { + List objects = new ArrayList<>(); + getAllObjects(objects); + return objects; + } + + public void getAllObjects(List _objects) { + _objects.add(this); + for (AbstractProperties o : CollectionUtils.makeNotNull(getChildObjects())) { + o.getAllObjects(_objects); + } + } + + public List getChildObjects() { + return null; + } + + public Map>>> getAllManagedObjects() { + return getAllManagedObjects(getAllObjects()); + } + + public static Map>>> getAllManagedObjects(List _objects) { + Map>>> objects = new HashMap<>(); + for (AbstractProperties o : _objects) { + objects.put(o.getPath(), CollectionUtils.asHashMap(o.getInterfaceName(), o.getProperties())); + } + return objects; + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleAdvertisement.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleAdvertisement.java new file mode 100644 index 0000000..d36267a --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleAdvertisement.java @@ -0,0 +1,69 @@ +package com.lanternsoftware.currentmonitor.bluetooth; + +import com.lanternsoftware.util.CollectionUtils; +import org.bluez.LEAdvertisement1; +import org.bluez.LEAdvertisingManager1; +import org.freedesktop.dbus.DBusPath; +import org.freedesktop.dbus.interfaces.Properties; +import org.freedesktop.dbus.types.Variant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; + +public class BleAdvertisement extends AbstractProperties implements LEAdvertisement1, Properties { + private static final Logger LOG = LoggerFactory.getLogger(BleAdvertisement.class); + + private final LEAdvertisingManager1 advertiser; + private final DBusPath advertisementPath; + + public BleAdvertisement(String _name, BleApplication _app) { + super(LEAdvertisement1.class); + String[] serviceUUIDs = CollectionUtils.transform(_app.getServices(), _s->_s.getUuid().toString()).toArray(new String[0]); + advertisementPath = new DBusPath(BleHelper.advertismentPath(_app.getName())); + advertiser = BleHelper.getRemoteObject(BleHelper.getAdapter().getDbusPath(), LEAdvertisingManager1.class); + properties.put("Type", new Variant<>("peripheral")); + properties.put("ServiceUUIDs", new Variant<>(serviceUUIDs)); + properties.put("LocalName", new Variant<>(_name)); + properties.put("Includes", new Variant<>(new String[]{"tx-power"})); + BleHelper.unExportObject(this); + BleHelper.exportObject(this); + } + + public void start() { + try { + advertiser.RegisterAdvertisement(advertisementPath, new HashMap<>()); + } + catch (Exception _e) { + LOG.error("Failed to register advertisement", _e); + } + } + + public void stop() { + try { + advertiser.UnregisterAdvertisement(advertisementPath); + } + catch (Exception _e) { + LOG.error("Failed to unregister advertisement", _e); + } + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return advertisementPath.getPath(); + } + + @Override + public DBusPath getPath() { + return advertisementPath; + } + + @Override + public void Release() { + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleApplication.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleApplication.java new file mode 100644 index 0000000..e4ca956 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleApplication.java @@ -0,0 +1,90 @@ +package com.lanternsoftware.currentmonitor.bluetooth; + +import com.lanternsoftware.util.CollectionUtils; +import org.bluez.GattApplication1; +import org.bluez.GattManager1; +import org.freedesktop.dbus.DBusPath; +import org.freedesktop.dbus.interfaces.ObjectManager; +import org.freedesktop.dbus.types.Variant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BleApplication implements GattApplication1, ObjectManager { + private static final Logger LOG = LoggerFactory.getLogger(BleApplication.class); + + private final String name; + private final DBusPath appPath; + private final GattManager1 appManager; + private final List services; + private final BleAdvertisement advertisement; + + public BleApplication(String _name, String _advertisedName, BleService... _services) { + this(_name, _advertisedName, CollectionUtils.asArrayList(_services)); + } + + public BleApplication(String _name, String _advertisedName, List _services) { + name = _name; + appPath = new DBusPath(BleHelper.applicationPath(_name)); + appManager = BleHelper.getRemoteObject(BleHelper.getAdapter().getDbusPath(), GattManager1.class); + services = _services; + advertisement = new BleAdvertisement(_advertisedName, this); + List objects = getManagedObjects(); + BleHelper.unExportObject(this); + objects.forEach(BleHelper::unExportObject); + BleHelper.exportObject(this); + objects.forEach(BleHelper::exportObject); + } + + public List getManagedObjects() { + return CollectionUtils.aggregate(services, AbstractProperties::getAllObjects); + } + + public String getName() { + return name; + } + + public List getServices() { + return services; + } + + @Override + public Map>>> GetManagedObjects() { + return AbstractProperties.getAllManagedObjects(getManagedObjects()); + } + + public void start() { + try { + appManager.RegisterApplication(appPath, new HashMap<>()); + advertisement.start(); + } + catch (Exception _e) { + LOG.error("Failed to register application", _e); + _e.printStackTrace(); + } + } + + public void stop() { + try { + advertisement.stop(); + appManager.UnregisterApplication(appPath); + BleHelper.connection.disconnect(); + } + catch (Exception _e) { + LOG.error("Failed to unregister application", _e); + } + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return appPath.getPath(); + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristic.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristic.java new file mode 100644 index 0000000..358952e --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristic.java @@ -0,0 +1,121 @@ +package com.lanternsoftware.currentmonitor.bluetooth; + +import com.lanternsoftware.datamodel.currentmonitor.CharacteristicFlag; +import com.lanternsoftware.util.CollectionUtils; +import org.bluez.GattCharacteristic1; +import org.bluez.datatypes.TwoTuple; +import org.freedesktop.dbus.DBusPath; +import org.freedesktop.dbus.FileDescriptor; +import org.freedesktop.dbus.types.UInt16; +import org.freedesktop.dbus.types.Variant; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class BleCharacteristic extends AbstractProperties implements GattCharacteristic1 { + private final String charName; + private final DBusPath charPath; + private final UUID uuid; + private final List descriptors; + private BleCharacteristicListener listener; + + public BleCharacteristic(String _serviceName, UUID _uuid, String _characteristicName, Collection _flags) { + this(_serviceName, _uuid, _characteristicName, _flags, null); + } + + public BleCharacteristic(String _serviceName, UUID _uuid, String _characteristicName, Collection _flags, List _descriptors) { + super(GattCharacteristic1.class); + charName = _characteristicName; + charPath = new DBusPath(BleHelper.characteristicPath(_serviceName, _characteristicName)); + uuid = _uuid; + descriptors = _descriptors; + properties.put("Service", new Variant<>(new DBusPath(BleHelper.servicePath(_serviceName)))); + if (uuid != null) + properties.put("UUID", new Variant<>(uuid.toString())); + if (CollectionUtils.isNotEmpty(_flags)) + properties.put("Flags", new Variant<>(CharacteristicFlag.toArray(_flags))); + if (CollectionUtils.isNotEmpty(descriptors)) + properties.put("Descriptors", new Variant<>(BleHelper.toPaths(descriptors))); + } + + public void setListener(BleCharacteristicListener _listener) { + listener = _listener; + } + + @Override + public List getChildObjects() { + return descriptors; + } + + @Override + public boolean isRemote() { + return false; + } + + public DBusPath getPath() { + return charPath; + } + + @Override + public String getObjectPath() { + return charPath.getPath(); + } + + @Override + public byte[] ReadValue(Map> _options) { + if (listener == null) + return null; + int offset = 0; + Variant voffset = _options.get("offset"); + if (voffset != null) { + if (voffset.getValue() instanceof UInt16) + offset = ((UInt16)voffset.getValue()).intValue(); + } + byte[] ret = listener.read(charName); + if (ret == null) + return null; + return offset > 0?Arrays.copyOfRange(ret, offset, ret.length):ret; + } + + @Override + public void WriteValue(byte[] _bytes, Map> _map) { + if (listener != null) + listener.write(charName, _bytes); + } + + @Override + public TwoTuple AcquireWrite(Map> _map) { + return null; + } + + @Override + public TwoTuple AcquireNotify(Map> _map) { + return null; + } + + @Override + public void StartNotify() { + + } + + @Override + public void StopNotify() { + + } + + @Override + public void Confirm() { + + } + + public UUID getUuid() { + return uuid; + } + + public List getDescriptors() { + return descriptors; + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristicListener.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristicListener.java new file mode 100644 index 0000000..be746c9 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristicListener.java @@ -0,0 +1,6 @@ +package com.lanternsoftware.currentmonitor.bluetooth; + +public interface BleCharacteristicListener { + void write(String _name, byte[] _value); + byte[] read(String _name); +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleDescriptor.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleDescriptor.java new file mode 100644 index 0000000..bf5ad35 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleDescriptor.java @@ -0,0 +1,32 @@ +package com.lanternsoftware.currentmonitor.bluetooth; + +import org.bluez.GattDescriptor1; +import org.freedesktop.dbus.DBusPath; + +import java.util.UUID; + +public class BleDescriptor extends AbstractProperties { + private final DBusPath charPath; + private final UUID uuid; + + public BleDescriptor(String _serviceName, String _characteristicName, String _descriptorName, UUID _uuid) { + super(GattDescriptor1.class); + charPath = new DBusPath(BleHelper.descriptorPath(_serviceName, _characteristicName, _descriptorName)); + uuid = _uuid; + } + + @Override + public DBusPath getPath() { + return charPath; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return charPath.getPath(); + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleHelper.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleHelper.java new file mode 100644 index 0000000..cdad5a2 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleHelper.java @@ -0,0 +1,115 @@ +package com.lanternsoftware.currentmonitor.bluetooth; + +import com.github.hypfvieh.bluetooth.DeviceManager; +import com.github.hypfvieh.bluetooth.wrapper.BluetoothAdapter; +import com.lanternsoftware.util.CollectionUtils; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.interfaces.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public abstract class BleHelper { + private static final Logger LOG = LoggerFactory.getLogger(BleHelper.class); + + private static String basePath; + private static final DeviceManager deviceManager; + public static final DBusConnection connection; + static { + DeviceManager m = null; + DBusConnection c = null; + try { + DeviceManager.createInstance(false); + m = DeviceManager.getInstance(); + c = m.getDbusConnection(); + } + catch (Exception _e) { + LOG.error("Failed to get dbus connection", _e); + } + deviceManager = m; + connection = c; + } + + public static void setBasePath(String _basePath) { + basePath = _basePath; + } + + public static String getBasePath() { + return basePath; + } + + public static String advertismentPath(String _advertisementName) { + return BleHelper.getBasePath() + "/advertisement/" + _advertisementName; + } + + public static String applicationPath(String _appPath) { + return BleHelper.getBasePath() + "/application/" + _appPath; + } + + public static String servicePath(String _serviceName) { + return BleHelper.getBasePath() + "/service/" + _serviceName; + } + + public static String characteristicPath(String _serviceName, String _characteristicName) { + return servicePath(_serviceName) + "/" + _characteristicName; + } + + public static String descriptorPath(String _serviceName, String _characteristicName, String _descriptorPath) { + return servicePath(_serviceName) + "/" + _characteristicName + "/" + _descriptorPath; + } + + public static String[] toPaths(List _properties) { + return CollectionUtils.transform(_properties, Properties::getObjectPath).toArray(new String[0]); + } + + public static BluetoothAdapter getAdapter() { + return (deviceManager != null)?deviceManager.getAdapter():null; + } + + public static void requestBusName(String _name) { + try { + if (connection != null) + connection.requestBusName(_name); + } + catch (Exception _e) { + LOG.error("Failed to request bus name", _e); + } + } + + public static T getRemoteObject(String _path, Class _objClass) { + try { + return connection.getRemoteObject("org.bluez", _path, _objClass); + } catch (DBusException _e) { + LOG.error("Failed to get remote object", _e); + return null; + } + } + + public static void exportObject(DBusInterface _object) { + try { + if (connection != null) + connection.exportObject(_object.getObjectPath(), _object); + } + catch (Exception _e) { + LOG.error("Failed to export object", _e); + } + } + + public static void unExportObject(DBusInterface _object) { + if (_object != null) + unExportObject(_object.getObjectPath()); + } + + public static void unExportObject(String _objectPath) { + try { + if (connection != null) + connection.unExportObject(_objectPath); + } + catch (Exception _e) { + LOG.error("Failed to unexport object", _e); + } + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleService.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleService.java new file mode 100644 index 0000000..bc4c279 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleService.java @@ -0,0 +1,55 @@ +package com.lanternsoftware.currentmonitor.bluetooth; + +import com.lanternsoftware.util.CollectionUtils; +import org.bluez.GattService1; +import org.freedesktop.dbus.DBusPath; +import org.freedesktop.dbus.types.Variant; + +import java.util.List; +import java.util.UUID; + + +public class BleService extends AbstractProperties implements GattService1 { + private final DBusPath servicePath; + private final UUID uuid; + private final List characteristics; + + public BleService(String _serviceName, UUID _uuid, List _characteristics) { + super(GattService1.class); + servicePath = new DBusPath(BleHelper.servicePath(_serviceName)); + uuid = _uuid; + characteristics = _characteristics; + if (uuid != null) + properties.put("UUID", new Variant<>(uuid.toString())); + properties.put("Primary", new Variant<>(Boolean.TRUE)); + if (CollectionUtils.isNotEmpty(characteristics)) + properties.put("Characteristics", new Variant<>(BleHelper.toPaths(characteristics))); + } + + public DBusPath getPath() { + return servicePath; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return getPath().getPath(); + } + + @Override + public List getChildObjects() { + return characteristics; + } + + public UUID getUuid() { + return uuid; + } + + public List getCharacteristics() { + return characteristics; + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java new file mode 100644 index 0000000..d815a81 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java @@ -0,0 +1,56 @@ +package com.lanternsoftware.currentmonitor.dao; + +import com.lanternsoftware.currentmonitor.MonitorConfig; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.util.Collections; +import java.util.List; + +public class MonitorConfigSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return MonitorConfig.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(MonitorConfig _o) + { + DaoEntity d = new DaoEntity(); + d.put("host", _o.getHost()); + d.put("auth_code", _o.getAuthCode()); + d.put("username", _o.getUsername()); + d.put("password", _o.getPassword()); + d.put("hub", _o.getHub()); + d.put("debug", _o.isDebug()); + d.put("socket_timeout", _o.getSocketTimeout()); + d.put("connect_timeout", _o.getConnectTimeout()); + d.put("update_interval", _o.getUpdateInterval()); + return d; + } + + @Override + public MonitorConfig fromDaoEntity(DaoEntity _d) + { + MonitorConfig o = new MonitorConfig(); + o.setHost(DaoSerializer.getString(_d, "host")); + o.setAuthCode(DaoSerializer.getString(_d, "auth_code")); + o.setUsername(DaoSerializer.getString(_d, "username")); + o.setPassword(DaoSerializer.getString(_d, "password")); + o.setHub(DaoSerializer.getInteger(_d, "hub")); + o.setDebug(DaoSerializer.getBoolean(_d, "debug")); + o.setSocketTimeout(DaoSerializer.getInteger(_d, "socket_timeout")); + o.setConnectTimeout(DaoSerializer.getInteger(_d, "connect_timeout")); + o.setUpdateInterval(DaoSerializer.getInteger(_d, "update_interval")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/led/LEDFlasher.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/led/LEDFlasher.java new file mode 100644 index 0000000..e7a5475 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/led/LEDFlasher.java @@ -0,0 +1,40 @@ +package com.lanternsoftware.currentmonitor.led; + +import com.lanternsoftware.util.concurrency.ConcurrencyUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class LEDFlasher implements Runnable { + private static final Logger LOG = LoggerFactory.getLogger(LEDFlasher.class); + + private final AtomicBoolean running = new AtomicBoolean(true); + private boolean on = false; + + @Override + public void run() { + while (running.get()) { + on = !on; + setLEDOn(on); + ConcurrencyUtils.sleep(250); + } + setLEDOn(false); + } + + public void stop() { + running.set(false); + } + + public static void setLEDOn(boolean _on) { + try { + if (_on) + Runtime.getRuntime().exec(new String[]{"sh", "-c", "echo default-on > /sys/class/leds/led1/trigger"}); + else + Runtime.getRuntime().exec(new String[]{"sh", "-c", "echo none > /sys/class/leds/led1/trigger"}); + } + catch (Exception _e) { + LOG.error("Failed to change LED state", _e); + } + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/util/NetworkMonitor.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/util/NetworkMonitor.java new file mode 100644 index 0000000..3130ef8 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/util/NetworkMonitor.java @@ -0,0 +1,53 @@ +package com.lanternsoftware.currentmonitor.util; + +import com.lanternsoftware.datamodel.currentmonitor.NetworkStatus; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class NetworkMonitor { + private static final Logger LOG = LoggerFactory.getLogger(NetworkMonitor.class); + + private static final Pattern ethernetPattern = Pattern.compile(".*(eth[0-9]):(.*)"); + private static final Pattern wifiPattern = Pattern.compile(".*(wlan[0-9]):(.*)"); + + public static NetworkStatus getNetworkStatus() { + NetworkStatus status = new NetworkStatus(); + String[] commands = {"ifconfig", "-a"}; + InputStream is = null; + try { + is = Runtime.getRuntime().exec(commands).getInputStream(); + String ifconfig = IOUtils.toString(is); + status.setEthernetIPs(getIPs(ifconfig, ethernetPattern)); + status.setWifiIPs(getIPs(ifconfig, wifiPattern)); + } + catch (Exception _e) { + LOG.error("Failed to check network state", _e); + } + finally { + IOUtils.closeQuietly(is); + } + return status; + } + + private static List getIPs(String _ifConfig, Pattern _pattern) { + List ips = new ArrayList<>(); + Matcher m = _pattern.matcher(_ifConfig); + while (m.find()) { + int start = m.start(0); + int ipStart = _ifConfig.indexOf("inet ", start) + 5; + if (ipStart > 4) { + int ipEnd = _ifConfig.indexOf(" ", ipStart); + if (ipEnd > -1) + ips.add(_ifConfig.substring(ipStart, ipEnd)); + } + } + return ips; + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/wifi/WifiConfig.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/wifi/WifiConfig.java new file mode 100644 index 0000000..cd3e9ed --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/wifi/WifiConfig.java @@ -0,0 +1,44 @@ +package com.lanternsoftware.currentmonitor.wifi; + +import com.lanternsoftware.util.ResourceLoader; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; + +public abstract class WifiConfig { + private static final Logger LOG = LoggerFactory.getLogger(WifiConfig.class); + + private static final String WIFI_CONFIG_PATH = "/etc/wpa_supplicant/wpa_supplicant.conf"; + private static final String CONF_FORMAT = "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\ncountry=US\nnetwork={\n\tssid=\"%s\"\n\t%s\n}\n"; + + public static void setCredentials(String _ssid, String _password) { + String[] commands = {"wpa_passphrase", _ssid, _password}; + InputStream is = null; + try { + is = Runtime.getRuntime().exec(commands).getInputStream(); + String newConf = IOUtils.toString(is); + if (newConf == null) + return; + int idx = newConf.indexOf("psk="); + if (idx > 0) { + if (newConf.charAt(idx-1) == '#') + idx = newConf.indexOf("psk=", idx+1); + if (idx > 0) { + int endIdx = newConf.indexOf("\n", idx); + if (endIdx > 0) { + String finalConf = String.format(CONF_FORMAT, _ssid, newConf.substring(idx, endIdx)); + ResourceLoader.writeFile(WIFI_CONFIG_PATH, finalConf); + } + } + } + } + catch (Exception _e) { + LOG.error("Failed to write wifi credentials", _e); + } + finally { + IOUtils.closeQuietly(is); + } + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/org/bluez/GattApplication1.java b/currentmonitor/lantern-currentmonitor/src/main/java/org/bluez/GattApplication1.java new file mode 100644 index 0000000..e56b17c --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/java/org/bluez/GattApplication1.java @@ -0,0 +1,6 @@ +package org.bluez; + +import org.freedesktop.dbus.interfaces.DBusInterface; + +public interface GattApplication1 extends DBusInterface { +} diff --git a/currentmonitor/lantern-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer b/currentmonitor/lantern-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer new file mode 100644 index 0000000..871a7e6 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer @@ -0,0 +1 @@ +com.lanternsoftware.currentmonitor.dao.MonitorConfigSerializer diff --git a/currentmonitor/lantern-currentmonitor/src/main/resources/logback.xml b/currentmonitor/lantern-currentmonitor/src/main/resources/logback.xml new file mode 100644 index 0000000..939eaaa --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/main/resources/logback.xml @@ -0,0 +1,28 @@ + + + + + + + ${log.pattern} + + + + + /opt/currentmonitor/log/log.txt + + /opt/currentmonitor/log/log.%d{yyyy-MM-dd}.%i.txt + 20MB + 20 + + + ${log.pattern} + + + + + + + + + \ No newline at end of file diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateConfig.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateConfig.java new file mode 100644 index 0000000..c882746 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateConfig.java @@ -0,0 +1,15 @@ +package com.lanternsoftware.currentmonitor; + + +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.dao.DaoSerializer; + +public class CreateConfig { + public static void main(String[] args) { +// MonitorConfig c = new MonitorConfig(0, "https://mark.lanternsoftware.com/currentmonitor"); + MonitorConfig c = new MonitorConfig(1, "https://mark.lanternsoftware.com/currentmonitor"); + c.setDebug(true); + ResourceLoader.writeFile(LanternFiles.OPS_PATH + "hub1.json", DaoSerializer.toJson(c)); + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateSSIDKey.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateSSIDKey.java new file mode 100644 index 0000000..da52298 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateSSIDKey.java @@ -0,0 +1,9 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.util.cryptography.AESTool; + +public class CreateSSIDKey { + public static void main(String[] args) { + AESTool.printRandomSecretKey(); + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorAppSerializers.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorAppSerializers.java new file mode 100644 index 0000000..e7dc8fb --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorAppSerializers.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.currentmonitor; + + +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.dao.generator.DaoSerializerGenerator; + +public class CurrentMonitorAppSerializers { + public static void main(String[] args) { + DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_PATH + "currentmonitor", true, null); + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/NetworkTest.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/NetworkTest.java new file mode 100644 index 0000000..b81ebc2 --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/NetworkTest.java @@ -0,0 +1,10 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.currentmonitor.util.NetworkMonitor; +import com.lanternsoftware.datamodel.currentmonitor.NetworkStatus; + +public class NetworkTest { + public static void main(String[] args) { + NetworkStatus status = NetworkMonitor.getNetworkStatus(); + } +} diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/ReleaseCurrentMonitor.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/ReleaseCurrentMonitor.java new file mode 100644 index 0000000..bd3efdb --- /dev/null +++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/ReleaseCurrentMonitor.java @@ -0,0 +1,38 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.xml.XmlNode; +import com.lanternsoftware.util.xml.XmlParser; +import org.apache.commons.codec.digest.DigestUtils; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; + +public class ReleaseCurrentMonitor { + public static void main(String[] args) { + XmlNode pom = XmlParser.loadXmlFile(LanternFiles.SOURCE_PATH + "currentmonitor" + File.separator + "lantern-currentmonitor" + File.separator + "pom.xml"); + if (pom == null) + return; + XmlNode versionNode = pom.getChild(Collections.singletonList("version")); + String version = versionNode.getContent(); + ProcessBuilder builder = new ProcessBuilder(); + builder.directory(new File(LanternFiles.SOURCE_PATH)); + builder.command("cmd.exe", "/c", "mvn clean install"); + builder.redirectOutput(ProcessBuilder.Redirect.INHERIT); + try { + Process process = builder.start(); + int exitCode = process.waitFor(); + assert exitCode == 0; + } catch (Exception _e) { + _e.printStackTrace(); + } + byte[] jar = ResourceLoader.loadFile(LanternFiles.SOURCE_PATH + "currentmonitor" + File.separator + "lantern-currentmonitor" + File.separator + "target" + File.separator + "lantern-currentmonitor.jar"); + DaoEntity meta = new DaoEntity("version", version).and("size", jar.length).and("checksum", DigestUtils.md5Hex(jar)); + ResourceLoader.writeFile(LanternFiles.OPS_PATH + "release" + File.separator + "lantern-currentmonitor.jar", jar); + ResourceLoader.writeFile(LanternFiles.OPS_PATH + "release" + File.separator + "version.json", DaoSerializer.toJson(meta)); + } +} diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/pom.xml b/currentmonitor/lantern-dataaccess-currentmonitor/pom.xml new file mode 100644 index 0000000..11cc06a --- /dev/null +++ b/currentmonitor/lantern-dataaccess-currentmonitor/pom.xml @@ -0,0 +1,83 @@ + + 4.0.0 + com.lanternsoftware.currentmonitor + lantern-dataaccess-currentmonitor + jar + 1.0.0 + lantern-dataaccess-currentmonitor + + + 1.8 + 1.8 + + + + + com.lanternsoftware.currentmonitor + lantern-datamodel-currentmonitor + 1.0.0 + + + com.lanternsoftware.util + lantern-util-dao-mongo + 1.0.0 + + + org.mindrot + jbcrypt + 0.4 + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + package + + jar + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.5 + + + true + + + + + + diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java new file mode 100644 index 0000000..183f783 --- /dev/null +++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java @@ -0,0 +1,45 @@ +package com.lanternsoftware.dataaccess.currentmonitor; + +import com.lanternsoftware.datamodel.currentmonitor.Account; +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; +import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode; +import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute; +import com.lanternsoftware.util.dao.mongo.MongoProxy; + +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; + +public interface CurrentMonitorDao { + void shutdown(); + + void putBreakerPower(BreakerPower _current); + List getBreakerPowerForAccount(int _accountId); + BreakerPower getLatestBreakerPower(int _accountId, int _hub, int _port); + BreakerGroupEnergy getBreakerGroupEnergy(int _accountId, String _groupId, EnergyBlockViewMode _viewMode, Date _start); + void putBreakerGroupEnergy(BreakerGroupEnergy _energy); + + void putHubPowerMinute(HubPowerMinute _power); + + BreakerConfig getConfig(int _accountId); + BreakerConfig getMergedConfig(AuthCode _authCode); + void putConfig(BreakerConfig _config); + + void updateSummaries(BreakerGroup _rootGroup, Set _daysToSummarize, TimeZone _tz); + + String authenticateAccount(String _username, String _password); + String getAuthCodeForEmail(String _email); + Account authCodeToAccount(String _authCode); + AuthCode decryptAuthCode(String _authCode); + + Account putAccount(Account _account); + Account getAccount(int _accountId); + Account getAccountByUsername(String _username); + + MongoProxy getProxy(); +} diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/DirtyMinute.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/DirtyMinute.java new file mode 100644 index 0000000..5fdac49 --- /dev/null +++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/DirtyMinute.java @@ -0,0 +1,57 @@ +package com.lanternsoftware.dataaccess.currentmonitor; + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.Date; + +@DBSerializable(autogen = false) +public class DirtyMinute { + private int accountId; + private int minute; + private Date posted; + + public DirtyMinute() { + } + + public DirtyMinute(int _accountId, int _minute, Date _posted) { + accountId = _accountId; + minute = _minute; + posted = _posted; + } + + public String getId() { + return accountId + "-" + minute; + } + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public Date getMinuteAsDate() { + return new Date(((long)minute)*60000); + } + + public int getMinute() { + return minute; + } + + public void setMinute(int _minute) { + minute = _minute; + } + + public void setMinute(Date _minute) { + minute = (int)(_minute.getTime()/60000); + } + + public Date getPosted() { + return posted; + } + + public void setPosted(Date _posted) { + posted = _posted; + } +} diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java new file mode 100644 index 0000000..6f62d6c --- /dev/null +++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java @@ -0,0 +1,273 @@ +package com.lanternsoftware.dataaccess.currentmonitor; + +import com.lanternsoftware.datamodel.currentmonitor.Account; +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.datamodel.currentmonitor.Breaker; +import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupSummary; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; +import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode; +import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute; +import com.lanternsoftware.datamodel.currentmonitor.Sequence; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.DateUtils; +import com.lanternsoftware.util.DebugTimer; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.cryptography.AESTool; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoQuery; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.DaoSort; +import com.lanternsoftware.util.dao.mongo.MongoConfig; +import com.lanternsoftware.util.dao.mongo.MongoProxy; +import org.mindrot.jbcrypt.BCrypt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class MongoCurrentMonitorDao implements CurrentMonitorDao { + private static final Logger logger = LoggerFactory.getLogger(MongoCurrentMonitorDao.class); + private static final AESTool aes = new AESTool(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "authKey.dat")); + private static final int BCRYPT_ROUNDS = 11; + private final Timer delayTimer = new Timer(); + private final ExecutorService executor = Executors.newCachedThreadPool(); + + private final MongoProxy proxy; + + public MongoCurrentMonitorDao(MongoConfig _config) { + proxy = new MongoProxy(_config); + proxy.ensureIndex(BreakerPower.class, DaoSort.sort("account_id").then("key")); + proxy.ensureIndex(HubPowerMinute.class, DaoSort.sort("account_id").then("minute")); + proxy.ensureIndex(BreakerGroupEnergy.class, DaoSort.sort("account_id").then("group_id").then("view_mode")); + proxy.ensureIndex(BreakerGroupSummary.class, DaoSort.sort("account_id").then("group_id").then("view_mode")); + proxy.ensureIndex(DirtyMinute.class, DaoSort.sort("posted")); + for (DirtyMinute minute : proxy.queryAll(DirtyMinute.class)) { + updateSummaries(minute); + } + proxy.delete(DirtyMinute.class, new DaoQuery()); + } + + public void shutdown() { + delayTimer.cancel(); + executor.shutdownNow(); + proxy.shutdown(); + } + + @Override + public void putBreakerPower(BreakerPower _power) { + proxy.save(_power); + } + + @Override + public void putHubPowerMinute(HubPowerMinute _power) { + if (_power == null) + return; + proxy.save(_power); + DirtyMinute minute = new DirtyMinute(_power.getAccountId(), _power.getMinute(), new Date()); + proxy.save(minute); + delayTimer.schedule(new TimerTask(){ + @Override + public void run() { + executor.submit(()->{ + if (proxy.queryOneAndDelete(DirtyMinute.class, new DaoQuery("_id", minute.getId())) != null) + updateSummaries(new DirtyMinute(_power.getAccountId(), _power.getMinute(), new Date())); + }); + } + }, 10000); + } + + private void updateSummaries(DirtyMinute _minute) { + DebugTimer timer = new DebugTimer("Updating summaries", logger); + List minutes = proxy.query(HubPowerMinute.class, new DaoQuery("account_id", _minute.getAccountId()).and("minute", _minute.getMinute())); + TimeZone tz = TimeZone.getTimeZone("America/Chicago"); + BreakerConfig config = getConfig(_minute.getAccountId()); + BreakerGroup group = CollectionUtils.getFirst(config.getBreakerGroups()); + Date day = DateUtils.getMidnightBefore(_minute.getMinuteAsDate(), tz); + BreakerGroupEnergy summary = getBreakerGroupEnergy(_minute.getAccountId(), group.getId(), EnergyBlockViewMode.DAY, day); + if (summary == null) + summary = new BreakerGroupEnergy(group, minutes, EnergyBlockViewMode.DAY, day, tz); + else + summary.addEnergy(group, minutes, tz); + putBreakerGroupEnergy(summary); + updateSummaries(group, CollectionUtils.asHashSet(day), tz); + timer.stop(); + } + + @Override + public List getBreakerPowerForAccount(int _accountId) { + return proxy.query(BreakerPower.class, new DaoQuery("account_id", _accountId)); + } + + @Override + public BreakerPower getLatestBreakerPower(int _accountId, int _panel, int _space) { + return proxy.queryOne(BreakerPower.class, new DaoQuery("account_id", _accountId).and("key", Breaker.key(_panel, _space)), DaoSort.sortDesc("read_time")); + } + + @Override + public BreakerGroupEnergy getBreakerGroupEnergy(int _accountId, String _groupId, EnergyBlockViewMode _viewMode, Date _start) { + return proxy.queryOne(BreakerGroupEnergy.class, new DaoQuery("_id", BreakerGroupEnergy.toId(_accountId, _groupId, _viewMode, _start))); + } + + @Override + public void updateSummaries(BreakerGroup _rootGroup, Set _daysToSummarize, TimeZone _tz) { + Set monthsToSummarize = CollectionUtils.transformToSet(_daysToSummarize, _c -> DateUtils.getStartOfMonth(_c, _tz)); + Set yearsToSummarize = CollectionUtils.transformToSet(monthsToSummarize, _c -> DateUtils.getStartOfYear(_c, _tz)); + for (Date month : monthsToSummarize) { + Calendar calDayStart = DateUtils.toCalendar(month, _tz); + Calendar end = DateUtils.getEndOfMonthCal(month, _tz); + List groupEnergyIds = new ArrayList<>(); + while (calDayStart.before(end)) { + groupEnergyIds.add(BreakerGroupEnergy.toId(_rootGroup.getAccountId(), _rootGroup.getId(), EnergyBlockViewMode.DAY, calDayStart.getTime())); + calDayStart.add(Calendar.DAY_OF_YEAR, 1); + } + List groupEnergies = CollectionUtils.aggregate(proxy.query(BreakerGroupSummary.class, DaoQuery.in("_id", groupEnergyIds)), BreakerGroupSummary::getAllGroups); + Map> energies = CollectionUtils.transformToMultiMap(groupEnergies, BreakerGroupSummary::getGroupId); + BreakerGroupEnergy summary = BreakerGroupEnergy.summary(_rootGroup, energies, EnergyBlockViewMode.MONTH, month, _tz); + putBreakerGroupEnergy(summary); + } + for (Date year : yearsToSummarize) { + Calendar calMonthStart = DateUtils.toCalendar(year, _tz); + Calendar end = DateUtils.getEndOfYearCal(year, _tz); + List groupEnergyIds = new ArrayList<>(); + while (calMonthStart.before(end)) { + groupEnergyIds.add(BreakerGroupEnergy.toId(_rootGroup.getAccountId(), _rootGroup.getId(), EnergyBlockViewMode.MONTH, calMonthStart.getTime())); + calMonthStart.add(Calendar.DAY_OF_YEAR, 1); + } + List groupEnergies = CollectionUtils.aggregate(proxy.query(BreakerGroupSummary.class, DaoQuery.in("_id", groupEnergyIds)), BreakerGroupSummary::getAllGroups); + Map> energies = CollectionUtils.transformToMultiMap(groupEnergies, BreakerGroupSummary::getGroupId); + BreakerGroupEnergy summary = BreakerGroupEnergy.summary(_rootGroup, energies, EnergyBlockViewMode.YEAR, year, _tz); + putBreakerGroupEnergy(summary); + } + List groupEnergies = CollectionUtils.aggregate(proxy.query(BreakerGroupSummary.class, new DaoQuery("group_id", _rootGroup.getId()).and("view_mode", EnergyBlockViewMode.YEAR.name())), BreakerGroupSummary::getAllGroups); + Map> energies = CollectionUtils.transformToMultiMap(groupEnergies, BreakerGroupSummary::getGroupId); + BreakerGroupEnergy summary = BreakerGroupEnergy.summary(_rootGroup, energies, EnergyBlockViewMode.ALL, new Date(0), _tz); + putBreakerGroupEnergy(summary); + } + + @Override + public void putBreakerGroupEnergy(BreakerGroupEnergy _energy) { + proxy.save(_energy); + proxy.save(new BreakerGroupSummary(_energy)); + } + + @Override + public BreakerConfig getConfig(int _accountId) { + return proxy.queryOne(BreakerConfig.class, new DaoQuery("_id", String.valueOf(_accountId))); + } + + @Override + public BreakerConfig getMergedConfig(AuthCode _authCode) { + if (_authCode == null) + return null; + List configs = CollectionUtils.transform(_authCode.getAllAccountIds(), this::getConfig, true); + BreakerConfig config = new BreakerConfig(); + config.setAccountId(_authCode.getAccountId()); + config.setBreakerHubs(CollectionUtils.aggregate(configs, BreakerConfig::getBreakerHubs)); + config.setBreakerGroups(CollectionUtils.aggregate(configs, BreakerConfig::getBreakerGroups)); + config.setPanels(CollectionUtils.aggregate(configs, BreakerConfig::getPanels)); + config.setMeters(CollectionUtils.aggregate(configs, BreakerConfig::getMeters)); + return config; + } + + @Override + public void putConfig(BreakerConfig _config) { + DaoQuery configQuery = new DaoQuery("_id", String.valueOf(_config.getAccountId())); + BreakerConfig oldConfig = proxy.queryOne(BreakerConfig.class, configQuery); + if (oldConfig != null) + proxy.delete(BreakerGroup.class, DaoQuery.in("_id", oldConfig.getAllBreakerGroupIds())); + proxy.save(_config); + } + + @Override + public String authenticateAccount(String _username, String _password) { + Account acct = proxy.queryOne(Account.class, new DaoQuery("username", _username)); + if ((acct == null) || !BCrypt.checkpw(_password, acct.getPassword())) + return null; + return aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(acct.getId(), acct.getAuxiliaryAccountIds()))); + } + + @Override + public Account authCodeToAccount(String _authCode) { + AuthCode code = decryptAuthCode(_authCode); + if (code == null) + return null; + return proxy.queryOne(Account.class, new DaoQuery("_id", code.getAccountId())); + } + + @Override + public AuthCode decryptAuthCode(String _authCode) { + return DaoSerializer.fromZipBson(aes.decryptFromBase64(_authCode), AuthCode.class); + } + + @Override + public String getAuthCodeForEmail(String _email) { + _email = _email.toLowerCase().trim(); + Account account = getAccountByUsername(_email); + if (account == null) { + account = new Account(); + account.setUsername(_email); + putAccount(account); + } + return aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(account.getId(), account.getAuxiliaryAccountIds()))); + } + + @Override + public Account putAccount(Account _account) { + if (_account == null) + return null; + _account.setUsername(NullUtils.makeNotNull(_account.getUsername()).toLowerCase().trim()); + Account account = getAccountByUsername(_account.getUsername()); + if (account != null) { + _account.setId(account.getId()); + if (NullUtils.isEmpty(_account.getPassword())) + _account.setPassword(account.getPassword()); + else + _account.setPassword(BCrypt.hashpw(_account.getPassword(), BCrypt.gensalt(BCRYPT_ROUNDS))); + } + else if (NullUtils.isNotEmpty(_account.getPassword())) { + _account.setPassword(BCrypt.hashpw(_account.getPassword(), BCrypt.gensalt(BCRYPT_ROUNDS))); + } + if (_account.getId() == 0) + _account.setId(proxy.updateOne(Sequence.class, null, new DaoEntity("$inc", new DaoEntity("sequence", 1))).getSequence()); + proxy.save(_account); + return clearPassword(_account); + } + + @Override + public Account getAccount(int _accountId) { + return clearPassword(proxy.queryOne(Account.class, new DaoQuery("_id", String.valueOf(_accountId)))); + } + + @Override + public Account getAccountByUsername(String _username) { + return clearPassword(proxy.queryOne(Account.class, new DaoQuery("username", NullUtils.makeNotNull(_username).toLowerCase().trim()))); + } + + private Account clearPassword(Account _account) { + if (_account == null) + return null; + _account.setPassword(null); + return _account; + } + + @Override + public MongoProxy getProxy() { + return proxy; + } +} diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/dao/DirtyMinuteSerializer.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/dao/DirtyMinuteSerializer.java new file mode 100644 index 0000000..ffe6c9e --- /dev/null +++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/dao/DirtyMinuteSerializer.java @@ -0,0 +1,44 @@ +package com.lanternsoftware.dataaccess.currentmonitor.dao; + +import com.lanternsoftware.dataaccess.currentmonitor.DirtyMinute; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class DirtyMinuteSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return DirtyMinute.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(DirtyMinute _o) + { + DaoEntity d = new DaoEntity(); + d.put("_id", _o.getId()); + d.put("account_id", _o.getAccountId()); + d.put("minute", _o.getMinute()); + d.put("posted", DaoSerializer.toLong(_o.getPosted())); + return d; + } + + @Override + public DirtyMinute fromDaoEntity(DaoEntity _d) + { + DirtyMinute o = new DirtyMinute(); + o.setAccountId(DaoSerializer.getInteger(_d, "account_id")); + o.setMinute(DaoSerializer.getInteger(_d, "minute")); + o.setPosted(DaoSerializer.getDate(_d, "posted")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer new file mode 100644 index 0000000..dfb31c6 --- /dev/null +++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer @@ -0,0 +1 @@ +com.lanternsoftware.dataaccess.currentmonitor.dao.DirtyMinuteSerializer diff --git a/currentmonitor/lantern-datamodel-currentmonitor/pom.xml b/currentmonitor/lantern-datamodel-currentmonitor/pom.xml new file mode 100644 index 0000000..cf72be0 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/pom.xml @@ -0,0 +1,73 @@ + + 4.0.0 + com.lanternsoftware.currentmonitor + lantern-datamodel-currentmonitor + jar + 1.0.0 + lantern-datamodel-currentmonitor + + + 1.8 + 1.8 + + + + + com.lanternsoftware.util + lantern-util-dao + 1.0.0 + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + package + + jar + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.5 + + + true + + + + + + diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java new file mode 100644 index 0000000..a418213 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java @@ -0,0 +1,46 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; + +import java.util.List; + +@DBSerializable(autogen = false) +public class Account { + @PrimaryKey private int id; + private String username; + private String password; + private List auxiliaryAccountIds; + + public int getId() { + return id; + } + + public void setId(int _id) { + id = _id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String _username) { + username = _username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String _password) { + password = _password; + } + + public List getAuxiliaryAccountIds() { + return auxiliaryAccountIds; + } + + public void setAuxiliaryAccountIds(List _auxiliaryAccountIds) { + auxiliaryAccountIds = _auxiliaryAccountIds; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/AuthCode.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/AuthCode.java new file mode 100644 index 0000000..fd5bf04 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/AuthCode.java @@ -0,0 +1,49 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.ArrayList; +import java.util.List; + +@DBSerializable(autogen = false) +public class AuthCode { + private int accountId; + private List auxiliaryAccountIds; + + public AuthCode() { + } + + public AuthCode(int _accountId, List _auxiliaryAccountIds) { + accountId = _accountId; + auxiliaryAccountIds = _auxiliaryAccountIds; + } + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public List getAuxiliaryAccountIds() { + return auxiliaryAccountIds; + } + + public void setAuxiliaryAccountIds(List _auxiliaryAccountIds) { + auxiliaryAccountIds = _auxiliaryAccountIds; + } + + public List getAllAccountIds() { + List ids = new ArrayList<>(); + ids.add(accountId); + if (auxiliaryAccountIds != null) + ids.addAll(auxiliaryAccountIds); + return ids; + } + + public boolean isAuthorized(int _accountId) { + return accountId == _accountId || CollectionUtils.contains(auxiliaryAccountIds, _accountId); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Breaker.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Breaker.java new file mode 100644 index 0000000..ba456bd --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Breaker.java @@ -0,0 +1,232 @@ +package com.lanternsoftware.datamodel.currentmonitor; + + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +@DBSerializable() +public class Breaker { + private static final int TANDEM_BREAKER_MASK = 3072; + private static final int SPACE_MASK = 1023; + private static final int TANDEM_BREAKER_A_MASK = 1024; + private static final int TANDEM_BREAKER_B_MASK = 2048; + + private int panel; + private int space; + private int meter; + private int hub; + private int port; + private String name; + private String description; + private int sizeAmps; + private double calibrationFactor; + private double lowPassFilter; + private BreakerPolarity polarity; + private BreakerType type; + private transient String key; + + public Breaker() { + } + + public Breaker(String _name, int _panel, int _space, int _hub, int _port, int _sizeAmps, double _lowPassFilter) { + name = _name; + panel = _panel; + space = _space; + hub = _hub; + port = _port; + sizeAmps = _sizeAmps; + lowPassFilter = _lowPassFilter; + } + + public int getPanel() { + return panel; + } + + public void setPanel(int _panel) { + panel = _panel; + key = null; + } + + public int getSpace() { + return space; + } + + public void setSpace(int _space) { + space = _space; + key = null; + } + + public int getMeter() { + return meter; + } + + public void setMeter(int _meter) { + meter = _meter; + } + + public String getSpaceDisplay() { + return toSpaceDisplay(space); + } + + public void setSpaceTandemA(int _space) { + space = TANDEM_BREAKER_A_MASK | _space; + } + + public void setSpaceTandemB(int _space) { + space = TANDEM_BREAKER_B_MASK | _space; + } + + public boolean isTandemBreaker() { + return (TANDEM_BREAKER_MASK & space) != 0; + } + + public boolean isTandemBreakerA() { + return isTandemBreakerA(space); + } + + public boolean isTandemBreakerB() { + return isTandemBreakerB(space); + } + + public String getName() { + return name; + } + + public void setName(String _name) { + name = _name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String _description) { + description = _description; + } + + public int getHub() { + return hub; + } + + public void setHub(int _hub) { + hub = _hub; + } + + public int getPort() { + return port; + } + + public void setPort(int _port) { + port = _port; + } + + public int getChip() { + return portToChip(port); + } + + public int getPin() { + return portToPin(port); + } + + public int getSizeAmps() { + return sizeAmps; + } + + public void setSizeAmps(int _sizeAmps) { + sizeAmps = _sizeAmps; + } + + public double getLowPassFilter() { + return lowPassFilter; + } + + public void setLowPassFilter(double _lowPassFilter) { + lowPassFilter = _lowPassFilter; + } + + public BreakerPolarity getPolarity() { + return polarity; + } + + public void setPolarity(BreakerPolarity _polarity) { + polarity = _polarity; + } + + public double getCalibrationFactor() { + return calibrationFactor == 0.0?1.0:calibrationFactor; + } + + public void setCalibrationFactor(double _calibrationFactor) { + calibrationFactor = _calibrationFactor; + } + + public BreakerType getType() { + if (type == null) { + if (isTandemBreaker()) + return BreakerType.SINGLE_POLE_TANDEM; + return BreakerType.SINGLE_POLE; + } + return type; + } + + public void setType(BreakerType _type) { + type = _type; + } + + public double getFinalCalibrationFactor() { + return getCalibrationFactor() * getSizeAmps() / 380.0; + } + + public String getKey() { + if (key == null) + key = key(panel, space); + return key; + } + + public static String key(int _panel, int _space) { + return String.format("%d-%d", _panel, _space); + } + + public static int portToChip(int _port) { + return (_port < 9)?1:0; + } + + public static int portToPin(int _port) { + return (_port < 9)?_port-1:_port-8; + } + + public static int toPort(int _chip, int _pin) { + return (_chip == 0)?_pin+8:_pin+1; + } + + public static boolean isTandemBreakerA(int _space) { + return (TANDEM_BREAKER_A_MASK & _space) != 0; + } + + public static boolean isTandemBreakerB(int _space) { + return (TANDEM_BREAKER_B_MASK & _space) != 0; + } + + public static int toId(int _panel, int _space) { + return (_panel << 12) | _space; + } + + public static int toPanel(int _id) { + return _id >> 12; + } + + public static int toSpace(int _id) { + return _id & (TANDEM_BREAKER_MASK |SPACE_MASK); + } + + public static String toSpaceDisplay(int _space) { + if (isTandemBreakerA(_space)) + return String.format("%dA", _space & SPACE_MASK); + if (isTandemBreakerB(_space)) + return String.format("%dB", _space & SPACE_MASK); + return String.valueOf(_space); + } + + public int getSpaceIndex() { + return space & SPACE_MASK; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerConfig.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerConfig.java new file mode 100644 index 0000000..9452743 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerConfig.java @@ -0,0 +1,162 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; + +import java.util.ArrayList; +import java.util.List; + +@DBSerializable(autogen = false) +public class BreakerConfig { + @PrimaryKey + private int accountId; + private List meters; + private List panels; + private List breakerHubs; + private List breakerGroups; + + public BreakerConfig() { + } + + public BreakerConfig(List _breakerGroups) { + breakerGroups = _breakerGroups; + } + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public List getMeters() { + return meters; + } + + public void setMeters(List _meters) { + meters = _meters; + } + + public List getPanels() { + return panels; + } + + public void setPanels(List _panels) { + panels = _panels; + } + + public List getBreakerHubs() { + return breakerHubs; + } + + public void setBreakerHubs(List _breakerHubs) { + breakerHubs = _breakerHubs; + } + + public List getBreakerGroups() { + return breakerGroups; + } + + public void setBreakerGroups(List _breakerGroups) { + breakerGroups = _breakerGroups; + } + + public List getAllBreakers() { + List allBreakers = new ArrayList<>(); + for (BreakerGroup g : CollectionUtils.makeNotNull(breakerGroups)) { + allBreakers.addAll(g.getAllBreakers()); + } + return allBreakers; + } + + public List getAllBreakerGroups() { + List groups = new ArrayList<>(); + for (BreakerGroup g : CollectionUtils.makeNotNull(breakerGroups)) { + groups.addAll(g.getAllBreakerGroups()); + } + return groups; + } + + public List getAllBreakerGroupIds() { + List ids = new ArrayList<>(); + for (BreakerGroup g : CollectionUtils.makeNotNull(breakerGroups)) { + ids.addAll(g.getAllBreakerGroupIds()); + } + return ids; + } + + public List getBreakersForHub(int _hub) { + return CollectionUtils.filter(getAllBreakers(), _b -> _b.getHub() == _hub); + } + + public BreakerHub getHub(int _hub) { + return CollectionUtils.filterOne(breakerHubs, _h->_h.getHub() == _hub); + } + + public boolean isSolarConfigured() { + return CollectionUtils.anyQualify(getAllBreakers(), _b->_b.getPolarity() == BreakerPolarity.SOLAR); + } + + public String nextGroupId() { + List ids = CollectionUtils.transform(getAllBreakerGroupIds(), NullUtils::toInteger); + return String.valueOf(CollectionUtils.getLargest(ids) + 1); + } + + public void addGroup(BreakerGroup _group) { + if (NullUtils.isEmpty(_group.getId())) { + _group.setId(nextGroupId()); + } + if (breakerGroups == null) + breakerGroups = new ArrayList<>(); + breakerGroups.add(_group); + } + + public void removeInvalidGroups() { + if (breakerGroups != null) + breakerGroups.removeIf(_g->!_g.removeInvalidGroups()); + } + + public String getGroupIdForBreaker(Breaker _breaker) { + return getGroupIdForBreaker(_breaker.getKey()); + } + + public String getGroupIdForBreaker(int _panel, int _space) { + return getGroupIdForBreaker(Breaker.key(_panel, _space)); + } + + public String getGroupIdForBreaker(String _breakerKey) { + BreakerGroup group = getGroupForBreaker(_breakerKey); + return group != null ? group.getId() : null; + } + + public BreakerGroup getGroupForBreaker(Breaker _breaker) { + return getGroupForBreaker(_breaker.getKey()); + } + + public BreakerGroup getGroupForBreaker(int _panel, int _space) { + return getGroupForBreaker(Breaker.key(_panel, _space)); + } + + public BreakerGroup getGroupForBreaker(String _breakerKey) { + if (_breakerKey == null) + return null; + for (BreakerGroup subGroup : CollectionUtils.makeNotNull(breakerGroups)) { + BreakerGroup group = subGroup.getGroupForBreaker(_breakerKey); + if (group != null) + return group; + } + return null; + } + + public BreakerGroup findParentGroup(BreakerGroup _group) { + for (BreakerGroup group : CollectionUtils.makeNotNull(breakerGroups)) { + BreakerGroup parent = group.findParentGroup(_group); + if (parent != null) + return parent; + } + return null; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroup.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroup.java new file mode 100644 index 0000000..654411b --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroup.java @@ -0,0 +1,192 @@ +package com.lanternsoftware.datamodel.currentmonitor; + + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +@DBSerializable() +public class BreakerGroup { + @PrimaryKey private String id; + private int accountId; + private String name; + private List subGroups; + private List breakers; + + public BreakerGroup() { + } + + public BreakerGroup(String _id, String _name, List _breakers) { + id = _id; + name = _name; + breakers = _breakers; + } + + public BreakerGroup(String _id, String _name, List _subGroups, List _breakers) { + id = _id; + name = _name; + subGroups = _subGroups; + breakers = _breakers; + } + + public String getId() { + return id; + } + + public void setId(String _id) { + id = _id; + } + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public String getName() { + return name; + } + + public void setName(String _name) { + name = _name; + } + + public List getSubGroups() { + return subGroups; + } + + public void setSubGroups(List _subGroups) { + subGroups = _subGroups; + } + + public List getBreakers() { + return breakers; + } + + public void setBreakers(List _breakers) { + breakers = _breakers; + } + + public List getAllBreakers() { + List allBreakers = new ArrayList<>(); + getAllBreakers(allBreakers); + return allBreakers; + } + + private void getAllBreakers(List _breakers) { + if (breakers != null) + _breakers.addAll(breakers); + for (BreakerGroup group : CollectionUtils.makeNotNull(subGroups)) { + group.getAllBreakers(_breakers); + } + } + + public List getAllBreakerKeys() { + return CollectionUtils.transform(getAllBreakers(), Breaker::getKey); + } + + public List getAllBreakerGroups() { + List allGroups = new ArrayList<>(); + getAllBreakerGroups(allGroups); + return allGroups; + } + + private void getAllBreakerGroups(List _groups) { + _groups.add(this); + for (BreakerGroup group : CollectionUtils.makeNotNull(subGroups)) { + group.getAllBreakerGroups(_groups); + } + } + + public List getAllBreakerGroupIds() { + return CollectionUtils.transform(getAllBreakerGroups(), BreakerGroup::getId); + } + + public Breaker getBreaker(String _breakerKey) { + for (Breaker b : CollectionUtils.makeNotNull(breakers)) { + if (NullUtils.isEqual(b.getKey(), _breakerKey)) + return b; + } + for (BreakerGroup group : CollectionUtils.makeNotNull(subGroups)) { + Breaker b = group.getBreaker(_breakerKey); + if (b != null) + return b; + } + return null; + } + + public String getGroupIdForBreaker(Breaker _breaker) { + return getGroupIdForBreaker(_breaker.getKey()); + } + + public String getGroupIdForBreaker(int _panel, int _space) { + return getGroupIdForBreaker(Breaker.key(_panel, _space)); + } + + public String getGroupIdForBreaker(String _breakerKey) { + BreakerGroup group = getGroupForBreaker(_breakerKey); + return group != null ? group.getId() : null; + } + + public BreakerGroup getGroupForBreaker(Breaker _breaker) { + return getGroupForBreaker(_breaker.getKey()); + } + + public BreakerGroup getGroupForBreaker(int _panel, int _space) { + return getGroupForBreaker(Breaker.key(_panel, _space)); + } + + public BreakerGroup getGroupForBreaker(String _breakerKey) { + if (_breakerKey == null) + return null; + Breaker b = CollectionUtils.filterOne(breakers, _b->_breakerKey.equals(_b.getKey())); + if (b != null) + return this; + for (BreakerGroup subGroup : CollectionUtils.makeNotNull(subGroups)) { + BreakerGroup group = subGroup.getGroupForBreaker(_breakerKey); + if (group != null) + return group; + } + return null; + } + + public BreakerGroup findParentGroup(BreakerGroup _group) { + if (CollectionUtils.contains(subGroups, _group)) + return this; + for (BreakerGroup subGroup : CollectionUtils.makeNotNull(subGroups)) { + BreakerGroup parent = subGroup.findParentGroup(_group); + if (parent != null) + return parent; + } + return null; + } + + public boolean removeInvalidGroups() { + if (subGroups != null) + subGroups.removeIf(_g->!_g.removeInvalidGroups()); + if (breakers != null) + breakers.removeIf(_b->(_b.getType() == null) || (_b.getType() == BreakerType.EMPTY) || (_b.getPort() < 1)); + return CollectionUtils.isNotEmpty(subGroups) || CollectionUtils.isNotEmpty(breakers); + } + + @Override + public boolean equals(Object _o) { + if (this == _o) return true; + if (_o == null || getClass() != _o.getClass()) return false; + BreakerGroup that = (BreakerGroup) _o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java new file mode 100644 index 0000000..943bc64 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java @@ -0,0 +1,337 @@ +package com.lanternsoftware.datamodel.currentmonitor; + + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeMap; + +@DBSerializable(autogen = false) +public class BreakerGroupEnergy { + private int accountId; + private String groupId; + private String groupName; + private EnergyBlockViewMode viewMode; + private Date start; + private List subGroups; + private List energyBlocks; + private double toGrid; + private double fromGrid; + + public BreakerGroupEnergy() { + } + + public BreakerGroupEnergy(BreakerGroup _group, Map> _powerReadings, EnergyBlockViewMode _viewMode, Date _start, TimeZone _tz) { + groupId = _group.getId(); + groupName = _group.getName(); + viewMode = _viewMode; + start = _start; + accountId = _group.getAccountId(); + subGroups = CollectionUtils.transform(_group.getSubGroups(), _g -> new BreakerGroupEnergy(_g, _powerReadings, _viewMode, _start, _tz)); + energyBlocks = new ArrayList<>(); + List breakerKeys = CollectionUtils.transform(_group.getBreakers(), Breaker::getKey); + if (!breakerKeys.isEmpty()) { + for (BreakerPower power : CollectionUtils.aggregate(breakerKeys, _powerReadings::get)) { + addEnergy(groupId, power.getReadTime(), power.getPower(), _tz); + } + } + } + + public BreakerGroupEnergy(BreakerGroup _group, List _power, EnergyBlockViewMode _viewMode, Date _start, TimeZone _tz) { + groupId = _group.getId(); + groupName = _group.getName(); + viewMode = _viewMode; + start = _start; + accountId = _group.getAccountId(); + subGroups = CollectionUtils.transform(_group.getSubGroups(), _g -> new BreakerGroupEnergy(_g, (List)null, _viewMode, _start, _tz)); + energyBlocks = new ArrayList<>(); + addEnergy(_group, _power, _tz); + } + + public void addEnergy(BreakerGroup _group, List _hubPower, TimeZone _tz) { + Map breakers = CollectionUtils.transformToMap(_group.getAllBreakers(), Breaker::getKey); + Map breakerKeyToGroup = new HashMap<>(); + for (BreakerGroup group : _group.getAllBreakerGroups()) { + for (Breaker b : group.getAllBreakers()) { + breakerKeyToGroup.put(b.getKey(), group); + } + } + addEnergy(breakers, breakerKeyToGroup, _hubPower, _tz); + } + + public void addEnergy(Map _breakers, Map _breakerKeyToGroup, List _hubPower, TimeZone _tz) { + if (CollectionUtils.isEmpty(_hubPower) || CollectionUtils.anyQualify(_hubPower, _p->_p.getAccountId() != accountId)) + return; + Date minute = CollectionUtils.getFirst(_hubPower).getMinuteAsDate(); + resetEnergy(minute, _tz); + Map meters = new HashMap<>(); + for (HubPowerMinute hubPower : _hubPower) { + for (BreakerPowerMinute breaker : CollectionUtils.makeNotNull(hubPower.getBreakers())) { + Breaker b = _breakers.get(breaker.breakerKey()); + if (b == null) + continue; + BreakerGroup group = _breakerKeyToGroup.get(breaker.breakerKey()); + if (group == null) + continue; + MeterMinute meter = meters.computeIfAbsent(b.getMeter(), _p->new MeterMinute()); + int idx = 0; + for (Float power : CollectionUtils.makeNotNull(breaker.getReadings())) { + if (power > 0) + meter.usage[idx] += power; + else + meter.solar[idx] += -power; + if (power != 0.0) + addEnergy(group.getId(), minute, power, _tz); + idx++; + } + } + } + + for (MeterMinute meter : meters.values()) { + for (int i = 0; i < 60; i++) { + if (meter.usage[i] > meter.solar[i]) + fromGrid += meter.usage[i] - meter.solar[i]; + else + toGrid += meter.solar[i] - meter.usage[i]; + } + } + } + + public void resetEnergy(Date _readTime, TimeZone _tz) { + EnergyBlock block = getBlock(_readTime, _tz, false); + if (block != null) + block.setJoules(0); + for (BreakerGroupEnergy subGroup : CollectionUtils.makeNotNull(subGroups)) { + subGroup.resetEnergy(_readTime, _tz); + } + } + + public void addEnergy(String _groupId, Date _readTime, double _joules, TimeZone _tz) { + if (NullUtils.isEqual(groupId, _groupId)) + getBlock(_readTime, _tz).addJoules(_joules); + else { + for (BreakerGroupEnergy subGroup : CollectionUtils.makeNotNull(subGroups)) { + subGroup.addEnergy(_groupId, _readTime, _joules, _tz); + } + } + } + + public static BreakerGroupEnergy summary(BreakerGroup _group, Map> _energies, EnergyBlockViewMode _viewMode, Date _start, TimeZone _tz) { + BreakerGroupEnergy energy = new BreakerGroupEnergy(); + energy.setGroupId(_group.getId()); + energy.setGroupName(_group.getName()); + energy.setAccountId(_group.getAccountId()); + energy.setViewMode(_viewMode); + energy.setStart(_start); + energy.setSubGroups(CollectionUtils.transform(_group.getSubGroups(), _g -> BreakerGroupEnergy.summary(_g, _energies, _viewMode, _start, _tz))); + for (BreakerGroupSummary curEnergy : CollectionUtils.makeNotNull(_energies.get(_group.getId()))) { + EnergyBlock block = energy.getBlock(curEnergy.getStart(), _tz); + block.addJoules(curEnergy.getJoules()); + energy.setToGrid(energy.getToGrid()+curEnergy.getToGrid()); + energy.setFromGrid(energy.getFromGrid()+curEnergy.getFromGrid()); + } + return energy; + } + + private EnergyBlock getBlock(Date _readTime, TimeZone _tz) { + return getBlock(_readTime, _tz, true); + } + + private EnergyBlock getBlock(Date _readTime, TimeZone _tz, boolean _add) { + int size = CollectionUtils.size(energyBlocks); + int idx = viewMode.blockIndex(_readTime, _tz); + if (_add && (idx >= size)) { + if (energyBlocks == null) + energyBlocks = new ArrayList<>(); + LinkedList newBlocks = new LinkedList<>(); + Date end = viewMode.toBlockEnd(_readTime, _tz); + while (idx >= size) { + Date start = viewMode.decrementBlock(end, _tz); + newBlocks.add(new EnergyBlock(start, end, 0)); + end = start; + size++; + } + Iterator iter = newBlocks.descendingIterator(); + while (iter.hasNext()) { + energyBlocks.add(iter.next()); + } + } + return CollectionUtils.get(energyBlocks, idx); + } + + public String getId() { + return toId(accountId, groupId, viewMode, start); + } + + public static String toId(int _accountId, String _groupId, EnergyBlockViewMode _viewMode, Date _start) { + return _accountId + "-" + _groupId + "-" + DaoSerializer.toEnumName(_viewMode) + "-" + _start.getTime(); + } + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String _groupId) { + groupId = _groupId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String _groupName) { + groupName = _groupName; + } + + public BreakerGroupEnergy getSubGroup(String _groupId) { + return CollectionUtils.filterOne(subGroups, _g->_groupId.equals(_g.getGroupId())); + } + + public List getSubGroups() { + return subGroups; + } + + public EnergyBlockViewMode getViewMode() { + return viewMode; + } + + public void setViewMode(EnergyBlockViewMode _viewMode) { + viewMode = _viewMode; + } + + public Date getStart() { + return start; + } + + public void setStart(Date _start) { + start = _start; + } + + public void setSubGroups(List _subGroups) { + subGroups = _subGroups; + } + + public List getEnergyBlocks() { + return energyBlocks; + } + + public void setEnergyBlocks(List _energyBlocks) { + energyBlocks = _energyBlocks; + } + + public double getToGrid() { + return toGrid; + } + + public void setToGrid(double _toGrid) { + toGrid = _toGrid; + } + + public double getFromGrid() { + return fromGrid; + } + + public void setFromGrid(double _fromGrid) { + fromGrid = _fromGrid; + } + + public double wattHours() { + return joules() / 3600; + } + + public double wattHours(Set _selectedBreakers) { + return joules(_selectedBreakers) / 3600; + } + + public double joules() { + return joules(null); + } + + public double joules(Set _selectedBreakers) { + return joules(_selectedBreakers, true); + } + + public double joules(Set _selectedBreakers, boolean _includeSubgroups) { + double joules = 0.0; + if (_includeSubgroups) { + for (BreakerGroupEnergy group : CollectionUtils.makeNotNull(subGroups)) { + joules += group.joules(_selectedBreakers); + } + } + if ((energyBlocks != null) && ((_selectedBreakers == null) || _selectedBreakers.contains(getGroupId()))) { + for (EnergyBlock energy : energyBlocks) { + joules += energy.getJoules(); + } + } + return joules; + } + + public List getAllGroups() { + Map groups = new TreeMap<>(); + getAllGroups(groups); + return new ArrayList<>(groups.values()); + } + + public void getAllGroups(Map _groups) { + _groups.put(getGroupId(), this); + for (BreakerGroupEnergy group : CollectionUtils.makeNotNull(subGroups)) { + group.getAllGroups(_groups); + } + } + + public List getAllEnergyBlocks() { + return getAllEnergyBlocks(null); + } + + public List getAllEnergyBlocks(Set _selectedGroups) { + return getAllEnergyBlocks(_selectedGroups, EnergyBlockType.ANY); + } + + public List getAllEnergyBlocks(Set _selectedGroups, EnergyBlockType _type) { + Map blocks = new TreeMap<>(); + getAllEnergyBlocks(_selectedGroups, blocks, _type); + return new ArrayList<>(blocks.values()); + } + + private void getAllEnergyBlocks(Set _selectedGroups, Map _energyBlocks, EnergyBlockType _type) { + if ((energyBlocks != null) && ((_selectedGroups == null) || _selectedGroups.contains(getGroupId()))) { + for (EnergyBlock block : energyBlocks) { + if ((_type == EnergyBlockType.ANY) || ((_type == EnergyBlockType.POSITIVE) && block.getJoules() >= 0.0) || ((_type == EnergyBlockType.NEGATIVE) && block.getJoules() <= 0.0)) { + EnergyBlock b = _energyBlocks.get(block.getStart().getTime()); + if (b == null) { + b = new EnergyBlock(block.getStart(), block.getEnd(), block.getJoules()); + _energyBlocks.put(block.getStart().getTime(), b); + } else + b.addJoules(block.getJoules()); + } + } + } + for (BreakerGroupEnergy group : CollectionUtils.makeNotNull(subGroups)) { + group.getAllEnergyBlocks(_selectedGroups, _energyBlocks, _type); + } + } + + private static class MeterMinute { + public double[] usage = new double[60]; + public double[] solar = new double[60]; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupSummary.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupSummary.java new file mode 100644 index 0000000..b881da2 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupSummary.java @@ -0,0 +1,139 @@ +package com.lanternsoftware.datamodel.currentmonitor; + + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +@DBSerializable(autogen = false) +public class BreakerGroupSummary { + private int accountId; + private String groupId; + private String groupName; + private EnergyBlockViewMode viewMode; + private Date start; + private List subGroups; + private double joules; + private double toGrid; + private double fromGrid; + + public BreakerGroupSummary() { + } + + public BreakerGroupSummary(BreakerGroupEnergy _energy) { + accountId = _energy.getAccountId(); + groupId = _energy.getGroupId(); + groupName = _energy.getGroupName(); + viewMode = _energy.getViewMode(); + start = _energy.getStart(); + subGroups = CollectionUtils.transform(_energy.getSubGroups(), BreakerGroupSummary::new); + joules = _energy.joules(null, false); + toGrid = _energy.getToGrid(); + fromGrid = _energy.getFromGrid(); + } + + public String getId() { + return toId(accountId, groupId, viewMode, start); + } + + public static String toId(int _accountId, String _groupId, EnergyBlockViewMode _viewMode, Date _start) { + return _accountId + "-" + _groupId + "-" + DaoSerializer.toEnumName(_viewMode) + "-" + _start.getTime(); + } + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String _groupId) { + groupId = _groupId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String _groupName) { + groupName = _groupName; + } + + public BreakerGroupSummary getSubGroup(String _groupId) { + return CollectionUtils.filterOne(subGroups, _g->_groupId.equals(_g.getGroupId())); + } + + public List getSubGroups() { + return subGroups; + } + + public EnergyBlockViewMode getViewMode() { + return viewMode; + } + + public void setViewMode(EnergyBlockViewMode _viewMode) { + viewMode = _viewMode; + } + + public Date getStart() { + return start; + } + + public void setStart(Date _start) { + start = _start; + } + + public void setSubGroups(List _subGroups) { + subGroups = _subGroups; + } + + public double getJoules() { + return joules; + } + + public void setJoules(double _joules) { + joules = _joules; + } + + public double getToGrid() { + return toGrid; + } + + public void setToGrid(double _toGrid) { + toGrid = _toGrid; + } + + public double getFromGrid() { + return fromGrid; + } + + public void setFromGrid(double _fromGrid) { + fromGrid = _fromGrid; + } + + public List getAllGroups() { + Map groups = new TreeMap<>(); + getAllGroups(groups); + return new ArrayList<>(groups.values()); + } + + public void getAllGroups(Map _groups) { + if (NullUtils.isNotEmpty(getGroupId())) + _groups.put(getGroupId(), this); + for (BreakerGroupSummary group : CollectionUtils.makeNotNull(subGroups)) { + group.getAllGroups(_groups); + } + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerHub.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerHub.java new file mode 100644 index 0000000..7c9a51a --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerHub.java @@ -0,0 +1,44 @@ +package com.lanternsoftware.datamodel.currentmonitor; + + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +@DBSerializable +public class BreakerHub { + private int hub; + private double voltageCalibrationFactor; + private int frequency; + private String bluetoothMac; + + public int getHub() { + return hub; + } + + public void setHub(int _hub) { + hub = _hub; + } + + public double getVoltageCalibrationFactor() { + return voltageCalibrationFactor; + } + + public void setVoltageCalibrationFactor(double _voltageCalibrationFactor) { + voltageCalibrationFactor = _voltageCalibrationFactor; + } + + public int getFrequency() { + return frequency; + } + + public void setFrequency(int _frequency) { + frequency = _frequency; + } + + public String getBluetoothMac() { + return bluetoothMac; + } + + public void setBluetoothMac(String _bluetoothMac) { + bluetoothMac = _bluetoothMac; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPanel.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPanel.java new file mode 100644 index 0000000..a24ac82 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPanel.java @@ -0,0 +1,52 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +@DBSerializable +public class BreakerPanel { + private int accountId; + private String name; + private int index; + private int spaces; + private int meter; + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public String getName() { + return name; + } + + public void setName(String _name) { + name = _name; + } + + public int getIndex() { + return index; + } + + public void setIndex(int _index) { + index = _index; + } + + public int getSpaces() { + return spaces; + } + + public void setSpaces(int _spaces) { + spaces = _spaces; + } + + public int getMeter() { + return meter; + } + + public void setMeter(int _meter) { + meter = _meter; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPolarity.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPolarity.java new file mode 100644 index 0000000..13fe60b --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPolarity.java @@ -0,0 +1,6 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +public enum BreakerPolarity { + NORMAL, + SOLAR; +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPower.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPower.java new file mode 100644 index 0000000..66af06f --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPower.java @@ -0,0 +1,92 @@ +package com.lanternsoftware.datamodel.currentmonitor; + + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.Date; + +@DBSerializable(autogen = false) +public class BreakerPower { + private int accountId; + private int panel; + private int space; + private Date readTime; + private String hubVersion; + private double power; + private double voltage; + + public BreakerPower() { + } + + public BreakerPower(int _panel, int _space, Date _readTime, double _power, double _voltage) { + panel = _panel; + space = _space; + readTime = _readTime; + power = _power; + voltage = _voltage; + } + + public String getId() { + return String.format("%d-%d-%d", accountId, panel, space); + } + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public int getPanel() { + return panel; + } + + public void setPanel(int _panel) { + panel = _panel; + } + + public int getSpace() { + return space; + } + + public void setSpace(int _space) { + space = _space; + } + + public Date getReadTime() { + return readTime; + } + + public void setReadTime(Date _readTime) { + readTime = _readTime; + } + + public String getHubVersion() { + return hubVersion; + } + + public void setHubVersion(String _hubVersion) { + hubVersion = _hubVersion; + } + + public double getPower() { + return power; + } + + public void setPower(double _power) { + power = _power; + } + + public double getVoltage() { + return voltage; + } + + public void setVoltage(double _voltage) { + voltage = _voltage; + } + + public String getKey() { + return Breaker.key(panel, space); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPowerMinute.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPowerMinute.java new file mode 100644 index 0000000..a995e2b --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPowerMinute.java @@ -0,0 +1,40 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.List; + +@DBSerializable(autogen = false) +public class BreakerPowerMinute { + private int panel; + private int space; + private List readings; + + public int getPanel() { + return panel; + } + + public void setPanel(int _panel) { + panel = _panel; + } + + public int getSpace() { + return space; + } + + public void setSpace(int _space) { + space = _space; + } + + public String breakerKey() { + return Breaker.key(panel, space); + } + + public List getReadings() { + return readings; + } + + public void setReadings(List _readings) { + readings = _readings; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerType.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerType.java new file mode 100644 index 0000000..c07b03a --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerType.java @@ -0,0 +1,36 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; + +import java.util.List; + +public enum BreakerType { + EMPTY("Empty"), + SINGLE_POLE("Single Pole"), + SINGLE_POLE_TANDEM("Single Pole Tandem (Two Breakers in One)"), + DOUBLE_POLE_TOP("Double Pole (240V)"), + DOUBLE_POLE_BOTTOM("Double Pole (240V)"); + + private final String display; + + BreakerType(String _display) { + display = _display; + } + + public String getDisplay() { + return display; + } + + public static List selectable() { + return CollectionUtils.asArrayList(EMPTY, SINGLE_POLE, SINGLE_POLE_TANDEM, DOUBLE_POLE_TOP); + } + + public static BreakerType fromDisplay(String _display) { + for (BreakerType type : values()) { + if (NullUtils.isEqual(_display, type.getDisplay())) + return type; + } + return null; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/CharacteristicFlag.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/CharacteristicFlag.java new file mode 100644 index 0000000..fb8cad7 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/CharacteristicFlag.java @@ -0,0 +1,31 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.CollectionUtils; + +import java.util.Collection; + +public enum CharacteristicFlag { + BROADCAST("broadcast"), + READ("read"), + WRITE_WITHOUT_RESPONSE("write-without-response"), + WRITE("write"), + NOTIFY("notify"), + INDICATE("indicate"), + AUTHENTICATED_SIGNED_WRITES("authenticated-signed-writes"), + RELIABLE_WRITE("reliable-write"), + WRITABLE_AUXILIARIES("writable-auxiliaries"), + ENCRYPT_READ("encrypt-read"), + ENCRYPT_WRITE("encrypt-write"), + ENCRYPT_AUTHENTICATED_READ("encrypt-authenticated-read"), + ENCRYPT_AUTHENTICATED_WRITE("encrypt-authenticated-write"); + + CharacteristicFlag(String _value) { + value = _value; + } + + public final String value; + + public static String[] toArray(Collection _flags) { + return CollectionUtils.transform(_flags, _c->_c.value).toArray(new String[0]); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlock.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlock.java new file mode 100644 index 0000000..188ee26 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlock.java @@ -0,0 +1,60 @@ +package com.lanternsoftware.datamodel.currentmonitor; + + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.Date; + +@DBSerializable +public class EnergyBlock { + private Date start; + private Date end; + private double joules; + + public EnergyBlock() { + } + + public EnergyBlock(Date _start, Date _end, double _joules) { + start = _start; + end = _end; + joules = _joules; + } + + public Date getStart() { + return start; + } + + public void setStart(Date _start) { + start = _start; + } + + public Date getEnd() { + return end; + } + + public void setEnd(Date _end) { + end = _end; + } + + public double getJoules() { + return joules; + } + + public void addJoules(double _joules) { + joules += _joules; + } + + public void setJoules(double _joules) { + joules = _joules; + } + + public double wattHours() { + return joules / 3600; + } + + public double getAveragePower() { + if ((end == null) || (start == null)) + return 0; + return 1000*joules/(end.getTime()-start.getTime()); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockType.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockType.java new file mode 100644 index 0000000..24eddad --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockType.java @@ -0,0 +1,7 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +public enum EnergyBlockType { + ANY, + POSITIVE, + NEGATIVE +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockViewMode.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockViewMode.java new file mode 100644 index 0000000..74f7a38 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockViewMode.java @@ -0,0 +1,144 @@ +package com.lanternsoftware.datamodel.currentmonitor; + + +import com.lanternsoftware.util.DateUtils; + +import javax.management.remote.rmi._RMIConnection_Stub; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +public enum EnergyBlockViewMode { + DAY, + MONTH, + YEAR, + ALL; + + public Date toStart(Date _dt, TimeZone _tz) { + if (this == DAY) + return DateUtils.getMidnightBefore(_dt, _tz); + if (this == MONTH) + return DateUtils.getStartOfMonth(_dt, _tz); + if (this == YEAR) + return DateUtils.getStartOfYear(_dt, _tz); + return new Date(0); + } + + public Date toEnd(Date _dt, TimeZone _tz) { + if (this == DAY) + return DateUtils.getMidnightAfter(_dt, _tz); + if (this == MONTH) + return DateUtils.getEndOfMonth(_dt, _tz); + if (this == YEAR) + return DateUtils.getEndOfYear(_dt, _tz); + return new Date(0); + } + + public Date toBlockStart(Date _dt, TimeZone _tz) { + if (this == DAY) + return DateUtils.getStartOfMinute(_dt, _tz); + if (this == MONTH) + return DateUtils.getMidnightBefore(_dt, _tz); + if (this == YEAR) + return DateUtils.getStartOfMonth(_dt, _tz); + return new Date(0); + } + + public Date toBlockEnd(Date _dt, TimeZone _tz) { + if (this == DAY) + return DateUtils.getEndOfMinute(_dt, _tz); + if (this == MONTH) + return DateUtils.getMidnightAfter(_dt, _tz); + if (this == YEAR) + return DateUtils.getEndOfMonth(_dt, _tz); + return new Date(0); + } + + public Date incrementBlock(Date _dt, TimeZone _tz) { + Calendar cal = DateUtils.toCalendar(_dt, _tz); + if (this == DAY) + cal.add(Calendar.MINUTE, 1); + else if (this == MONTH) + cal.add(Calendar.DAY_OF_YEAR, 1); + if (this == YEAR) + cal.add(Calendar.MONTH, 1); + return cal.getTime(); + } + + public Date decrementBlock(Date _dt, TimeZone _tz) { + Calendar cal = DateUtils.toCalendar(_dt, _tz); + if (this == DAY) + cal.add(Calendar.MINUTE, -1); + else if (this == MONTH) + cal.add(Calendar.DAY_OF_YEAR, -1); + if (this == YEAR) + cal.add(Calendar.MONTH, -1); + return cal.getTime(); + } + + public Date incrementView(Date _dt, TimeZone _tz) { + Calendar cal = DateUtils.toCalendar(_dt, _tz); + if (this == DAY) + cal.add(Calendar.DAY_OF_YEAR, 1); + else if (this == MONTH) + cal.add(Calendar.MONTH, 1); + if (this == YEAR) + cal.add(Calendar.YEAR, 1); + return cal.getTime(); + } + + public Date decrementView(Date _dt, TimeZone _tz) { + Calendar cal = DateUtils.toCalendar(_dt, _tz); + if (this == DAY) + cal.add(Calendar.DAY_OF_YEAR, -1); + else if (this == MONTH) + cal.add(Calendar.MONTH, -1); + if (this == YEAR) + cal.add(Calendar.YEAR, -1); + return cal.getTime(); + } + + public int blockCount(Date _start, TimeZone _tz) { + if (this == ALL) + return 1; + Date end = toEnd(_start, _tz); + int blockCnt = 0; + while (_start.before(end)) { + blockCnt++; + _start = toBlockEnd(_start, _tz); + } + return blockCnt; + } + + public int blockIndex(Date _readTime, TimeZone _tz) { + if (this == DAY) { + Date start = DateUtils.getMidnightBefore(_readTime, _tz); + return (int)((_readTime.getTime() - start.getTime())/60000); + } + else if (this == MONTH) { + Calendar read = DateUtils.toCalendar(_readTime, _tz); + return read.get(Calendar.DAY_OF_MONTH) - 1; + } + if (this == YEAR) { + Calendar read = DateUtils.toCalendar(_readTime, _tz); + return read.get(Calendar.MONTH); + } + return 0; + } + + public Date toBlockStart(int _index, Date _start, TimeZone _tz) { + if (this == DAY) + return new Date(_start.getTime() + _index*60000); + else if (this == MONTH) { + Calendar read = DateUtils.toCalendar(_start, _tz); + read.add(Calendar.DAY_OF_MONTH, _index); + return read.getTime(); + } + if (this == YEAR) { + Calendar read = DateUtils.toCalendar(_start, _tz); + read.add(Calendar.MONTH, _index); + return read.getTime(); + } + return new Date(0); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigCharacteristic.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigCharacteristic.java new file mode 100644 index 0000000..b58eea9 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigCharacteristic.java @@ -0,0 +1,44 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.NullUtils; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.UUID; + +public enum HubConfigCharacteristic { + WifiCredentials(2, CharacteristicFlag.WRITE), + AuthCode(3, CharacteristicFlag.WRITE), + HubIndex(4, CharacteristicFlag.READ, CharacteristicFlag.WRITE), + Restart(5, CharacteristicFlag.WRITE), + Reboot(6, CharacteristicFlag.WRITE), + AccountId(7, CharacteristicFlag.READ), + NetworkState(8, CharacteristicFlag.READ), + Flash(9, CharacteristicFlag.WRITE); + + public final int idx; + public final UUID uuid; + public final EnumSet flags; + + HubConfigCharacteristic(int _idx, CharacteristicFlag... _flags) { + idx = _idx; + uuid = HubConfigService.uuidFormat.format(_idx); + flags = EnumSet.copyOf(Arrays.asList(_flags)); + } + + public int getIdx() { + return idx; + } + + public UUID getUUID() { + return uuid; + } + + public EnumSet getFlags() { + return flags; + } + + public boolean isChar(String _char) { + return NullUtils.isEqual(name(), _char); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigService.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigService.java new file mode 100644 index 0000000..635a8d0 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigService.java @@ -0,0 +1,39 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.cryptography.AESTool; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class HubConfigService { + public static final UUIDFormatter uuidFormat = new UUIDFormatter("c5650001-d50f-49af-b906-cada0dc17937"); + private static final AESTool aes = new AESTool(37320708309265127L,-8068168662055796771L,-4867793276337148572L,4425609941731230765L); + private static final UUID serviceUUID = uuidFormat.format(1); + + public HubConfigService() { + } + + public static UUID getServiceUUID() { + return serviceUUID; + } + + public List getCharacteristics() { + return Arrays.asList(HubConfigCharacteristic.values()); + } + + public static byte[] encryptWifiCreds(String _ssid, String _password) { + DaoEntity creds = new DaoEntity("ssid", _ssid).and("pwd", _password); + return aes.encrypt(DaoSerializer.toZipBson(creds)); + } + + public static String decryptWifiSSID(byte[] _payload) { + return DaoSerializer.getString(DaoSerializer.fromZipBson(aes.decrypt(_payload)), "ssid"); + } + + public static String decryptWifiPassword(byte[] _payload) { + return DaoSerializer.getString(DaoSerializer.fromZipBson(aes.decrypt(_payload)), "pwd"); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubPowerMinute.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubPowerMinute.java new file mode 100644 index 0000000..2eb3137 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubPowerMinute.java @@ -0,0 +1,58 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.Date; +import java.util.List; + +@DBSerializable +public class HubPowerMinute { + private int accountId; + private int hub; + private int minute; + private List breakers; + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public int getHub() { + return hub; + } + + public void setHub(int _hub) { + hub = _hub; + } + + public Date getMinuteAsDate() { + return new Date(((long)minute)*60000); + } + + public int getMinute() { + return minute; + } + + public void setMinute(int _minute) { + minute = _minute; + } + + public void setMinute(Date _minute) { + minute = (int)(_minute.getTime()/60000); + } + + public List getBreakers() { + return breakers; + } + + public void setBreakers(List _breakers) { + breakers = _breakers; + } + + public String getId() { + return String.format("%d-%d-%d", accountId, hub, minute); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Meter.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Meter.java new file mode 100644 index 0000000..ebf13f8 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Meter.java @@ -0,0 +1,34 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +@DBSerializable +public class Meter { + private int accountId; + private int index; + private String name; + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public int getIndex() { + return index; + } + + public void setIndex(int _index) { + index = _index; + } + + public String getName() { + return name; + } + + public void setName(String _name) { + name = _name; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkAdapter.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkAdapter.java new file mode 100644 index 0000000..5e0862e --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkAdapter.java @@ -0,0 +1,42 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.CollectionUtils; + +import java.util.Collection; +import java.util.EnumSet; + +public enum NetworkAdapter { + ETHERNET((byte)0x1), + WIFI((byte)0x2); + + public final byte bt; + + NetworkAdapter(byte _bt) { + bt = _bt; + } + + public static NetworkAdapter fromByte(byte _bt) { + for (NetworkAdapter a : values()) { + if (a.bt == _bt) + return a; + } + return null; + } + + public static EnumSet fromMask(byte _bt) { + EnumSet values = EnumSet.noneOf(NetworkAdapter.class); + for (NetworkAdapter a : values()) { + if ((a.bt & _bt) == a.bt) + values.add(a); + } + return values; + } + + public static byte toMask(Collection _adapters) { + byte mask = 0; + for (NetworkAdapter a : CollectionUtils.makeNotNull(_adapters)) { + mask |= a.bt; + } + return mask; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkStatus.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkStatus.java new file mode 100644 index 0000000..75ab05a --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkStatus.java @@ -0,0 +1,44 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.CollectionUtils; + +import java.util.EnumSet; +import java.util.List; + +public class NetworkStatus { + private List wifiIPs; + private List ethernetIPs; + + public List getWifiIPs() { + return wifiIPs; + } + + public void setWifiIPs(List _wifiIPs) { + wifiIPs = _wifiIPs; + } + + public List getEthernetIPs() { + return ethernetIPs; + } + + public void setEthernetIPs(List _ethernetIPs) { + ethernetIPs = _ethernetIPs; + } + + public boolean isWifiConnected() { + return CollectionUtils.isNotEmpty(wifiIPs); + } + + public boolean isEthernetConnected() { + return CollectionUtils.isNotEmpty(ethernetIPs); + } + + public byte toMask() { + EnumSet adapters = EnumSet.noneOf(NetworkAdapter.class); + if (isWifiConnected()) + adapters.add(NetworkAdapter.WIFI); + if (isEthernetConnected()) + adapters.add(NetworkAdapter.ETHERNET); + return NetworkAdapter.toMask(adapters); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Sequence.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Sequence.java new file mode 100644 index 0000000..308051a --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Sequence.java @@ -0,0 +1,28 @@ +package com.lanternsoftware.datamodel.currentmonitor; + + +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; + +@DBSerializable +public class Sequence { + @PrimaryKey + private String id; + private int sequence; + + public String getId() { + return id; + } + + public void setId(String _id) { + id = _id; + } + + public int getSequence() { + return sequence; + } + + public void setSequence(int _sequence) { + sequence = _sequence; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/SignupResponse.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/SignupResponse.java new file mode 100644 index 0000000..2e0a0f5 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/SignupResponse.java @@ -0,0 +1,45 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +@DBSerializable +public class SignupResponse { + private String error; + private String authCode; + + public SignupResponse() { + } + + public static SignupResponse error(String _error) { + SignupResponse response = new SignupResponse(); + response.setError(_error); + return response; + } + + public static SignupResponse success(String _authCode) { + SignupResponse response = new SignupResponse(); + response.setAuthCode(_authCode); + return response; + } + + public String getError() { + return error; + } + + public void setError(String _error) { + error = _error; + } + + public String getAuthCode() { + return authCode; + } + + public void setAuthCode(String _authCode) { + authCode = _authCode; + } + + public boolean isSuccess() { + return NullUtils.isEmpty(error) && NullUtils.isNotEmpty(authCode); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/UUIDFormatter.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/UUIDFormatter.java new file mode 100644 index 0000000..d63e238 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/UUIDFormatter.java @@ -0,0 +1,17 @@ +package com.lanternsoftware.datamodel.currentmonitor; + +import java.util.UUID; + +public class UUIDFormatter { + private final String uuidPrefix; + private final String uuidSuffix; + + public UUIDFormatter(String _uuid) { + uuidPrefix = _uuid.substring(0,4); + uuidSuffix = _uuid.substring(8); + } + + public UUID format(int _idx) { + return UUID.fromString(uuidPrefix + String.format("%04X", _idx) + uuidSuffix); + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java new file mode 100644 index 0000000..c4c9eb9 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java @@ -0,0 +1,50 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.Account; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class AccountSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return Account.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(Account _o) + { + DaoEntity d = new DaoEntity(); + d.put("_id", String.valueOf(_o.getId())); + d.put("username", _o.getUsername()); + d.put("password", _o.getPassword()); + if (CollectionUtils.isNotEmpty(_o.getAuxiliaryAccountIds())) + d.put("aux_account_ids", CollectionUtils.toByteArray(_o.getAuxiliaryAccountIds())); + return d; + } + + @Override + public Account fromDaoEntity(DaoEntity _d) + { + Account o = new Account(); + o.setId(DaoSerializer.getInteger(_d, "_id")); + o.setUsername(DaoSerializer.getString(_d, "username")); + o.setPassword(DaoSerializer.getString(_d, "password")); + o.setAuxiliaryAccountIds(CollectionUtils.fromByteArrayOfIntegers(DaoSerializer.getByteArray(_d, "aux_account_ids"))); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AuthCodeSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AuthCodeSerializer.java new file mode 100644 index 0000000..f0afb65 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AuthCodeSerializer.java @@ -0,0 +1,46 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class AuthCodeSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return AuthCode.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(AuthCode _o) + { + DaoEntity d = new DaoEntity(); + d.put("account_id", _o.getAccountId()); + if (CollectionUtils.isNotEmpty(_o.getAuxiliaryAccountIds())) + d.put("aux_account_ids", CollectionUtils.toByteArray(_o.getAuxiliaryAccountIds())); + return d; + } + + @Override + public AuthCode fromDaoEntity(DaoEntity _d) + { + AuthCode o = new AuthCode(); + o.setAccountId(DaoSerializer.getInteger(_d, "account_id")); + o.setAuxiliaryAccountIds(CollectionUtils.fromByteArrayOfIntegers(DaoSerializer.getByteArray(_d, "aux_account_ids"))); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerConfigSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerConfigSerializer.java new file mode 100644 index 0000000..598713b --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerConfigSerializer.java @@ -0,0 +1,52 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup; +import com.lanternsoftware.datamodel.currentmonitor.BreakerHub; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPanel; +import com.lanternsoftware.datamodel.currentmonitor.Meter; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.util.Collections; +import java.util.List; + +public class BreakerConfigSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return BreakerConfig.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(BreakerConfig _o) + { + DaoEntity d = new DaoEntity(); + d.put("_id", String.valueOf(_o.getAccountId())); + d.put("meters", DaoSerializer.toDaoEntities(_o.getMeters(), DaoProxyType.MONGO)); + d.put("panels", DaoSerializer.toDaoEntities(_o.getPanels(), DaoProxyType.MONGO)); + d.put("breaker_hubs", DaoSerializer.toDaoEntities(_o.getBreakerHubs(), DaoProxyType.MONGO)); + d.put("breaker_groups", DaoSerializer.toDaoEntities(_o.getBreakerGroups(), DaoProxyType.MONGO)); + return d; + } + + @Override + public BreakerConfig fromDaoEntity(DaoEntity _d) + { + BreakerConfig o = new BreakerConfig(); + o.setAccountId(DaoSerializer.getInteger(_d, "_id")); + o.setMeters(DaoSerializer.getList(_d, "meters", Meter.class)); + o.setPanels(DaoSerializer.getList(_d, "panels", BreakerPanel.class)); + o.setBreakerHubs(DaoSerializer.getList(_d, "breaker_hubs", BreakerHub.class)); + o.setBreakerGroups(DaoSerializer.getList(_d, "breaker_groups", BreakerGroup.class)); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java new file mode 100644 index 0000000..382e2c7 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java @@ -0,0 +1,98 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy; +import com.lanternsoftware.datamodel.currentmonitor.EnergyBlock; +import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +public class BreakerGroupEnergySerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return BreakerGroupEnergy.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(BreakerGroupEnergy _o) + { + DaoEntity d = new DaoEntity(); + d.put("_id", _o.getId()); + d.put("account_id", _o.getAccountId()); + d.put("group_id", _o.getGroupId()); + d.put("group_name", _o.getGroupName()); + d.put("view_mode", DaoSerializer.toEnumName(_o.getViewMode())); + d.put("start", DaoSerializer.toLong(_o.getStart())); + d.put("sub_groups", DaoSerializer.toDaoEntities(_o.getSubGroups(), DaoProxyType.MONGO)); + if (CollectionUtils.size(_o.getEnergyBlocks()) > 0) { + Date start = _o.getStart(); + Date now = new Date(); + TimeZone tz = TimeZone.getTimeZone("America/Chicago"); + ByteBuffer bb = ByteBuffer.allocate(_o.getViewMode().blockCount(start, tz) * 4); + for (EnergyBlock b : _o.getEnergyBlocks()) { + if (b.getStart().before(start)) + continue; + if (now.before(start)) + break; + while (start.before(b.getStart())) { + bb.putFloat(0); + start = _o.getViewMode().toBlockEnd(start, tz); + } + bb.putFloat((float) b.getJoules()); + start = _o.getViewMode().toBlockEnd(start, tz); + } + if (bb.position() < bb.limit()) + d.put("blocks", Arrays.copyOfRange(bb.array(), 0, bb.position())); + else + d.put("blocks", bb.array()); + } + d.put("to_grid", _o.getToGrid()); + d.put("from_grid", _o.getFromGrid()); + return d; + } + + @Override + public BreakerGroupEnergy fromDaoEntity(DaoEntity _d) + { + TimeZone tz = TimeZone.getTimeZone("America/Chicago"); + BreakerGroupEnergy o = new BreakerGroupEnergy(); + o.setGroupId(DaoSerializer.getString(_d, "group_id")); + o.setAccountId(DaoSerializer.getInteger(_d, "account_id")); + o.setGroupName(DaoSerializer.getString(_d, "group_name")); + o.setViewMode(DaoSerializer.getEnum(_d, "view_mode", EnergyBlockViewMode.class)); + o.setStart(DaoSerializer.getDate(_d, "start")); + o.setSubGroups(DaoSerializer.getList(_d, "sub_groups", BreakerGroupEnergy.class)); + List blocks = new ArrayList<>(); + byte[] blockData = DaoSerializer.getByteArray(_d, "blocks"); + if (CollectionUtils.length(blockData) > 0) { + ByteBuffer bb = ByteBuffer.wrap(blockData); + Date start = o.getStart(); + while (bb.hasRemaining()) { + EnergyBlock block = new EnergyBlock(start, o.getViewMode().toBlockEnd(start, tz), bb.getFloat()); + blocks.add(block); + start = block.getEnd(); + } + } + o.setEnergyBlocks(blocks); + o.setToGrid(DaoSerializer.getDouble(_d, "to_grid")); + o.setFromGrid(DaoSerializer.getDouble(_d, "from_grid")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSerializer.java new file mode 100644 index 0000000..48bcafd --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSerializer.java @@ -0,0 +1,49 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.Breaker; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class BreakerGroupSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return BreakerGroup.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(BreakerGroup _o) + { + DaoEntity d = new DaoEntity(); + if (_o.getId() != null) + d.put("_id", _o.getId()); + d.put("account_id", _o.getAccountId()); + d.put("name", _o.getName()); + d.put("sub_groups", DaoSerializer.toDaoEntities(_o.getSubGroups(), DaoProxyType.MONGO)); + d.put("breakers", DaoSerializer.toDaoEntities(_o.getBreakers(), DaoProxyType.MONGO)); + return d; + } + + @Override + public BreakerGroup fromDaoEntity(DaoEntity _d) + { + BreakerGroup o = new BreakerGroup(); + o.setId(DaoSerializer.getString(_d, "_id")); + o.setAccountId(DaoSerializer.getInteger(_d, "account_id")); + o.setName(DaoSerializer.getString(_d, "name")); + o.setSubGroups(DaoSerializer.getList(_d, "sub_groups", BreakerGroup.class)); + o.setBreakers(DaoSerializer.getList(_d, "breakers", Breaker.class)); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSummarySerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSummarySerializer.java new file mode 100644 index 0000000..05a6008 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSummarySerializer.java @@ -0,0 +1,55 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupSummary; +import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.util.Collections; +import java.util.List; +import java.util.TimeZone; + +public class BreakerGroupSummarySerializer extends AbstractDaoSerializer { + @Override + public Class getSupportedClass() { + return BreakerGroupSummary.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(BreakerGroupSummary _o) { + DaoEntity d = new DaoEntity(); + d.put("_id", _o.getId()); + d.put("account_id", _o.getAccountId()); + d.put("group_id", _o.getGroupId()); + d.put("group_name", _o.getGroupName()); + d.put("view_mode", DaoSerializer.toEnumName(_o.getViewMode())); + d.put("start", DaoSerializer.toLong(_o.getStart())); + d.put("sub_groups", DaoSerializer.toDaoEntities(_o.getSubGroups(), DaoProxyType.MONGO)); + d.put("joules", _o.getJoules()); + d.put("to_grid", _o.getToGrid()); + d.put("from_grid", _o.getFromGrid()); + return d; + } + + @Override + public BreakerGroupSummary fromDaoEntity(DaoEntity _d) { + BreakerGroupSummary o = new BreakerGroupSummary(); + o.setGroupId(DaoSerializer.getString(_d, "group_id")); + o.setAccountId(DaoSerializer.getInteger(_d, "account_id")); + o.setGroupName(DaoSerializer.getString(_d, "group_name")); + o.setViewMode(DaoSerializer.getEnum(_d, "view_mode", EnergyBlockViewMode.class)); + o.setStart(DaoSerializer.getDate(_d, "start")); + o.setSubGroups(DaoSerializer.getList(_d, "sub_groups", BreakerGroupSummary.class)); + o.setJoules(DaoSerializer.getDouble(_d, "joules")); + o.setToGrid(DaoSerializer.getDouble(_d, "to_grid")); + o.setFromGrid(DaoSerializer.getDouble(_d, "from_grid")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerHubSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerHubSerializer.java new file mode 100644 index 0000000..c70e31e --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerHubSerializer.java @@ -0,0 +1,45 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.BreakerHub; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class BreakerHubSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return BreakerHub.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(BreakerHub _o) + { + DaoEntity d = new DaoEntity(); + d.put("hub", _o.getHub()); + d.put("voltage_calibration_factor", _o.getVoltageCalibrationFactor()); + d.put("frequency", _o.getFrequency()); + d.put("bluetooth_mac", _o.getBluetoothMac()); + return d; + } + + @Override + public BreakerHub fromDaoEntity(DaoEntity _d) + { + BreakerHub o = new BreakerHub(); + o.setHub(DaoSerializer.getInteger(_d, "hub")); + o.setVoltageCalibrationFactor(DaoSerializer.getDouble(_d, "voltage_calibration_factor")); + o.setFrequency(DaoSerializer.getInteger(_d, "frequency")); + o.setBluetoothMac(DaoSerializer.getString(_d, "bluetooth_mac")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPanelSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPanelSerializer.java new file mode 100644 index 0000000..9e0f8eb --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPanelSerializer.java @@ -0,0 +1,47 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.BreakerPanel; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class BreakerPanelSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return BreakerPanel.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(BreakerPanel _o) + { + DaoEntity d = new DaoEntity(); + d.put("account_id", _o.getAccountId()); + d.put("name", _o.getName()); + d.put("index", _o.getIndex()); + d.put("spaces", _o.getSpaces()); + d.put("meter", _o.getMeter()); + return d; + } + + @Override + public BreakerPanel fromDaoEntity(DaoEntity _d) + { + BreakerPanel o = new BreakerPanel(); + o.setAccountId(DaoSerializer.getInteger(_d, "account_id")); + o.setName(DaoSerializer.getString(_d, "name")); + o.setIndex(DaoSerializer.getInteger(_d, "index")); + o.setSpaces(DaoSerializer.getInteger(_d, "spaces")); + o.setMeter(DaoSerializer.getInteger(_d, "meter")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerMinuteSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerMinuteSerializer.java new file mode 100644 index 0000000..cf6d912 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerMinuteSerializer.java @@ -0,0 +1,48 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.BreakerPowerMinute; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class BreakerPowerMinuteSerializer extends AbstractDaoSerializer { + @Override + public Class getSupportedClass() { + return BreakerPowerMinute.class; + } + + @Override + public DaoEntity toDaoEntity(BreakerPowerMinute _o) { + DaoEntity d = new DaoEntity(); + d.put("panel", _o.getPanel()); + d.put("space", _o.getSpace()); + ByteBuffer bb = ByteBuffer.allocate(240); + for (Float reading : CollectionUtils.makeNotNull(_o.getReadings())) { + bb.putFloat(DaoSerializer.toFloat(reading)); + } + d.put("readings", bb.array()); + return d; + } + + @Override + public BreakerPowerMinute fromDaoEntity(DaoEntity _d) { + BreakerPowerMinute o = new BreakerPowerMinute(); + o.setPanel(DaoSerializer.getInteger(_d, "panel")); + o.setSpace(DaoSerializer.getInteger(_d, "space")); + byte[] data = DaoSerializer.getByteArray(_d, "readings"); + List readings = new ArrayList<>(); + o.setReadings(readings); + if (CollectionUtils.length(data) > 0) { + ByteBuffer bb = ByteBuffer.wrap(data); + while (bb.hasRemaining()) { + readings.add(bb.getFloat()); + } + } + return o; + } +} diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerSerializer.java new file mode 100644 index 0000000..8852bd8 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerSerializer.java @@ -0,0 +1,54 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.util.Collections; +import java.util.List; + +public class BreakerPowerSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return BreakerPower.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(BreakerPower _o) + { + DaoEntity d = new DaoEntity(); + d.put("_id", _o.getId()); + d.put("account_id", _o.getAccountId()); + d.put("panel", _o.getPanel()); + d.put("space", _o.getSpace()); + d.put("key", _o.getKey()); + d.put("read_time", DaoSerializer.toLong(_o.getReadTime())); + d.put("hub_version", _o.getHubVersion()); + d.put("power", _o.getPower()); + d.put("voltage", _o.getVoltage()); + return d; + } + + @Override + public BreakerPower fromDaoEntity(DaoEntity _d) + { + BreakerPower o = new BreakerPower(); + o.setAccountId(DaoSerializer.getInteger(_d, "account_id")); + o.setPanel(DaoSerializer.getInteger(_d, "panel")); + o.setSpace(DaoSerializer.getInteger(_d, "space")); + o.setReadTime(DaoSerializer.getDate(_d, "read_time")); + o.setHubVersion(DaoSerializer.getString(_d, "hub_version")); + o.setPower(DaoSerializer.getDouble(_d, "power")); + o.setVoltage(DaoSerializer.getDouble(_d, "voltage")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerSerializer.java new file mode 100644 index 0000000..9c3635e --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerSerializer.java @@ -0,0 +1,63 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.Breaker; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity; +import com.lanternsoftware.datamodel.currentmonitor.BreakerType; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class BreakerSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return Breaker.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(Breaker _o) + { + DaoEntity d = new DaoEntity(); + d.put("panel", _o.getPanel()); + d.put("space", _o.getSpace()); + d.put("meter", _o.getMeter()); + d.put("hub", _o.getHub()); + d.put("port", _o.getPort()); + d.put("name", _o.getName()); + d.put("description", _o.getDescription()); + d.put("size_amps", _o.getSizeAmps()); + d.put("calibration_factor", _o.getCalibrationFactor()); + d.put("low_pass_filter", _o.getLowPassFilter()); + d.put("polarity", DaoSerializer.toEnumName(_o.getPolarity())); + d.put("type", DaoSerializer.toEnumName(_o.getType())); + return d; + } + + @Override + public Breaker fromDaoEntity(DaoEntity _d) + { + Breaker o = new Breaker(); + o.setPanel(DaoSerializer.getInteger(_d, "panel")); + o.setSpace(DaoSerializer.getInteger(_d, "space")); + o.setMeter(DaoSerializer.getInteger(_d, "meter")); + o.setHub(DaoSerializer.getInteger(_d, "hub")); + o.setPort(DaoSerializer.getInteger(_d, "port")); + o.setName(DaoSerializer.getString(_d, "name")); + o.setDescription(DaoSerializer.getString(_d, "description")); + o.setSizeAmps(DaoSerializer.getInteger(_d, "size_amps")); + o.setCalibrationFactor(DaoSerializer.getDouble(_d, "calibration_factor")); + o.setLowPassFilter(DaoSerializer.getDouble(_d, "low_pass_filter")); + o.setPolarity(DaoSerializer.getEnum(_d, "polarity", BreakerPolarity.class)); + o.setType(DaoSerializer.getEnum(_d, "type", BreakerType.class)); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/EnergyBlockSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/EnergyBlockSerializer.java new file mode 100644 index 0000000..a59abd7 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/EnergyBlockSerializer.java @@ -0,0 +1,43 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.EnergyBlock; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class EnergyBlockSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return EnergyBlock.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(EnergyBlock _o) + { + DaoEntity d = new DaoEntity(); + d.put("start", DaoSerializer.toLong(_o.getStart())); + d.put("end", DaoSerializer.toLong(_o.getEnd())); + d.put("joules", _o.getJoules()); + return d; + } + + @Override + public EnergyBlock fromDaoEntity(DaoEntity _d) + { + EnergyBlock o = new EnergyBlock(); + o.setStart(DaoSerializer.getDate(_d, "start")); + o.setEnd(DaoSerializer.getDate(_d, "end")); + o.setJoules(DaoSerializer.getDouble(_d, "joules")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/HubPowerMinuteSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/HubPowerMinuteSerializer.java new file mode 100644 index 0000000..3656467 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/HubPowerMinuteSerializer.java @@ -0,0 +1,46 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.BreakerPowerMinute; +import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class HubPowerMinuteSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return HubPowerMinute.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(HubPowerMinute _o) + { + DaoEntity d = new DaoEntity(); + d.put("account_id", _o.getAccountId()); + d.put("hub", _o.getHub()); + d.put("minute", _o.getMinute()); + d.put("breakers", DaoSerializer.toDaoEntities(_o.getBreakers(), DaoProxyType.MONGO)); + return d; + } + + @Override + public HubPowerMinute fromDaoEntity(DaoEntity _d) + { + HubPowerMinute o = new HubPowerMinute(); + o.setAccountId(DaoSerializer.getInteger(_d, "account_id")); + o.setHub(DaoSerializer.getInteger(_d, "hub")); + o.setMinute(DaoSerializer.getInteger(_d, "minute")); + o.setBreakers(DaoSerializer.getList(_d, "breakers", BreakerPowerMinute.class)); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/MeterSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/MeterSerializer.java new file mode 100644 index 0000000..eaeced1 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/MeterSerializer.java @@ -0,0 +1,43 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.Meter; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class MeterSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return Meter.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(Meter _o) + { + DaoEntity d = new DaoEntity(); + d.put("account_id", _o.getAccountId()); + d.put("index", _o.getIndex()); + d.put("name", _o.getName()); + return d; + } + + @Override + public Meter fromDaoEntity(DaoEntity _d) + { + Meter o = new Meter(); + o.setAccountId(DaoSerializer.getInteger(_d, "account_id")); + o.setIndex(DaoSerializer.getInteger(_d, "index")); + o.setName(DaoSerializer.getString(_d, "name")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SequenceSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SequenceSerializer.java new file mode 100644 index 0000000..a5c0766 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SequenceSerializer.java @@ -0,0 +1,42 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.Sequence; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class SequenceSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return Sequence.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(Sequence _o) + { + DaoEntity d = new DaoEntity(); + if (_o.getId() != null) + d.put("_id", _o.getId()); + d.put("sequence", _o.getSequence()); + return d; + } + + @Override + public Sequence fromDaoEntity(DaoEntity _d) + { + Sequence o = new Sequence(); + o.setId(DaoSerializer.getString(_d, "_id")); + o.setSequence(DaoSerializer.getInteger(_d, "sequence")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SignupResponseSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SignupResponseSerializer.java new file mode 100644 index 0000000..099cbf2 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SignupResponseSerializer.java @@ -0,0 +1,41 @@ +package com.lanternsoftware.datamodel.currentmonitor.dao; + +import com.lanternsoftware.datamodel.currentmonitor.SignupResponse; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import java.util.Collections; +import java.util.List; + +public class SignupResponseSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return SignupResponse.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(SignupResponse _o) + { + DaoEntity d = new DaoEntity(); + d.put("error", _o.getError()); + d.put("auth_code", _o.getAuthCode()); + return d; + } + + @Override + public SignupResponse fromDaoEntity(DaoEntity _d) + { + SignupResponse o = new SignupResponse(); + o.setError(DaoSerializer.getString(_d, "error")); + o.setAuthCode(DaoSerializer.getString(_d, "auth_code")); + return o; + } +} \ No newline at end of file diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer b/currentmonitor/lantern-datamodel-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer new file mode 100644 index 0000000..ece1bb1 --- /dev/null +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer @@ -0,0 +1,16 @@ +com.lanternsoftware.datamodel.currentmonitor.dao.AccountSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.AuthCodeSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.BreakerConfigSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.BreakerGroupEnergySerializer +com.lanternsoftware.datamodel.currentmonitor.dao.BreakerGroupSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.BreakerGroupSummarySerializer +com.lanternsoftware.datamodel.currentmonitor.dao.BreakerHubSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.BreakerPanelSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.BreakerPowerMinuteSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.BreakerPowerSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.BreakerSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.EnergyBlockSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.HubPowerMinuteSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.MeterSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.SequenceSerializer +com.lanternsoftware.datamodel.currentmonitor.dao.SignupResponseSerializer diff --git a/currentmonitor/lantern-service-currentmonitor/pom.xml b/currentmonitor/lantern-service-currentmonitor/pom.xml new file mode 100644 index 0000000..68af784 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/pom.xml @@ -0,0 +1,89 @@ + + 4.0.0 + com.lanternsoftware.currentmonitor + lantern-service-currentmonitor + war + 1.0.0 + lantern-service-currentmonitor + + + 1.8 + 1.8 + + + + + com.lanternsoftware.currentmonitor + lantern-dataaccess-currentmonitor + 1.0.0 + + + com.lanternsoftware.util + lantern-util-servlet + 1.0.0 + + + com.google.api-client + google-api-client + 1.30.4 + + + javax + javaee-api + 8.0 + provided + + + org.slf4j + slf4j-api + 1.7.29 + + + ch.qos.logback + logback-classic + 1.2.3 + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + maven-war-plugin + 2.5 + + + + true + lib/ + + + + + + + diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/context/Globals.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/context/Globals.java new file mode 100644 index 0000000..3ee445c --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/context/Globals.java @@ -0,0 +1,23 @@ +package com.lanternsoftware.currentmonitor.context; + +import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao; +import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.dao.mongo.MongoConfig; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +public class Globals implements ServletContextListener { + public static CurrentMonitorDao dao; + + @Override + public void contextInitialized(ServletContextEvent sce) { + dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg")); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + dao.shutdown(); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java new file mode 100644 index 0000000..021fcff --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java @@ -0,0 +1,57 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.lanternsoftware.currentmonitor.context.Globals; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.servlet.BasicAuth; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Collections; + +@WebServlet("/auth/*") +public class AuthServlet extends CMServlet { + private static final NetHttpTransport transport = new NetHttpTransport(); + private static final JacksonFactory jsonFactory = new JacksonFactory(); + private static final Logger logger = LoggerFactory.getLogger(AuthServlet.class); + private static final String googleSsoKey = ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "google_sso_key.txt"); + + @Override + protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) { + String authCode = _req.getHeader("auth_code"); + if (NullUtils.isEmpty(authCode)) { + BasicAuth auth = new BasicAuth(_req); + if (NullUtils.isEqual(auth.getUsername(), "googlesso")) { + GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory).setAudience(Collections.singletonList(googleSsoKey)).build(); + try { + GoogleIdToken idToken = verifier.verify(auth.getPassword()); + if (idToken != null) { + GoogleIdToken.Payload payload = idToken.getPayload(); + String email = payload.getEmail(); + authCode = Globals.dao.getAuthCodeForEmail(email); + } + } + catch (Exception _e) { + logger.error("Failed to validate google auth token", _e); + } + } + else + authCode = Globals.dao.authenticateAccount(auth.getUsername(), auth.getPassword()); + } + DaoEntity rep = new DaoEntity("auth_code", authCode); + if (isPath(_req, 0, "bin")) + zipBsonResponse(_rep, rep); + else + jsonResponse(_rep, rep); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CMServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CMServlet.java new file mode 100644 index 0000000..ccc1bfd --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CMServlet.java @@ -0,0 +1,101 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; +import org.apache.commons.io.IOUtils; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; +import java.io.InputStream; +import java.io.OutputStream; + +public abstract class CMServlet extends HttpServlet { + public static void setResponseHtml(HttpServletResponse _response, String _sHtml) { + setResponseEntity(_response, MediaType.TEXT_HTML, _sHtml); + } + + public static void setResponseEntity(HttpServletResponse _response, String _sContentType, String _sEntity) { + setResponseEntity(_response, 200, _sContentType, _sEntity); + } + + public static void setResponseEntity(HttpServletResponse _response, String _sContentType, byte[] _btData) { + setResponseEntity(_response, 200, _sContentType, _btData); + } + + public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, String _sEntity) { + setResponseEntity(_response, _iStatus, _sContentType, NullUtils.toByteArray(_sEntity)); + } + + public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, byte[] _btData) { + OutputStream os = null; + try { + _response.setStatus(_iStatus); + _response.setCharacterEncoding("UTF-8"); + _response.setContentType(_sContentType); + if ((_btData != null) && (_btData.length > 0)) { + _response.setContentLength(_btData.length); + os = _response.getOutputStream(); + os.write(_btData); + } else + _response.setContentLength(0); + } catch (Exception e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(os); + } + } + + protected void zipBsonResponse(HttpServletResponse _response, Object _object) + { + setResponseEntity(_response, 200, MediaType.APPLICATION_OCTET_STREAM, DaoSerializer.toZipBson(_object)); + } + + protected void jsonResponse(HttpServletResponse _response, Object _object) + { + setResponseEntity(_response, 200, MediaType.APPLICATION_JSON, DaoSerializer.toJson(_object)); + } + + protected void jsonResponse(HttpServletResponse _response, String _json) + { + setResponseEntity(_response, 200, MediaType.APPLICATION_JSON, _json); + } + + protected String getRequestPayloadAsString(HttpServletRequest _req) { + return NullUtils.toString(getRequestPayload(_req)); + } + + protected byte[] getRequestPayload(HttpServletRequest _req) { + InputStream is = null; + try { + is = _req.getInputStream(); + return IOUtils.toByteArray(is); + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + finally { + IOUtils.closeQuietly(is); + } + } + + protected DaoEntity getRequestZipBson(HttpServletRequest _req) { + return DaoSerializer.fromZipBson(getRequestPayload(_req)); + } + + protected T getRequestPayload(HttpServletRequest _req, Class _retClass) { + return DaoSerializer.fromZipBson(getRequestPayload(_req), _retClass); + } + + protected String[] path(HttpServletRequest _req) { + return NullUtils.cleanSplit(NullUtils.makeNotNull(_req.getPathInfo()), "/"); + } + + protected boolean isPath(HttpServletRequest _req, int _index, String _path) { + return NullUtils.isEqual(_path, CollectionUtils.get(path(_req), _index)); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CommandServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CommandServlet.java new file mode 100644 index 0000000..8003152 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CommandServlet.java @@ -0,0 +1,49 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +@WebServlet("/command") +public class CommandServlet extends SecureServlet { + + @Override + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + File folder = new File(LanternFiles.OPS_PATH + _authCode.getAccountId()); + List commands = new ArrayList<>(); + if (folder.exists() && folder.isDirectory()) { + for (File command : CollectionUtils.asArrayList(folder.listFiles())) { + if (command.isDirectory()) + continue; + String c = command.getName(); + String extension = NullUtils.after(c, "."); + if (NullUtils.isNotEmpty(extension)) + c = c.replace("." + extension, ""); + commands.add(c); + } + } + zipBsonResponse(_rep, new DaoEntity("commands", commands)); + } + + @Override + protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + DaoEntity payload = getRequestZipBson(_req); + if (payload == null) + return; + String command = DaoSerializer.getString(payload, "command"); + String path = LanternFiles.OPS_PATH + _authCode.getAccountId() + File.separator + "payload" + File.separator; + new File(path).mkdirs(); + ResourceLoader.writeFile(path+ command + ".txt", DaoSerializer.getString(payload, "payload")); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/ConfigServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/ConfigServlet.java new file mode 100644 index 0000000..c7ed38d --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/ConfigServlet.java @@ -0,0 +1,34 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.lanternsoftware.currentmonitor.context.Globals; +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/config/*") +public class ConfigServlet extends SecureServlet { + @Override + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + if (isPath(_req, 0, "bin")) + zipBsonResponse(_rep, Globals.dao.getMergedConfig(_authCode)); + else + jsonResponse(_rep, Globals.dao.getMergedConfig(_authCode)); + } + + @Override + protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + BreakerConfig config = getRequestPayload(_req, BreakerConfig.class); + if (config == null) { + _rep.setStatus(400); + return; + } + if (config.getAccountId() != _authCode.getAccountId()) { + _rep.setStatus(401); + return; + } + Globals.dao.putConfig(config); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupEnergyServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupEnergyServlet.java new file mode 100644 index 0000000..449729c --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupEnergyServlet.java @@ -0,0 +1,48 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.lanternsoftware.currentmonitor.context.Globals; +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy; +import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Date; +import java.util.List; + +@WebServlet("/energy/group/*") +public class GroupEnergyServlet extends SecureServlet { + @Override + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + String[] path = path(_req); + if (path.length < 3) { + _rep.setStatus(400); + return; + } + EnergyBlockViewMode viewMode = NullUtils.toEnum(EnergyBlockViewMode.class, path[1], EnergyBlockViewMode.DAY); + Date start = new Date(NullUtils.toLong(path[2])); + List energies = CollectionUtils.transform(_authCode.getAllAccountIds(), _id->Globals.dao.getBreakerGroupEnergy(_id, path[0], viewMode, start), true); + if (CollectionUtils.isNotEmpty(energies)) { + BreakerGroupEnergy energy; + if (energies.size() > 1) { + energy = new BreakerGroupEnergy(); + energy.setAccountId(_authCode.getAccountId()); + energy.setGroupId("Sites"); + energy.setGroupName("Sites"); + energy.setStart(start); + energy.setViewMode(viewMode); + energy.setSubGroups(CollectionUtils.asArrayList(energies)); + } + else + energy = CollectionUtils.getFirst(energies); + if (NullUtils.isEqual(CollectionUtils.get(path, 3), "bin")) + zipBsonResponse(_rep, energy); + else + jsonResponse(_rep, energy); + } else + _rep.setStatus(404); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupPowerServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupPowerServlet.java new file mode 100644 index 0000000..5af0898 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupPowerServlet.java @@ -0,0 +1,26 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.lanternsoftware.currentmonitor.context.Globals; +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.datamodel.currentmonitor.Breaker; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; +import org.bson.Document; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/power/group/*") +public class GroupPowerServlet extends SecureServlet { + @Override + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + String[] path = path(_req); + if (path.length < 1) + zipBsonResponse(_rep, new DaoEntity("breakers", DaoSerializer.toDaoEntities(Globals.dao.getBreakerPowerForAccount(_authCode.getAccountId())))); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/PowerServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/PowerServlet.java new file mode 100644 index 0000000..a0bd27c --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/PowerServlet.java @@ -0,0 +1,49 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.lanternsoftware.currentmonitor.context.Globals; +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; +import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.DaoSerializer; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +@WebServlet("/power/*") +public class PowerServlet extends SecureServlet { + @Override + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + String[] path = path(_req); + if (path.length < 2) { + _rep.setStatus(400); + return; + } + int hub = DaoSerializer.toInteger(CollectionUtils.get(path, 0)); + int port = DaoSerializer.toInteger(CollectionUtils.get(path, 1)); + jsonResponse(_rep, Globals.dao.getLatestBreakerPower(_authCode.getAccountId(), hub, port)); + } + + @Override + protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + String[] path = path(_req); + if ((path.length > 0) && NullUtils.isEqual(CollectionUtils.get(path, 0), "hub")) { + Globals.dao.putHubPowerMinute(getRequestPayload(_req, HubPowerMinute.class)); + return; + } + if ((path.length > 0) && NullUtils.isEqual(CollectionUtils.get(path, 0), "batch")) { + List powers = DaoSerializer.getList(getRequestZipBson(_req), "readings", BreakerPower.class); + if (!powers.isEmpty()) { + CollectionUtils.edit(powers, _p->_p.setAccountId(_authCode.getAccountId())); + Globals.dao.getProxy().save(powers); + } + return; + } + BreakerPower power = getRequestPayload(_req, BreakerPower.class); + power.setAccountId(_authCode.getAccountId()); + Globals.dao.putBreakerPower(power); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SecureServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SecureServlet.java new file mode 100644 index 0000000..bf89561 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SecureServlet.java @@ -0,0 +1,35 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.lanternsoftware.currentmonitor.context.Globals; +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public abstract class SecureServlet extends CMServlet { + @Override + protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) { + AuthCode authCode = Globals.dao.decryptAuthCode(_req.getHeader("auth_code")); + if (authCode == null) { + _rep.setStatus(401); + return; + } + get(authCode, _req, _rep); + } + + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + } + + @Override + protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) { + AuthCode authCode = Globals.dao.decryptAuthCode(_req.getHeader("auth_code")); + if (authCode == null) { + _rep.setStatus(401); + return; + } + post(authCode, _req, _rep); + } + + protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java new file mode 100644 index 0000000..8bf2ed2 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java @@ -0,0 +1,45 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.lanternsoftware.currentmonitor.context.Globals; +import com.lanternsoftware.datamodel.currentmonitor.Account; +import com.lanternsoftware.datamodel.currentmonitor.SignupResponse; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.email.EmailValidator; +import com.lanternsoftware.util.servlet.BasicAuth; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/signup") +public class SignupServlet extends CMServlet { + @Override + protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) { + BasicAuth auth = new BasicAuth(_req); + Account acct = Globals.dao.getAccountByUsername(auth.getUsername()); + if (acct != null) { + jsonResponse(_rep, SignupResponse.error("An account for " + auth.getUsername() + " already exists")); + return; + } + if (!EmailValidator.getInstance().isValid(auth.getUsername())) { + jsonResponse(_rep, SignupResponse.error(auth.getUsername() + " is not a valid email address")); + return; + } + if (NullUtils.length(auth.getPassword()) < 8) { + jsonResponse(_rep, SignupResponse.error("Your password must be at least 8 characters long")); + return; + } + if (NullUtils.isEqual("password", auth.getPassword())) { + jsonResponse(_rep, SignupResponse.error("Seriously? \"password\"? Come on.")); + return; + } + acct = new Account(); + acct.setUsername(auth.getUsername()); + acct.setPassword(auth.getPassword()); + Globals.dao.putAccount(acct); + String authCode = Globals.dao.authenticateAccount(auth.getUsername(), auth.getPassword()); + jsonResponse(_rep, SignupResponse.success(authCode)); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/UpdateServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/UpdateServlet.java new file mode 100644 index 0000000..b5ab090 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/UpdateServlet.java @@ -0,0 +1,22 @@ +package com.lanternsoftware.currentmonitor.servlet; + +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.dao.DaoSerializer; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; +import java.io.File; + +@WebServlet("/update/*") +public class UpdateServlet extends CMServlet { + @Override + protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) { + if (isPath(_req, 0, "version")) + setResponseEntity(_rep, MediaType.APPLICATION_OCTET_STREAM, DaoSerializer.toZipBson(DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "release" + File.separator + "version.json")))); + else + setResponseEntity(_rep, MediaType.APPLICATION_OCTET_STREAM, ResourceLoader.loadFile(LanternFiles.OPS_PATH + "release" + File.separator + "lantern-currentmonitor.jar")); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/resources/logback.xml b/currentmonitor/lantern-service-currentmonitor/src/main/resources/logback.xml new file mode 100644 index 0000000..ece41c5 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + + + ${log.pattern} + + + + + + + + + + \ No newline at end of file diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/webapp/WEB-INF/web.xml b/currentmonitor/lantern-service-currentmonitor/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..8f6b092 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,9 @@ + + + + + com.lanternsoftware.currentmonitor.context.Globals + + \ No newline at end of file diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAccount.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAccount.java new file mode 100644 index 0000000..fb5faa4 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAccount.java @@ -0,0 +1,25 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao; +import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao; +import com.lanternsoftware.datamodel.currentmonitor.Account; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.dao.mongo.MongoConfig; + +import java.util.Arrays; + +public class CreateAccount { + public static void main(String[] args) { + CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg")); + Account account = new Account(); + account.setId(1); + account.setPassword("*redacted*"); + + account.setId(2); + account.setUsername("admin@lanternsoftware.com"); + account.setPassword("*redacted*"); + + dao.putAccount(account); + dao.shutdown(); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthCode.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthCode.java new file mode 100644 index 0000000..93ec941 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthCode.java @@ -0,0 +1,15 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.cryptography.AESTool; +import com.lanternsoftware.util.dao.DaoSerializer; + +public class CreateAuthCode { + private static final AESTool aes = new AESTool(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "authKey.dat")); + + public static void main(String[] args) { + System.out.println(aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(100, null)))); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthKey.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthKey.java new file mode 100644 index 0000000..e67192b --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthKey.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.cryptography.AESTool; + +public class CreateAuthKey { + public static void main(String[] args) { + ResourceLoader.writeFile(LanternFiles.OPS_PATH + "authKey.dat", AESTool.generateRandomSecretKey().getEncoded()); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateBreakers.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateBreakers.java new file mode 100644 index 0000000..f899866 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateBreakers.java @@ -0,0 +1,241 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao; +import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao; +import com.lanternsoftware.datamodel.currentmonitor.Breaker; +import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup; +import com.lanternsoftware.datamodel.currentmonitor.BreakerHub; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPanel; +import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity; +import com.lanternsoftware.datamodel.currentmonitor.Meter; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.mongo.MongoConfig; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class CreateBreakers { + public static void main(String[] args) { + CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg")); + +/* Breaker bf1 = new Breaker("Solar A", 2, 20, 0, 1, 50, 1.6); + bf1.setPolarity(BreakerPolarity.SOLAR); + Breaker bf2 = new Breaker("Solar B", 2, 18, 0, 2, 50, 1.6); + bf2.setPolarity(BreakerPolarity.SOLAR); + Breaker bf3 = new Breaker("Septic Agitator", 2, 11, 0, 3, 20, 1.6); + Breaker bf4 = new Breaker("Garage/Guest Baths", 2, 5, 0, 4, 20, 1.6); + Breaker bf5 = new Breaker("Office", 2, 2, 0, 5, 20, 1.6); + Breaker bf6 = new Breaker("Upstairs Furnace R-A", 1, 2, 0, 6, 50, 1.6); + Breaker bf7 = new Breaker("Upstairs Furnace R-B", 1, 4, 0, 7, 50, 1.6); + Breaker bf8 = new Breaker("Upstairs Furnace R-C", 1, 6, 0, 8, 30, 1.6); + Breaker bf9 = new Breaker("Upstairs Furnace R-D", 1, 8, 0, 9, 30, 1.6); + Breaker bf10 = new Breaker("Upstairs Furnace HP-A", 1, 1, 0, 10, 30, 1.6); + Breaker bf11 = new Breaker("Upstairs Furnace HP-B", 1, 3, 0, 11, 30, 1.6); + Breaker bf12 = new Breaker("Dryer A", 2, 8, 0, 12, 30, 1.6); + Breaker bf13 = new Breaker("Dryer B", 2, 10, 0, 13, 30, 1.6); + Breaker bf2_1 = new Breaker("Main Furnace R-A", 0, 6, 1, 1, 50, 1.6); + Breaker bf2_2 = new Breaker("Main Furnace R-B", 0, 8, 1, 2, 50, 1.6); + Breaker bf2_3 = new Breaker("Main Furnace R-C", 0, 2, 1, 3, 50, 1.6); + Breaker bf2_4 = new Breaker("Main Furnace R-D", 0, 4, 1, 4, 50, 1.6); + Breaker bf2_5 = new Breaker("Main Furnace HP-A", 0, 1, 1, 5, 30, 1.6); + Breaker bf2_6 = new Breaker("Main Furnace HP-B", 0, 3, 1, 6, 30, 1.6); + Breaker bf2_7 = new Breaker("Hot Water Heater A", 0, 5, 1, 7, 30, 1.6); + Breaker bf2_8 = new Breaker("Hot Water Heater B", 0, 7, 1, 8, 30, 1.6); + Breaker bf2_9 = new Breaker("Basement HP-A", 1, 13, 1, 9, 30, 1.6); + Breaker bf2_10 = new Breaker("Basement HP-B", 1, 15, 1, 10, 30, 1.6); + Breaker bf2_11 = new Breaker("Oven A", 2, 12, 1, 11, 50, 1.6); + Breaker bf2_12 = new Breaker("Oven B", 2, 14, 1, 12, 50, 1.6); + Breaker bf2_13 = new Breaker("Master Bathroom", 3, 9, 2, 5, 20, 1.6); + Breaker bf2_14 = new Breaker("Refrigerator", 2, 6, 1, 14, 20, 1.6); + bf2_14.setSpaceTandemB(6); + Breaker bf2_15 = new Breaker("Master Bedroom", 2, 1, 1, 15, 20, 1.6); + BreakerGroup basementHP = new BreakerGroup("6", "Heat Pump", Arrays.asList(bf2_9, bf2_10)); + BreakerGroup basementCC = new BreakerGroup("3", "Basement", Arrays.asList(basementHP), null); + BreakerGroup mainHP = new BreakerGroup("5", "Heat Pump", Arrays.asList(bf2_5, bf2_6)); + BreakerGroup mainR = new BreakerGroup("4", "Air Handler/Resistive", Arrays.asList(bf2_1, bf2_2, bf2_3, bf2_4)); + BreakerGroup mainCC = new BreakerGroup("2", "Main Floor", Arrays.asList(mainHP, mainR), null); + BreakerGroup upstairsHP = new BreakerGroup("34", "Heat Pump", Arrays.asList(bf10, bf11)); + BreakerGroup upstairsR = new BreakerGroup("35", "Air Handler/Resistive", Arrays.asList(bf6, bf7, bf8, bf9)); + BreakerGroup upstairsCC = new BreakerGroup("36", "Upstairs", Arrays.asList(upstairsHP, upstairsR), null); + BreakerGroup cc = new BreakerGroup("1", "Climate Control", Arrays.asList(mainCC, upstairsCC, basementCC), null); + BreakerGroup hotWater = new BreakerGroup("7", "Hot Water Heater", Arrays.asList(bf2_7, bf2_8)); + BreakerGroup oven = new BreakerGroup("8", "Oven/Cooktop", Arrays.asList(bf2_11, bf2_12)); + BreakerGroup masterBR = new BreakerGroup("9", "Master Bedroom", Arrays.asList(bf2_13, bf2_15)); + BreakerGroup fridge = new BreakerGroup("10", "Refrigerator", Arrays.asList(bf2_14)); + BreakerGroup solar = new BreakerGroup("11", "Solar", Arrays.asList(bf1, bf2)); + BreakerGroup septic = new BreakerGroup("13", "Septic Aerator", Arrays.asList(bf3)); + BreakerGroup garage = new BreakerGroup("14", "Garage/Guest Baths", Arrays.asList(bf4)); + BreakerGroup office = new BreakerGroup("15", "Office", Arrays.asList(bf5)); + BreakerGroup dryer = new BreakerGroup("37", "Dryer", Arrays.asList(bf12, bf13)); + Breaker bf3_1 = new Breaker("Hub 2 Port 1", 3, 1, 2, 1, 20, 1.6); + Breaker bf3_2 = new Breaker("Hub 2 Port 2", 3, 3, 2, 2, 20, 1.6); + Breaker bf3_3 = new Breaker("Hub 2 Port 3", 3, 5, 2, 3, 20, 1.6); + Breaker bf3_4 = new Breaker("Hub 2 Port 4", 3, 7, 2, 4, 20, 1.6); + Breaker bf3_6 = new Breaker("Hub 2 Port 6", 3, 11, 2, 6, 20, 1.6); + Breaker bf3_7 = new Breaker("Hub 2 Port 7", 3, 13, 2, 7, 20, 1.6); + Breaker bf3_8 = new Breaker("Hub 2 Port 8", 3, 15, 2, 8, 20, 1.6); + Breaker bf3_9 = new Breaker("Hub 2 Port 9", 3, 2, 2, 9, 20, 1.6); + Breaker bf3_10 = new Breaker("Hub 2 Port 10", 3, 4, 2, 10, 20, 1.6); + Breaker bf3_12 = new Breaker("Hub 2 Port 12", 3, 6, 2, 12, 20, 1.6); + Breaker bf3_13 = new Breaker("Hub 2 Port 13", 3, 8, 2, 13, 20, 1.6); + Breaker bf3_14 = new Breaker("Hub 2 Port 14", 3, 10, 2, 14, 20, 1.6); + BreakerGroup g1 = new BreakerGroup("17", "Living Room/Printer/Outdoor Lights", Arrays.asList(bf3_1)); + BreakerGroup g2 = new BreakerGroup("18", "Dishwasher/Disposal/Sink Lights", Arrays.asList(bf3_2)); + BreakerGroup g3 = new BreakerGroup("19", "Microwave", Arrays.asList(bf3_3)); + BreakerGroup g4 = new BreakerGroup("20", "Kitchen Outlets", Arrays.asList(bf3_4)); +// BreakerGroup g5 = new BreakerGroup("21", "Hub 2 Port 5", Arrays.asList(bf3_5)); + BreakerGroup g6 = new BreakerGroup("22", "Mini Fridge", Arrays.asList(bf3_6)); + BreakerGroup g7 = new BreakerGroup("23", "Basement Lights/Outlets", Arrays.asList(bf3_7)); + BreakerGroup g8 = new BreakerGroup("24", "Theatre", Arrays.asList(bf3_8)); + BreakerGroup g9 = new BreakerGroup("25", "Sunroom Fan/Outside Floods", Arrays.asList(bf3_9)); + BreakerGroup g10 = new BreakerGroup("26", "Radon/Deep Freezer", Arrays.asList(bf3_10)); +// BreakerGroup g11 = new BreakerGroup("27", "Hub 2 Port 11", Arrays.asList(bf3_11)); + BreakerGroup g12 = new BreakerGroup("28", "Kitchen Lights", Arrays.asList(bf3_12)); + BreakerGroup g13 = new BreakerGroup("29", "Router/Networking", Arrays.asList(bf3_13)); + BreakerGroup g14 = new BreakerGroup("30", "Half Bath/Utility/Garage Lights", Arrays.asList(bf3_14)); +// BreakerGroup g15 = new BreakerGroup("31", "Bar Outlets", Arrays.asList(bf3_15)); + BreakerGroup kitchen = new BreakerGroup("33", "Kitchen", Arrays.asList(oven, fridge, g2, g3, g4, g12), null); + + Breaker b4_1 = new Breaker("Hub 3 Port 1", 4, 1, 3, 1, 20, 1.6); + Breaker b4_2 = new Breaker("Hub 3 Port 2", 4, 2, 3, 2, 30, 1.6); + Breaker b4_3 = new Breaker("Hub 3 Port 3", 4, 3, 3, 3, 50, 1.6); + Breaker b4_4 = new Breaker("Hub 3 Port 4", 4, 4, 3, 4, 20, 1.6); + Breaker b4_5 = new Breaker("Hub 3 Port 5", 4, 5, 3, 5, 20, 1.6); + Breaker b4_6 = new Breaker("Hub 3 Port 6", 4, 6, 3, 6, 20, 1.6); + Breaker b4_7 = new Breaker("Hub 3 Port 7", 4, 7, 3, 7, 20, 1.6); + Breaker b4_8 = new Breaker("Hub 3 Port 8", 4, 8, 3, 8, 20, 1.6); + Breaker b4_9 = new Breaker("Hub 3 Port 9", 4, 9, 3, 9, 20, 1.6); + Breaker b4_10 = new Breaker("Hub 3 Port 10", 4, 10, 3, 10, 20, 1.6); + Breaker b4_11 = new Breaker("Hub 3 Port 11", 4, 11, 3, 11, 20, 1.6); + Breaker b4_12 = new Breaker("Hub 3 Port 12", 4, 12, 3, 12, 20, 1.6); + Breaker b4_13 = new Breaker("Hub 3 Port 13", 4, 13, 3, 13, 20, 1.6); + Breaker b4_14 = new Breaker("Hub 3 Port 14", 4, 14, 3, 14, 20, 1.6); + Breaker b4_15 = new Breaker("Hub 3 Port 15", 4, 15, 3, 15, 20, 1.6); + BreakerGroup g4_1 = new BreakerGroup("41", "Hub 3 Port 1", Arrays.asList(b4_1)); + BreakerGroup g4_2 = new BreakerGroup("42", "Hub 3 Port 2", Arrays.asList(b4_2)); + BreakerGroup g4_3 = new BreakerGroup("43", "Hub 3 Port 3", Arrays.asList(b4_3)); + BreakerGroup g4_4 = new BreakerGroup("44", "Hub 3 Port 4", Arrays.asList(b4_4)); + BreakerGroup g4_5 = new BreakerGroup("45", "Hub 3 Port 5", Arrays.asList(b4_5)); + BreakerGroup g4_6 = new BreakerGroup("46", "Hub 3 Port 6", Arrays.asList(b4_6)); + BreakerGroup g4_7 = new BreakerGroup("47", "Hub 3 Port 7", Arrays.asList(b4_7)); + BreakerGroup g4_8 = new BreakerGroup("48", "Hub 3 Port 8", Arrays.asList(b4_8)); + BreakerGroup g4_9 = new BreakerGroup("49", "Hub 3 Port 9", Arrays.asList(b4_9)); + BreakerGroup g4_10 = new BreakerGroup("50", "Hub 3 Port 10", Arrays.asList(b4_10)); + BreakerGroup g4_11 = new BreakerGroup("51", "Hub 3 Port 11", Arrays.asList(b4_11)); + BreakerGroup g4_12 = new BreakerGroup("52", "Hub 3 Port 12", Arrays.asList(b4_12)); + BreakerGroup g4_13 = new BreakerGroup("53", "Hub 3 Port 13", Arrays.asList(b4_13)); + BreakerGroup g4_14 = new BreakerGroup("54", "Hub 3 Port 14", Arrays.asList(b4_14)); + BreakerGroup g4_15 = new BreakerGroup("55", "Hub 3 Port 15", Arrays.asList(b4_15)); + BreakerGroup debug = new BreakerGroup("40", "Debug Hub 3", Arrays.asList(g4_1, g4_2, g4_3), null); +// BreakerGroup debug = new BreakerGroup("40", "Debug Hub 3", Arrays.asList(g4_1, g4_2, g4_3, g4_4, g4_5, g4_6, g4_7, g4_8, g4_9, g4_10, g4_11, g4_12, g4_13, g4_14, g4_15), null); +// BreakerGroup debug = new BreakerGroup("40", "Debug Hub 3", Arrays.asList(b4_1)); + + + BreakerGroup house = new BreakerGroup("0", "*redacted*", Arrays.asList(solar, cc, hotWater, dryer, septic, masterBR, garage, office, kitchen, g1, g6, g7, g8, g9, g10, g13, g14, debug), null); + BreakerConfig config = new BreakerConfig(Collections.singletonList(house)); + CollectionUtils.edit(config.getAllBreakerGroups(), _g->_g.setAccountId(1)); + CollectionUtils.edit(config.getAllBreakers(), _b->{ + _b.setAccountId(1); + _b.setCalibrationFactor(1.0); + }); + b4_1.setCalibrationFactor(1.215); + b4_2.setCalibrationFactor(1.215); + b4_3.setCalibrationFactor(1.215); + config.setAccountId(1); + BreakerHub hub0 = new BreakerHub(); + hub0.setHub(0); + hub0.setVoltageCalibrationFactor(0.4587); + hub0.setFrequency(60); + BreakerHub hub1 = new BreakerHub(); + hub1.setHub(1); + hub1.setVoltageCalibrationFactor(0.439); + hub1.setFrequency(60); + BreakerHub hub2 = new BreakerHub(); + hub2.setHub(2); + hub2.setVoltageCalibrationFactor(0.3535); + hub2.setFrequency(60); + BreakerHub hub3 = new BreakerHub(); + hub3.setHub(3); + hub3.setVoltageCalibrationFactor(0.419); + hub3.setFrequency(60); + config.setBreakerHubs(Arrays.asList(hub0, hub1, hub2, hub3)); + + Meter heatMeter = new Meter(); + heatMeter.setAccountId(1); + heatMeter.setIndex(0); + heatMeter.setName("Heat"); + + Meter mainMeter = new Meter(); + mainMeter.setAccountId(1); + mainMeter.setIndex(1); + mainMeter.setName("Main/Solar"); + + config.setMeters(CollectionUtils.asArrayList(heatMeter, mainMeter)); + + BreakerPanel heat1 = new BreakerPanel(); + heat1.setAccountId(1); + heat1.setIndex(0); + heat1.setMeter(heatMeter.getIndex()); + heat1.setName("Heat 1"); + heat1.setSpaces(20); + + BreakerPanel heat2 = new BreakerPanel(); + heat2.setAccountId(1); + heat2.setIndex(1); + heat2.setMeter(heatMeter.getIndex()); + heat2.setName("Heat 2"); + heat2.setSpaces(20); + + BreakerPanel main = new BreakerPanel(); + main.setAccountId(1); + main.setIndex(2); + main.setMeter(mainMeter.getIndex()); + main.setName("Main"); + main.setSpaces(20); + + BreakerPanel sub = new BreakerPanel(); + sub.setAccountId(1); + sub.setIndex(3); + sub.setMeter(mainMeter.getIndex()); + sub.setName("Sub-Panel"); + sub.setSpaces(20); + + config.setPanels(CollectionUtils.asArrayList(heat1, heat2, main, sub)); + + Map panelToMeter = CollectionUtils.transformToMap(config.getPanels(), BreakerPanel::getIndex, BreakerPanel::getMeter); + CollectionUtils.edit(config.getAllBreakers(), _b->_b.setMeter(DaoSerializer.toInteger(panelToMeter.get(_b.getPanel()))));*/ + + BreakerConfig config = dao.getConfig(1); +// BreakerConfig config = DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "breakerconfig_backup_210107.json"), BreakerConfig.class); +// ResourceLoader.writeFile(LanternFiles.OPS_PATH + "breakerconfig_backup_210107.json", DaoSerializer.toJson(config)); +// CollectionUtils.edit(config.getAllBreakerGroups(), _g->{ +// if (NullUtils.isEmpty(_g.getName())) { +// Breaker b = CollectionUtils.getFirst(_g.getBreakers()); +// if (b != null) +// _g.setName(String.format("Panel %d, %s", b.getPanel(), b.getName())); +// } +// }); +// for (BreakerGroup group : CollectionUtils.filter(config.getAllBreakerGroups(), _g->NullUtils.isEmpty(_g.getId()))) { +// List ids = CollectionUtils.transform(config.getAllBreakerGroupIds(), NullUtils::toInteger); +// group.setId(String.valueOf(CollectionUtils.getLargest(ids) + 1)); +// } +// BreakerGroup root = CollectionUtils.getFirst(config.getBreakerGroups()); +// root.getSubGroups().removeIf(_g->_g.getName().equals("Debug Hub 3")); +// config.removeInvalidGroups(); +// for (BreakerGroup group : config.getAllBreakerGroups()) { +// if (NullUtils.isEmpty(group.getId())) { +// List ids = CollectionUtils.transform(config.getAllBreakerGroupIds(), NullUtils::toInteger); +// group.setId(String.valueOf(CollectionUtils.getLargest(ids) + 1)); +// } +// } + dao.putConfig(config); + dao.shutdown(); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateMongoConfig.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateMongoConfig.java new file mode 100644 index 0000000..64d6628 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateMongoConfig.java @@ -0,0 +1,10 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.dao.mongo.MongoConfig; + +public class CreateMongoConfig { + public static void main(String[] args) { + new MongoConfig("localhost", "*redacted*", "*redacted*", "CURRENT_MONITOR").saveToDisk(LanternFiles.OPS_PATH + "mongo.cfg"); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorSerializers.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorSerializers.java new file mode 100644 index 0000000..fdb0ecd --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorSerializers.java @@ -0,0 +1,13 @@ +package com.lanternsoftware.currentmonitor; + + +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.dao.generator.DaoSerializerGenerator; +import com.lanternsoftware.util.dao.generator.SwiftModelGenerator; + +public class CurrentMonitorSerializers { + public static void main(String[] args) { + DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_PATH + "currentmonitor", true, null); + SwiftModelGenerator.generateModel(LanternFiles.SOURCE_PATH + "currentmonitor", LanternFiles.SOURCE_PATH + "iOS"); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/MigrateSummaries.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/MigrateSummaries.java new file mode 100644 index 0000000..f5734b7 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/MigrateSummaries.java @@ -0,0 +1,55 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao; +import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupSummary; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.dao.mongo.MongoConfig; + +public class MigrateSummaries { + public static void main(String[] args) { + CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg")); +// TimeZone tz = TimeZone.getTimeZone("America/Chicago"); +// List summaries = dao.getProxy().query(BreakerGroupEnergy.class, null); +// CollectionUtils.edit(summaries, _s->CollectionUtils.edit(_s.getAllGroups(), _t->_t.setAccountId(1))); +// dao.getProxy().save(summaries); + + dao.getProxy().save(CollectionUtils.transform(dao.getProxy().queryAll(BreakerGroupEnergy.class), BreakerGroupSummary::new)); + +// List readings = null; +// while ((readings == null) || !readings.isEmpty()) { +// readings = dao.getProxy().query(BreakerPower.class, new DaoQuery("account_id", new DaoQuery("$ne", 1)), null, null, 0, 1000000); +// System.out.println("Adding account id to " + readings.size() + " power readings"); +// CollectionUtils.edit(readings, _s -> _s.setAccountId(1)); +// dao.getProxy().save(readings); +// } +// +// List archives = null; +// while ((archives == null) || !archives.isEmpty()) { +// archives = dao.getProxy().query(BreakerPowerArchive.class, new DaoQuery("account_id", new DaoQuery("$ne", 1)), null, null, 0, 50); +// System.out.println("Adding account id to " + archives.size() + " archives"); +// CollectionUtils.edit(archives, _s -> _s.setAccountId(1)); +// dao.getProxy().save(archives); +// } + +// List readings = CollectionUtils.filter(dao.getBreakerPower(Arrays.asList("0-1", "0-2"), DateUtils.date(6,26,2020, 17, 0, 0, 0, tz), DateUtils.date(6,26,2020, 22, 0, 0, 0, tz)), _p->_p.getPower() > 0.0); +// CollectionUtils.edit(readings, _p->_p.setPower(-_p.getPower())); +// dao.getProxy().save(readings); + +// Map> dups = CollectionUtils.transformToMultiMap(dao.getBreakerPower(Arrays.asList("2-1","2-2","2-3","2-4","2-5","2-6","2-7","2-8","2-9","2-10","2-11","2-12","2-13","2-14","2-15"), DateUtils.date(6,26,2020, 17, 0, 0, 0, tz), DateUtils.date(6,26,2020, 18, 0, 0, 0, tz)), _p->_p.getKey()+_p.getReadTime().getTime()); +// for (List dup : dups.values()) { +// if (dup.size() > 1) { +// CollectionUtils.removeFirst(dup); +// dao.getProxy().delete(BreakerPower.class, DaoQuery.in("_id", CollectionUtils.transform(dup, BreakerPower::getId))); +// } +// } + +// List summaries = dao.getProxy().query(BreakerGroupEnergy.class, null); +// ResourceLoader.writeFile(LanternFiles.OPS_PATH + "summaryBackup.json", DaoSerializer.toJson(DaoSerializer.toDaoEntities(summaries))); +// for (BreakerGroupEnergy summary : summaries) { +// dao.getProxy().save(summary); +// } + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java new file mode 100644 index 0000000..723c6f9 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java @@ -0,0 +1,99 @@ +package com.lanternsoftware.currentmonitor; + +import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao; +import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao; +import com.lanternsoftware.datamodel.currentmonitor.Breaker; +import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy; +import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupSummary; +import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode; +import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.DateUtils; +import com.lanternsoftware.util.DebugTimer; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.dao.DaoQuery; +import com.lanternsoftware.util.dao.mongo.MongoConfig; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +public class RebuildSummaries { + public static void main(String[] args) { + int accountId = 1; + CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg")); + TimeZone tz = TimeZone.getTimeZone("America/Chicago"); + Date start = DateUtils.date(1, 7, 2021, tz); +// Date start = DateUtils.getMidnightBeforeNow(tz); + Date end = DateUtils.getMidnightAfterNow(tz); + Map> days = CollectionUtils.transformToMultiMap(dao.getProxy().query(HubPowerMinute.class, new DaoQuery("account_id", accountId).andBetweenInclusiveExclusive("minute", (int)(start.getTime()/60000), (int)(end.getTime()/60000))), _m->DateUtils.getMidnightBefore(_m.getMinuteAsDate(), tz)); + BreakerConfig config = dao.getConfig(accountId); + BreakerGroup root = CollectionUtils.getFirst(config.getBreakerGroups()); + Map breakers = CollectionUtils.transformToMap(root.getAllBreakers(), Breaker::getKey); + Map breakerKeyToGroup = new HashMap<>(); + for (BreakerGroup group : root.getAllBreakerGroups()) { + for (Breaker b : group.getAllBreakers()) { + breakerKeyToGroup.put(b.getKey(), group); + } + } + + for (Map.Entry> day : days.entrySet()) { + BreakerGroupEnergy energy = null; + DebugTimer timer = new DebugTimer("Time to rebuild one day"); + Map> minutes = CollectionUtils.transformToMultiMap(day.getValue(), HubPowerMinute::getMinute); + for (List minute : minutes.values()) { + if (energy == null) + energy = new BreakerGroupEnergy(root, minute, EnergyBlockViewMode.DAY, day.getKey(), tz); + else + energy.addEnergy(breakers, breakerKeyToGroup, minute, tz); + } + timer.stop(); + if (energy != null) + dao.putBreakerGroupEnergy(energy); + } + dao.updateSummaries(root, days.keySet(), tz); + +// List summaries = dao.getProxy().query(BreakerGroupEnergy.class, null); +// CollectionUtils.edit(summaries, _s->CollectionUtils.edit(_s.getAllGroups(), _t->_t.setAccountId(1))); +// dao.getProxy().save(summaries); + +// List readings = null; +// while ((readings == null) || !readings.isEmpty()) { +// readings = dao.getProxy().query(BreakerPower.class, new DaoQuery("account_id", new DaoQuery("$ne", 1)), null, null, 0, 1000000); +// System.out.println("Adding account id to " + readings.size() + " power readings"); +// CollectionUtils.edit(readings, _s -> _s.setAccountId(1)); +// dao.getProxy().save(readings); +// } +// +// List archives = null; +// while ((archives == null) || !archives.isEmpty()) { +// archives = dao.getProxy().query(BreakerPowerArchive.class, new DaoQuery("account_id", new DaoQuery("$ne", 1)), null, null, 0, 50); +// System.out.println("Adding account id to " + archives.size() + " archives"); +// CollectionUtils.edit(archives, _s -> _s.setAccountId(1)); +// dao.getProxy().save(archives); +// } + +// List readings = CollectionUtils.filter(dao.getBreakerPower(Arrays.asList("0-1", "0-2"), DateUtils.date(6,26,2020, 17, 0, 0, 0, tz), DateUtils.date(6,26,2020, 22, 0, 0, 0, tz)), _p->_p.getPower() > 0.0); +// CollectionUtils.edit(readings, _p->_p.setPower(-_p.getPower())); +// dao.getProxy().save(readings); + +// Map> dups = CollectionUtils.transformToMultiMap(dao.getBreakerPower(Arrays.asList("2-1","2-2","2-3","2-4","2-5","2-6","2-7","2-8","2-9","2-10","2-11","2-12","2-13","2-14","2-15"), DateUtils.date(6,26,2020, 17, 0, 0, 0, tz), DateUtils.date(6,26,2020, 18, 0, 0, 0, tz)), _p->_p.getKey()+_p.getReadTime().getTime()); +// for (List dup : dups.values()) { +// if (dup.size() > 1) { +// CollectionUtils.removeFirst(dup); +// dao.getProxy().delete(BreakerPower.class, DaoQuery.in("_id", CollectionUtils.transform(dup, BreakerPower::getId))); +// } +// } + +// List summaries = dao.getProxy().query(BreakerGroupEnergy.class, null); +// ResourceLoader.writeFile(LanternFiles.OPS_PATH + "summaryBackup.json", DaoSerializer.toJson(DaoSerializer.toDaoEntities(summaries))); +// for (BreakerGroupEnergy summary : summaries) { +// dao.getProxy().save(summary); +// } + dao.shutdown(); + } +} diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/resources/logback.xml b/currentmonitor/lantern-service-currentmonitor/src/test/resources/logback.xml new file mode 100644 index 0000000..ece41c5 --- /dev/null +++ b/currentmonitor/lantern-service-currentmonitor/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + + + ${log.pattern} + + + + + + + + + + \ No newline at end of file diff --git a/currentmonitor/pom.xml b/currentmonitor/pom.xml new file mode 100644 index 0000000..db89212 --- /dev/null +++ b/currentmonitor/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + pom + com.lanternsoftware.currentmonitor + currentmonitor + currentmonitor + 1.0.0 + + + 1.8 + 1.8 + + + + lantern-currentmonitor + lantern-dataaccess-currentmonitor + lantern-datamodel-currentmonitor + lantern-service-currentmonitor + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..faaced3 --- /dev/null +++ b/pom.xml @@ -0,0 +1,19 @@ + + 4.0.0 + pom + com.lanternsoftware + LanternPowerMonitor + LanternPowerMonitor + 1.0.0 + + + 1.8 + 1.8 + + + + currentmonitor + util + zwave + + \ No newline at end of file diff --git a/util/lantern-util-common/pom.xml b/util/lantern-util-common/pom.xml new file mode 100644 index 0000000..dc2d50c --- /dev/null +++ b/util/lantern-util-common/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + com.lanternsoftware.util + lantern-util-common + lantern-util-common + 1.0.0 + jar + + + + commons-io + commons-io + 2.4 + + + commons-codec + commons-codec + 1.8 + + + org.slf4j + slf4j-api + 1.7.29 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar + + + + + + + diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/CollectionUtils.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/CollectionUtils.java new file mode 100644 index 0000000..5333a12 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/CollectionUtils.java @@ -0,0 +1,935 @@ +package com.lanternsoftware.util; + +import java.lang.reflect.Array; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.RandomAccess; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class CollectionUtils { + public static T getFirst(Collection _collObjects) { + if ((_collObjects != null) && !_collObjects.isEmpty()) + return _collObjects.iterator().next(); + return null; + } + + public static T removeFirst(Collection _objects) { + if (_objects == null) + return null; + Iterator iter = _objects.iterator(); + if (iter.hasNext()) { + T t = iter.next(); + iter.remove(); + return t; + } + return null; + } + + public static T removeOne(Collection _coll, IQualifier _qualifier) { + if ((_coll == null) || (_qualifier == null)) + return null; + Iterator iter = _coll.iterator(); + while (iter.hasNext()) { + T t = iter.next(); + if (_qualifier.qualifies(t)) { + iter.remove(); + return t; + } + } + return null; + } + + public static List removeAll(Collection _coll, IQualifier _qualifier) { + if ((_coll == null) || (_qualifier == null)) + return null; + List ret = new ArrayList<>(); + Iterator iter = _coll.iterator(); + while (iter.hasNext()) { + T t = iter.next(); + if (_qualifier.qualifies(t)) { + iter.remove(); + ret.add(t); + } + } + return ret; + } + + public static T getLast(Collection _collObjects) { + if ((_collObjects == null) || _collObjects.isEmpty()) + return null; + if (_collObjects instanceof RandomAccess) { + List listObjects = (List) _collObjects; + return listObjects.get(listObjects.size() - 1); + } + if (_collObjects instanceof Deque) { + Deque listObjects = (Deque) _collObjects; + return listObjects.getLast(); + } + T t = null; + Iterator iter = _collObjects.iterator(); + while (iter.hasNext()) { + t = iter.next(); + } + return t; + } + + public static T getFirst(T[] _arrObjects) { + if (size(_arrObjects) > 0) + return _arrObjects[0]; + return null; + } + + public static boolean isEmpty(Collection _collObjects) { + return (_collObjects == null) || _collObjects.isEmpty(); + } + + public static boolean isNotEmpty(Collection _collObjects) { + return (_collObjects != null) && !_collObjects.isEmpty(); + } + + public static boolean isEmpty(Map _map) { + return (_map == null) || _map.isEmpty(); + } + + public static boolean isNotEmpty(Map _map) { + return (_map != null) && !_map.isEmpty(); + } + + public static Collection makeNotNull(Collection _collObjects) { + if (_collObjects != null) + return _collObjects; + return new ArrayList(); + } + + public static List makeNotNull(List _listObjects) { + if (_listObjects != null) + return _listObjects; + return new ArrayList(); + } + + public static Map makeNotNull(Map _mapObjects) { + if (_mapObjects != null) + return _mapObjects; + return new HashMap(); + } + + public static int size(Collection _collObjects) { + if (_collObjects == null) + return 0; + return _collObjects.size(); + } + + public static int size(Map _collObjects) { + if (_collObjects == null) + return 0; + return _collObjects.size(); + } + + public static int size(T[] _arr) { + if (_arr == null) + return 0; + return _arr.length; + } + + public static T get(List _list, int _idx) { + if (_list == null) + return null; + if ((_idx < 0) || (_idx >= _list.size())) + return null; + return _list.get(_idx); + } + + public static T get(T[] _t, int _idx) { + if (_t == null) + return null; + if ((_idx < 0) || (_idx >= _t.length)) + return null; + return _t[_idx]; + } + + public static List get(Map _map, Collection _keys) { + if (_keys == null) + return null; + List ret = new ArrayList<>(); + for (T t : _keys) { + ret.add(_map.get(t)); + } + return ret; + } + + public static T last(T[] _arr) { + if (_arr == null || _arr.length == 0) + return null; + return _arr[_arr.length - 1]; + } + + public static boolean contains(Collection _coll, T _t) { + if (_coll == null) + return false; + return _coll.contains(_t); + } + + public static boolean containsAny(Collection _coll, T... _t) { + return containsAny(_coll, asArrayList(_t)); + } + + public static boolean containsAny(Collection _coll, Collection _values) { + if (size(_values) == 0) + return false; + for (T t : _values) { + if (contains(_coll, t)) + return true; + } + return false; + } + + public static boolean containsAll(Collection _coll, T... _t) { + return containsAll(_coll, asArrayList(_t)); + } + + public static boolean containsAll(Collection _coll, Collection _values) { + if (size(_values) == 0) + return true; + for (T t : _values) { + if (!contains(_coll, t)) + return false; + } + return true; + } + + public static boolean containsNone(Collection _coll, T... _t) { + return containsNone(_coll, asArrayList(_t)); + } + + public static boolean containsNone(Collection _coll, Collection _values) { + if (size(_values) == 0) + return true; + for (T t : _values) { + if (contains(_coll, t)) + return false; + } + return true; + } + + public static List merge(Collection> _colls) { + List list = new ArrayList<>(); + for (List coll : makeNotNull(_colls)) { + list.addAll(coll); + } + return list; + } + + public static List merge(Collection _coll1, Collection _coll2) { + List list = new ArrayList<>(makeNotNull(_coll1)); + list.addAll(makeNotNull(_coll2)); + return list; + } + + public static List aggregate(Collection _coll, IAggregator _aggregator) { + List list = new ArrayList<>(); + for (T t : makeNotNull(_coll)) { + List vs = _aggregator.aggregate(t); + if (vs != null) + list.addAll(vs); + } + return list; + } + + public static Map aggregateToMap(Collection _coll, IAggregator _aggregator, ITransformer _keyTransformer) { + return transformToMap(aggregate(_coll, _aggregator), _keyTransformer); + } + + public static byte[] merge(byte[]... _arrs) { + int iSize = 0; + for (byte[] curArr : _arrs) { + if (curArr != null) + iSize += curArr.length; + } + byte[] arr = new byte[iSize]; + int offset = 0; + for (byte[] curArr : _arrs) { + if (curArr == null) + continue; + System.arraycopy(curArr, 0, arr, offset, curArr.length); + offset += curArr.length; + } + return arr; + } + + public static List getMultiMapList(T _key, Map> _map) { + List list = _map.get(_key); + if (list == null) { + list = new ArrayList<>(); + _map.put(_key, list); + } + return list; + } + + public static Set getMultiMapSet(T _key, Map> _map) { + Set set = _map.get(_key); + if (set == null) { + set = new HashSet<>(); + _map.put(_key, set); + } + return set; + } + + public static List addToMultiMap(T _key, V _value, Map> _map) { + List list = getMultiMapList(_key, _map); + list.add(_value); + return list; + } + + public static Set addToMultiMapSet(T _key, V _value, Map> _map) { + Set set = getMultiMapSet(_key, _map); + set.add(_value); + return set; + } + + public static int sumIntegers(Collection _coll) { + int sum = 0; + for (Integer val : makeNotNull(_coll)) { + if (val != null) + sum += val; + } + return sum; + } + + public static Double sum(Collection _coll) { + double sum = 0.0; + for (Double val : makeNotNull(_coll)) { + if (val != null) + sum += val; + } + return sum; + } + + public static Double mean(Collection _coll) { + int cnt = 0; + double total = 0.0; + for (Double val : makeNotNull(_coll)) { + if (val != null) { + cnt++; + total += val; + } + } + if (cnt == 0) + return 0.0; + return total / cnt; + } + + public static Double variance(Collection _coll) { + double mean = mean(_coll); + int cnt = 0; + double total = 0.0; + for (Double val : makeNotNull(_coll)) { + if (val != null) { + cnt++; + total += (val - mean) * (val - mean); + } + } + if (cnt == 0) + return 0.0; + return total / cnt; + } + + public static Double standardDeviation(Collection _coll) { + return Math.sqrt(variance(_coll)); + } + + public static List> split(List _list, int _size) { + if (_list == null) + return Collections.emptyList(); + int iPieces = (_list.size() / _size) + 1; + List> list = new ArrayList<>(iPieces); + for (int i = 0; i < iPieces; i++) { + list.add(_list.subList(i * _size, Math.min(_list.size(), (i + 1) * _size))); + } + return list; + } + + public static List> splitEvenly(List _list, int _maxSize) { + if (isEmpty(_list)) + return Collections.emptyList(); + return splitIntoPieces(_list, ((_list.size()-1) / _maxSize) + 1); + } + + public static List> splitIntoPieces(List _list, int _pieces) { + return splitIntoPieces(_list, _pieces, false); + } + + public static List> splitIntoPieces(List _list, int _pieces, boolean _createNewLists) { + if (isEmpty(_list)) + return Collections.emptyList(); + if (_list.size() < _pieces) + return Collections.singletonList(_list); + int size = (int)Math.ceil(((double)_list.size())/_pieces); + List> list = new ArrayList<>(_pieces); + int offset = 0; + while (offset < _list.size()) { + List subList = _list.subList(offset, Math.min(_list.size(), offset+size)); + list.add(_createNewLists?new ArrayList<>(subList):subList); + offset += size; + } + return list; + } + + public static ArrayList asArrayList(T... _values) { + if (_values == null) + return new ArrayList<>(0); + ArrayList list = new ArrayList<>(_values.length); + for (T t : _values) + list.add(t); + return list; + } + + public static HashSet asHashSet(T... _values) { + HashSet setValues = new HashSet<>(); + if (_values == null) + return setValues; + for (T t : _values) + setValues.add(t); + return setValues; + } + + public static HashMap asHashMap(K _key, V _value) { + HashMap map = new HashMap<>(); + map.put(_key, _value); + return map; + } + + public static ArrayList asArrayList(Iterable _iterable) { + if (_iterable == null) + return new ArrayList(0); + ArrayList list = new ArrayList<>(); + for (T t : _iterable) + list.add(t); + return list; + } + + public static ArrayList asArrayList(Iterator _iter) { + if (_iter == null) + return new ArrayList(0); + ArrayList list = new ArrayList<>(); + while (_iter.hasNext()) + list.add(_iter.next()); + return list; + } + + public static HashSet asHashSet(Iterable _iterable) { + HashSet setValues = new HashSet<>(); + if (_iterable == null) + return setValues; + for (T t : _iterable) + setValues.add(t); + return setValues; + } + + public static HashSet asHashSet(Iterator _iter) { + HashSet setValues = new HashSet<>(); + if (_iter == null) + return setValues; + while (_iter.hasNext()) + setValues.add(_iter.next()); + return setValues; + } + + public static boolean allQualify(Collection _coll, IQualifier _qualifier) { + if ((_coll == null) || (_qualifier == null)) + return false; + for (T t : _coll) { + if ((t == null) || !_qualifier.qualifies(t)) + return false; + } + return true; + } + + public static boolean anyQualify(Collection _coll, IQualifier _qualifier) { + if ((_coll == null) || (_qualifier == null)) + return false; + for (T t : _coll) { + if ((t != null) && _qualifier.qualifies(t)) + return true; + } + return false; + } + + public static boolean noneQualify(Collection _coll, IQualifier _qualifier) { + if ((_coll == null) || (_qualifier == null)) + return true; + for (T t : _coll) { + if ((t != null) && _qualifier.qualifies(t)) + return false; + } + return true; + } + + public static List filter(Collection _coll, IFilter _filter) { + if ((_coll == null) || (_filter == null)) + return new ArrayList<>(); + List listValues = new ArrayList<>(); + for (T t : _coll) { + if (_filter.isFiltered(t)) + listValues.add(t); + } + return listValues; + } + + public static T filterOne(Collection _coll, IFilter _filter) { + if ((_coll == null) || (_filter == null)) + return null; + for (T t : _coll) { + if (_filter.isFiltered(t)) + return t; + } + return null; + } + + public static int indexOf(List _list, IQualifier _qual) { + if ((_list == null) || (_qual == null)) + return -1; + int i = 0; + for (T t : _list) { + if (_qual.qualifies(t)) + return i; + i++; + } + return -1; + } + + public static void filterMod(Iterable _iterable, IFilter _filter) { + if ((_iterable == null) || (_filter == null)) + return; + Iterator iter = _iterable.iterator(); + while (iter.hasNext()) { + if (!_filter.isFiltered(iter.next())) + iter.remove(); + } + } + + public static List filterToType(Iterable _iterable, Class _class) { + List list = new ArrayList<>(); + if (_iterable == null) + return list; + for (T t : _iterable) { + if (_class.isInstance(t)) + list.add(_class.cast(t)); + } + return list; + } + + + public static void edit(Iterable _coll, IEditor _editor) { + if ((_coll == null) || (_editor == null)) + return; + for (T t : _coll) { + _editor.edit(t); + } + } + + public static List transform(Collection _coll, ITransformer _transformer) { + return transform(_coll, _transformer, false); + } + + public static List transform(Iterable _iter, ITransformer _transformer) { + return transform(_iter, _transformer, false); + } + + public static List transform(Iterable _iter, ITransformer _transformer, boolean _excludeNulls) { + if ((_iter == null) || (_transformer == null)) + return new ArrayList<>(); + List listValues = new ArrayList<>(); + for (T t : _iter) { + if (_excludeNulls && (t == null)) + continue; + V v = _transformer.transform(t); + if (!_excludeNulls || (v != null)) + listValues.add(v); + } + return listValues; + } + + public static List transform(Collection _coll, ITransformer _transformer, boolean _excludeNulls) { + if ((_coll == null) || (_transformer == null)) + return new ArrayList<>(); + List listValues = new ArrayList<>(_coll.size()); + for (T t : _coll) { + V v = _transformer.transform(t); + if (!_excludeNulls || (v != null)) + listValues.add(v); + } + return listValues; + } + + @SuppressWarnings("unchecked") + public static V[] transform(T[] _coll, ITransformer _transformer, Class _destType) { + V[] ret = (V[])Array.newInstance(_destType, size(_coll)); + for (int i=0; i < size(_coll); i++) { + ret[i] = _transformer.transform(_coll[i]); + } + return ret; + } + + public static Map transformToMap(Collection _coll, ITransformer _transformer) { + Map mapValues = new HashMap<>(); + if ((_coll == null) || (_transformer == null)) + return mapValues; + for (T t : _coll) { + V v = _transformer.transform(t); + if (v != null) + mapValues.put(v, t); + } + return mapValues; + } + + public static Map transformToMap(Collection _coll, ITransformer _keyTrans, ITransformer _valTrans) { + Map mapValues = new HashMap<>(); + if ((_coll == null) || (_keyTrans == null) || (_valTrans == null)) + return mapValues; + for (T t : _coll) { + V v = _keyTrans.transform(t); + U u = _valTrans.transform(t); + if ((v != null) && (u != null)) + mapValues.put(v, u); + } + return mapValues; + } + + public static Map> transformToMultiMap(Collection _coll, ITransformer _transformer) { + Map> mapValues = new HashMap<>(); + if ((_coll == null) || (_transformer == null)) + return mapValues; + for (T t : _coll) { + V v = _transformer.transform(t); + if (v != null) + addToMultiMap(v, t, mapValues); + } + return mapValues; + } + + public static Map> transformToMultiMap(Collection _coll, ITransformer _keyTrans, ITransformer _valTrans) { + Map> mapValues = new HashMap<>(); + if ((_coll == null) || (_keyTrans == null) || (_valTrans == null)) + return mapValues; + for (T t : _coll) { + V v = _keyTrans.transform(t); + U u = _valTrans.transform(t); + if ((v != null) && (u != null)) + addToMultiMap(v, u, mapValues); + } + return mapValues; + } + + public static void addAllToMap(Collection _coll, ITransformer _keyTrans, Map _map) { + for (T t : makeNotNull(_coll)) { + V v = _keyTrans.transform(t); + if (v != null) + _map.put(v, t); + } + } + + public static void addAllToMultiMap(Collection _coll, ITransformer _keyTrans, Map> _map) { + for (T t : makeNotNull(_coll)) { + V v = _keyTrans.transform(t); + if (v != null) + addToMultiMap(v, t, _map); + } + } + + public static String transformToCommaSeparated(Collection _coll, ITransformer _transformer) { + return transformToCommaSeparated(_coll, _transformer, false); + } + + public static String transformToCommaSeparated(Collection _coll, ITransformer _transformer, boolean _spaceAfterComma) { + if (_transformer == null) + return null; + return commaSeparated(transform(_coll, _transformer), _spaceAfterComma); + } + + public static String transformAndDelimit(Collection _coll, ITransformer _transformer, String _delimiter) { + return transformAndDelimit(_coll, _transformer, _delimiter, false); + } + + public static String transformAndDelimit(Collection _coll, ITransformer _transformer, String _delimiter, boolean _discardEmptyValues) { + if (_transformer == null) + return null; + return delimit(transform(_coll, _transformer), _delimiter, _discardEmptyValues); + } + + public static String commaSeparated(Collection _values) { + return commaSeparated(_values, false); + } + + public static String commaSeparated(Collection _values, boolean _spaceAfterComma) { + return delimit(_values, _spaceAfterComma ? ", " : ","); + } + + public static String delimit(Collection _values, String _delimiter) { + return delimit(_values, _delimiter, false); + } + + public static String delimit(Collection _values, String _delimiter, boolean _discardEmptyValues) { + StringBuilder builder = null; + for (String value : makeNotNull(_values)) { + if (_discardEmptyValues && NullUtils.isEmpty(value)) + continue; + if (builder == null) + builder = new StringBuilder(); + else + builder.append(_delimiter); + builder.append(value); + } + if (builder != null) + return builder.toString(); + return null; + } + + public static List undelimit(String _value, String _delimiter) { + return undelimit(_value, _delimiter, true); + } + + public static List undelimit(String _value, String _delimiter, boolean _discardEmptyValues) { + if (_value == null) + return new ArrayList<>(); + return asArrayList(_discardEmptyValues?NullUtils.cleanSplit(_value, _delimiter): _value.split(_delimiter)); + } + + public static Set transformToSet(Collection _coll, ITransformer _transformer) { + Set setValues = new HashSet(); + if ((_coll == null) || (_transformer == null)) + return setValues; + for (T t : _coll) { + if (t != null) { + V v = _transformer.transform(t); + if (v != null) + setValues.add(v); + } + } + return setValues; + } + + public static > T getSmallest(Collection _collObjects) + { + return getSmallest(_collObjects, new Comparator() + { + @Override + public int compare(T o1, T o2) + { + return NullUtils.compare(o1, o2); + } + }); + } + + public static T getSmallest(Collection _objects, Comparator _comparator) + { + if (_objects == null) + return null; + T ret = null; + for (T t : _objects) + { + if (t == null) + continue; + if ((ret == null) || (_comparator.compare(t, ret) < 0)) + ret = t; + } + return ret; + } + + public static > List getAllSmallest(Collection _collObjects) + { + return getAllSmallest(_collObjects, new Comparator() + { + @Override + public int compare(T o1, T o2) + { + return NullUtils.compare(o1, o2); + } + }); + } + + public static List getAllSmallest(Collection _objects, Comparator _comparator) + { + final List ret = new ArrayList<>(); + if (_objects == null) + return ret; + for (T t : _objects) { + if (t == null) + continue; + if (ret.isEmpty()) + ret.add(t); + else { + int comp = _comparator.compare(t, CollectionUtils.getFirst(ret)); + if (comp == 0) + ret.add(t); + else if (comp < 0) { + ret.clear(); + ret.add(t); + } + } + } + return ret; + } + + public static > T getLargest(Collection _collObjects) + { + return getLargest(_collObjects, new Comparator() + { + @Override + public int compare(T o1, T o2) + { + return NullUtils.compare(o1, o2); + } + }); + } + + public static T getLargest(Collection _objects, Comparator _comparator) + { + if (_objects == null) + return null; + T ret = null; + for (T t : _objects) + { + if (t == null) + continue; + if ((ret == null) || (_comparator.compare(t, ret) > 0)) + ret = t; + } + return ret; + } + + public static List getSmallest(Collection _objects, Comparator _comparator, int _count) + { + return getSmallest(_objects, _comparator, _count, null); + } + + public static List getSmallest(Collection _objects, Comparator _comparator, int _count, IFilter _filter) + { + if (_objects == null) + return null; + if (_count * 4 > _objects.size()) + { + List items = new ArrayList(); + for (T t : _objects) + { + if ((_filter == null) || !_filter.isFiltered(t)) + items.add(t); + } + Collections.sort(items, _comparator); + return subList(items, 0, _count); + } + TreeMap> mapReturn = new TreeMap<>(_comparator); + for (T t : _objects) + { + if ((t == null) || ((_filter != null) && _filter.isFiltered(t))) + continue; + if (mapReturn.size() < _count) + addToMultiMap(t, t, mapReturn); + else + { + Iterator iter = mapReturn.descendingKeySet().iterator(); + if (_comparator.compare(t, iter.next()) < 0) + { + iter.remove(); + addToMultiMap(t, t, mapReturn); + } + } + } + List items = new ArrayList(_count); + for (List list : mapReturn.values()) + { + items.addAll(list); + } + return subList(items, 0, _count); + } + + public static List subList(List _list, int _fromIndex, int _toIndex) { + if ((_list == null) || (_fromIndex > _list.size() - 1)) + return new ArrayList(); + return _list.subList(_fromIndex, Math.min(_toIndex, _list.size())); + } + + public static > T mostCommon(Collection _collObjects) { + return mostCommon(_collObjects, new Comparator() { + @Override + public int compare(T _o1, T _o2) { + return NullUtils.compare(_o1, _o2); + } + }); + } + + public static T mostCommon(Collection _collObjects, Comparator _comparator) { + int iMax = 0; + Map mapCounts = new TreeMap(_comparator); + for (T t : makeNotNull(_collObjects)) { + AtomicInteger i = mapCounts.get(t); + if (i == null) { + mapCounts.put(t, new AtomicInteger(1)); + if (iMax == 0) + iMax = 1; + } else { + if (i.incrementAndGet() > iMax) + iMax = i.intValue(); + } + } + for (Entry e : mapCounts.entrySet()) { + if (e.getValue().intValue() == iMax) + return e.getKey(); + } + return null; + } + + public static List getAll(Map _map, Collection _keys) { + List ret = new ArrayList<>(); + if (_map == null) + return ret; + for (T t : makeNotNull(_keys)) { + V v = _map.get(t); + if (v != null) + ret.add(v); + } + return ret; + } + + public static byte[] toByteArray(Collection _integers) { + if (isEmpty(_integers)) + return null; + ByteBuffer bb = ByteBuffer.allocate(_integers.size() * 4); + for (Integer i : _integers) { + bb.putInt(i); + } + return bb.array(); + } + + public static List fromByteArrayOfIntegers(byte[] _btIntegers) { + if (length(_btIntegers) > 0) { + List auxIds = new ArrayList<>(_btIntegers.length / 4); + ByteBuffer bb = ByteBuffer.wrap(_btIntegers); + while (bb.hasRemaining()) { + auxIds.add(bb.getInt()); + } + return auxIds; + } + return new ArrayList<>(); + } + + public static int length(byte[] _arr) + { + if (_arr == null) + return 0; + return _arr.length; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DateUtils.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DateUtils.java new file mode 100644 index 0000000..4c2d0aa --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DateUtils.java @@ -0,0 +1,658 @@ +package com.lanternsoftware.util; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +public abstract class DateUtils { + public static long toLong(Date _dt) { + if (_dt == null) + return Long.MIN_VALUE; + return _dt.getTime(); + } + + public static Date toDate(long _epochOffset) { + if (_epochOffset == Long.MIN_VALUE) + return null; + return new Date(_epochOffset); + } + + public static Date millisecondsFromNow(long _milliseconds) { + return new Date(new Date().getTime() + _milliseconds); + } + + public static Date secondsFromNow(long _seconds) { + return addSeconds(new Date(), _seconds); + } + + public static Date minutesFromNow(int _minutes) { + return addMinutes(new Date(), _minutes); + } + + public static Date hoursFromNow(int _hours) { + return addHours(new Date(), _hours); + } + + public static Date daysFromNow(int _days) { + return addDays(new Date(), _days); + } + + public static Date addSeconds(Date _dt, long _seconds) { + if (_dt == null) + return null; + return new Date(_dt.getTime() + _seconds * 1000L); + } + + public static Date addMinutes(Date _dt, int _minutes) { + if (_dt == null) + return null; + return new Date(_dt.getTime() + _minutes * 60000L); + } + + public static Date addHours(Date _dt, int _hours) { + if (_dt == null) + return null; + return new Date(_dt.getTime() + _hours * 3600000L); + } + + public static Date addDays(Date _dt, int _days) { + if (_dt == null) + return null; + return new Date(_dt.getTime() + _days * 86400000L); + } + + public static Date addDays(Date _dt, int _days, TimeZone _tz) { + if (_dt == null) + return null; + Calendar cal = toCalendar(_dt, _tz); + cal.add(Calendar.DAY_OF_YEAR, _days); + return cal.getTime(); + } + + public static Date addMonths(Date _dt, int _months) { + Calendar cal = GregorianCalendar.getInstance(); + cal.setTime(_dt); + cal.add(Calendar.MONTH, _months); + return cal.getTime(); + } + + public static Date addMonths(Date _dt, int _months, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + if (cal == null) + return null; + cal.add(Calendar.MONTH, _months); + return cal.getTime(); + } + + public static Date addMonthKeepDayOfWeek(Date _dt, int _months, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + if (cal == null) + return null; + int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); + int dayOfWeekInMonth = cal.get(Calendar.DAY_OF_WEEK_IN_MONTH); + cal.add(Calendar.MONTH, _months); + cal.set(Calendar.DAY_OF_WEEK, dayOfWeek); + cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, dayOfWeekInMonth); + return cal.getTime(); + } + + public static Date addYears(Date _dt, int _years) { + Calendar cal = GregorianCalendar.getInstance(); + cal.setTime(_dt); + cal.add(Calendar.YEAR, _years); + return cal.getTime(); + } + + public static Date addYears(Date _dt, int _years, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + if (cal == null) + return null; + cal.add(Calendar.YEAR, _years); + return cal.getTime(); + } + + public static long diffInMilliseconds(Date _dt1, Date _dt2) { + return diffInMilliseconds(_dt1, _dt2, Long.MAX_VALUE); + } + + public static long diffInMilliseconds(Date _dt1, Date _dt2, long _defaultIfNull) { + if ((_dt1 == null) || (_dt2 == null)) + return _defaultIfNull; + return Math.abs(_dt1.getTime() - _dt2.getTime()); + } + + public static long diffInSeconds(Date _dt1, Date _dt2) { + return diffInSeconds(_dt1, _dt2, Long.MAX_VALUE); + } + + public static long diffInSeconds(Date _dt1, Date _dt2, long _defaultIfNull) { + if ((_dt1 == null) || (_dt2 == null)) + return _defaultIfNull; + return Math.abs(_dt1.getTime() - _dt2.getTime()) / 1000; + } + + public static long diffInMinutes(Date _dt1, Date _dt2) { + return diffInMinutes(_dt1, _dt2, Long.MAX_VALUE); + } + + public static long diffInMinutes(Date _dt1, Date _dt2, long _defaultIfNull) { + if ((_dt1 == null) || (_dt2 == null)) + return _defaultIfNull; + return Math.abs(_dt1.getTime() - _dt2.getTime()) / 60000; + } + + public static long diffInHours(Date _dt1, Date _dt2) { + return diffInHours(_dt1, _dt2, Long.MAX_VALUE); + } + + public static long diffInHours(Date _dt1, Date _dt2, long _defaultIfNull) { + if ((_dt1 == null) || (_dt2 == null)) + return _defaultIfNull; + return Math.abs(_dt1.getTime() - _dt2.getTime()) / 3600000; + } + + public static boolean isAfter(Date _dt1, Date _dt2) { + return isAfter(_dt1, _dt2, false); + } + + public static boolean isAfter(Date _dt1, Date _dt2, boolean _defaultIfNull) { + if ((_dt1 == null) || (_dt2 == null)) + return _defaultIfNull; + return _dt1.after(_dt2); + } + + public static boolean isAfterOrEqualTo(Date _dt1, Date _dt2) { + return isAfterOrEqualTo(_dt1, _dt2, false); + } + + public static boolean isAfterOrEqualTo(Date _dt1, Date _dt2, boolean _defaultIfNull) { + if ((_dt1 == null) || (_dt2 == null)) + return _defaultIfNull; + return _dt1.getTime() >= _dt2.getTime(); + } + + public static boolean isBefore(Date _dt1, Date _dt2) { + return isBefore(_dt1, _dt2, false); + } + + public static boolean isBefore(Date _dt1, Date _dt2, boolean _defaultIfNull) { + if ((_dt1 == null) || (_dt2 == null)) + return _defaultIfNull; + return _dt1.before(_dt2); + } + + public static boolean isBeforeOrEqualTo(Date _dt1, Date _dt2) { + return isBeforeOrEqualTo(_dt1, _dt2, false); + } + + public static boolean isBeforeOrEqualTo(Date _dt1, Date _dt2, boolean _defaultIfNull) { + if ((_dt1 == null) || (_dt2 == null)) + return _defaultIfNull; + return _dt1.getTime() <= _dt2.getTime(); + } + + public static String getAge(Date _dtDOB) { + if (_dtDOB == null) + return ""; + return getAge(_dtDOB, getMidnightBeforeNow()); + } + + public static String getAge(Date _dtDOB, Date _dtReference) { + if (_dtDOB == null) + return ""; + return getAge(_dtDOB.getTime(), _dtReference.getTime()); + } + + public static String getAge(long _dob, long _reference) { + long lAge = _reference - _dob; + if (lAge < 24 * 3600000) // less than a day old + return String.format("%.2d:%.2d hours", lAge / 3600000, (lAge % 3600000) / 60000); + if (lAge < 7 * 24 * 3600000) // less than a week old + return String.format("%d days", lAge / (24 * 3600000)); + Date dtStart = new Date(_dob); + Date dtEnd = new Date(_reference); + int iMonths = getMonthsBetween(dtStart, dtEnd); + if (iMonths == 0) + return String.format("%d days", (dtEnd.getTime() - dtStart.getTime()) / (7 * 24 * 3600000)); + int iYears = getYearsBetween(dtStart, dtEnd); + if (iYears < 2) + return String.format("%d months", iMonths); + return String.format("%d years", iYears); + } + + public static int getMonthsBetween(Date _dtStart, Date _dtEnd) { + Calendar calStart = getGMTCalendar(_dtStart.getTime()); + Calendar calEnd = getGMTCalendar(_dtEnd.getTime()); + int diff = calEnd.get(Calendar.YEAR) * 24 + calEnd.get(Calendar.MONTH) - calStart.get(Calendar.YEAR) * 24 + calStart.get(Calendar.MONTH); + if (calStart.get(Calendar.DAY_OF_MONTH) > calEnd.get(Calendar.DAY_OF_MONTH)) + diff--; + return diff; + } + + public static int getYearsBetween(Date _dtStart, Date _dtEnd) { + Calendar calStart = getGMTCalendar(_dtStart.getTime()); + Calendar calEnd = getGMTCalendar(_dtEnd.getTime()); + int diff = calEnd.get(Calendar.YEAR) - calStart.get(Calendar.YEAR); + if (isLaterInYear(calStart, calEnd)) + diff--; + return diff; + } + + public static int getAgeInYears(Date _dtDOB) { + if (_dtDOB == null) + return 0; + return getAgeInYears(_dtDOB.getTime()); + } + + public static int getAgeInYears(Date _dtDOB, Date _dtReference) { + if (_dtDOB == null) + return 0; + return getAgeInYears(_dtDOB.getTime(), _dtReference); + } + + public static int getAgeInYears(long _lDOB) { + return getAgeInYears(_lDOB, getMidnightBeforeNow()); + } + + public static int getAgeInYears(long _lDOB, Date _dtReference) { + if (_lDOB == 0 || _dtReference == null) + return 0; + Calendar calDOB = getGMTCalendar(_lDOB); + Calendar calToday = getGMTCalendar(_dtReference.getTime()); + + int age = calToday.get(Calendar.YEAR) - calDOB.get(Calendar.YEAR); + if (!isLaterInYear(calToday, calDOB)) + age--; + return age; + } + + public static Calendar getGMTCalendar(long _lTime) { + return toCalendar(_lTime, TimeZone.getTimeZone("GMT")); + } + + private static boolean isLaterInYear(Calendar _cal1, Calendar _cal2) { + if (_cal1.get(Calendar.MONTH) > _cal2.get(Calendar.MONTH)) + return true; + return (_cal1.get(Calendar.MONTH) == _cal2.get(Calendar.MONTH)) && (_cal1.get(Calendar.DAY_OF_MONTH) >= _cal2.get(Calendar.DAY_OF_MONTH)); + } + + public static boolean isSameDay(Date _d1, Date _d2, TimeZone _tz) { + return getMidnightBefore(_d1, _tz).equals(getMidnightBefore(_d2, _tz)); + } + + public static boolean isSameDayOfWeek(Date _d1, Date _d2, TimeZone _tz) { + return getDayOfWeek(_d1, _tz) == getDayOfWeek(_d2, _tz); + } + + public static boolean isSameTimeOfDay(Date _d1, Date _d2, TimeZone _tz) { + Calendar cal1 = toCalendar(_d1, _tz); + Calendar cal2 = toCalendar(_d2, _tz); + if ((cal1 == null) || (cal2 == null)) + return false; + if (cal1.get(Calendar.HOUR_OF_DAY) != cal2.get(Calendar.HOUR_OF_DAY)) + return false; + if (cal1.get(Calendar.MINUTE) != cal2.get(Calendar.MINUTE)) + return false; + if (cal1.get(Calendar.SECOND) != cal2.get(Calendar.SECOND)) + return false; + return (cal1.get(Calendar.MILLISECOND) == cal2.get(Calendar.MILLISECOND)); + } + + public static Date getMidnightBeforeNow() { + return getMidnightBeforeNow(TimeZone.getTimeZone("GMT")); + } + + public static Date getMidnightBeforeNow(TimeZone _tz) { + return hoursAfterMidnight(new Date(), 0, _tz); + } + + public static Calendar getMidnightBeforeNowCal(TimeZone _tz) { + return hoursAfterMidnightCal(new Date(), 0, _tz); + } + + public static Date getMidnightBefore(Date _dt, TimeZone _tz) { + return hoursAfterMidnight(_dt, 0, _tz); + } + + public static Calendar getMidnightBeforeCal(Date _dt, TimeZone _tz) { + return hoursAfterMidnightCal(_dt, 0, _tz); + } + + public static Date getMidnightAfterNow() { + return getMidnightAfterNow(TimeZone.getTimeZone("GMT")); + } + + public static Date getMidnightAfterNow(TimeZone _tz) { + return hoursAfterMidnight(new Date(), 24, _tz); + } + + public static Calendar getMidnightAfterNowCal(TimeZone _tz) { + return hoursAfterMidnightCal(new Date(), 24, _tz); + } + + public static Date getMidnightAfter(Date _dt, TimeZone _tz) { + return hoursAfterMidnight(_dt, 24, _tz); + } + + public static Calendar getMidnightAfterCal(Date _dt, TimeZone _tz) { + return hoursAfterMidnightCal(_dt, 24, _tz); + } + + public static Date hoursAfterMidnight(Date _dt, int _hours, TimeZone _tz) { + return hoursAfterMidnightCal(_dt, _hours, _tz).getTime(); + } + + public static Calendar hoursAfterMidnightCal(Date _dt, int _hours, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + if (cal == null) + return null; + cal.set(Calendar.HOUR_OF_DAY, _hours); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal; + } + + public static boolean isBetween(Date _dt, Date _dtFrom, Date _dtTo) { + if (_dt == null) + return false; + if ((_dtFrom != null) && _dtFrom.after(_dt)) + return false; + return (_dtTo == null) || _dtTo.after(_dt); + } + + public static Date setTimeOfDay(Date _date, Date _time, TimeZone _tz) { + Calendar date = toCalendar(_date, _tz); + Calendar time = toCalendar(_time, _tz); + if ((date == null) || (time == null)) + return null; + date.set(Calendar.HOUR_OF_DAY, time.get(Calendar.HOUR_OF_DAY)); + date.set(Calendar.MINUTE, time.get(Calendar.MINUTE)); + date.set(Calendar.SECOND, time.get(Calendar.SECOND)); + date.set(Calendar.MILLISECOND, time.get(Calendar.MILLISECOND)); + return date.getTime(); + } + + public static Calendar toCalendar(long _ts, TimeZone _tz) { + return toCalendar(new Date(_ts), _tz); + } + + public static Calendar toCalendar(Date _date, TimeZone _tz) { + if (_date == null) + return null; + Calendar cal = new GregorianCalendar(_tz); + cal.setTime(_date); + return cal; + } + + public static DateFormat dateFormat(String _format, TimeZone _tz) { + SimpleDateFormat format = new SimpleDateFormat(_format); + format.setTimeZone(_tz); + return format; + } + + public static String format(String _format, Date _dt) { + return format(_format, TimeZone.getTimeZone("UTC"), _dt); + } + + public static String format(String _format, TimeZone _tz, Date _dt) { + if (_dt == null) + return null; + return dateFormat(_format, _tz).format(_dt); + } + + public static Date parse(String _format, String _date) { + return parse(_format, TimeZone.getTimeZone("UTC"), _date); + } + + public static Date parse(String _format, TimeZone _tz, String _date) { + if (NullUtils.isEmpty(_date)) + return null; + try { + return dateFormat(_format, _tz).parse(_date); + } + catch (Exception _e) { + return null; + } + } + + public static Date date(int _month, int _day, int _year, TimeZone _tz) { + return date(_month, _day, _year, 0, 0, 0, 0, _tz); + } + + public static Date date(int _month, int _day, int _year, int _hour, int _minutes, int _seconds, int _ms, TimeZone _tz) { + Calendar cal = GregorianCalendar.getInstance(_tz); + cal.set(Calendar.YEAR, _year); + cal.set(Calendar.MONTH, _month - 1); + cal.set(Calendar.DAY_OF_MONTH, _day); + cal.set(Calendar.HOUR_OF_DAY, _hour); + cal.set(Calendar.MINUTE, _minutes); + cal.set(Calendar.SECOND, _seconds); + cal.set(Calendar.MILLISECOND, _ms); + return cal.getTime(); + } + + public static int getDayOfWeek(Date _dt, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + return cal == null ? 0 : cal.get(Calendar.DAY_OF_WEEK); + } + + public static Date setDayOfWeek(Date _dt, TimeZone _tz, int _dayOfWeek) { + Calendar cal = toCalendar(_dt, _tz); + if (cal == null) + return null; + if ((_dayOfWeek >= Calendar.SUNDAY) && (_dayOfWeek <= Calendar.SATURDAY)) + cal.set(Calendar.DAY_OF_WEEK, _dayOfWeek); + return cal.getTime(); + } + + public static Date getMidnightBeforeDayOfWeek(Date _dt, TimeZone _tz, int _dayOfWeek) { + return getMidnightBefore(setDayOfWeek(_dt, _tz, _dayOfWeek), _tz); + } + + public static Date getMidnightAfterDayOfWeek(Date _dt, TimeZone _tz, int _dayOfWeek) { + return getMidnightAfter(setDayOfWeek(_dt, _tz, _dayOfWeek), _tz); + } + + public static boolean isDstTransitionDay(Date _dt, TimeZone _tz) { + Date midnight = getMidnightBefore(_dt, _tz); + Calendar cal = toCalendar(midnight, _tz); + if (cal == null) + return false; + cal.set(Calendar.HOUR_OF_DAY, 8); + return (cal.getTimeInMillis() - midnight.getTime() != 28800000); + } + + public static Date setDayOfWeek(Date _dt, TimeZone _tz, String _dayOfWeek) { + Calendar cal = toCalendar(_dt, _tz); + if (cal == null) + return null; + int dayOfWeekInt = 0; + switch (_dayOfWeek) { + case "Sunday": + dayOfWeekInt = Calendar.SUNDAY; + break; + case "Monday": + dayOfWeekInt = Calendar.MONDAY; + break; + case "Tuesday": + dayOfWeekInt = Calendar.TUESDAY; + break; + case "Wednesday": + dayOfWeekInt = Calendar.WEDNESDAY; + break; + case "Thursday": + dayOfWeekInt = Calendar.THURSDAY; + break; + case "Friday": + dayOfWeekInt = Calendar.FRIDAY; + break; + case "Saturday": + dayOfWeekInt = Calendar.SATURDAY; + break; + } + if (dayOfWeekInt > 0) + cal.set(Calendar.DAY_OF_WEEK, dayOfWeekInt); + + return cal.getTime(); + } + + public static Date getStartOfMinute(TimeZone _tz) { + return getStartOfMinute(new Date(), _tz); + } + + public static Date getStartOfMinute(Date _dt, TimeZone _tz) { + return getStartOfMinuteCal(_dt, _tz).getTime(); + } + + public static Calendar getStartOfMinuteCal(Date _dt, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal; + } + + public static Date getEndOfMinute(TimeZone _tz) { + return getEndOfMinute(new Date(), _tz); + } + + public static Date getEndOfMinute(Date _dt, TimeZone _tz) { + return getEndOfMinuteCal(_dt, _tz).getTime(); + } + + public static Calendar getEndOfMinuteCal(Date _dt, TimeZone _tz) { + Calendar cal = getStartOfMinuteCal(_dt, _tz); + cal.add(Calendar.MINUTE, 1); + return cal; + } + + public static Date getStartOfHour(TimeZone _tz) { + return getStartOfHour(new Date(), _tz); + } + + public static Date getStartOfHour(Date _dt, TimeZone _tz) { + return getStartOfHourCal(_dt, _tz).getTime(); + } + + public static Calendar getStartOfHourCal(Date _dt, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal; + } + + public static Date getEndOfHour(TimeZone _tz) { + return getEndOfHour(new Date(), _tz); + } + + public static Date getEndOfHour(Date _dt, TimeZone _tz) { + return getEndOfHourCal(_dt, _tz).getTime(); + } + + public static Calendar getEndOfHourCal(Date _dt, TimeZone _tz) { + Calendar cal = getStartOfHourCal(_dt, _tz); + cal.add(Calendar.HOUR_OF_DAY, 1); + return cal; + } + + public static Date getStartOfWeek(TimeZone _tz) { + return getStartOfWeek(new Date(), _tz); + } + + public static Date getStartOfWeek(Date _dt, TimeZone _tz) { + return getStartOfWeekCal(_dt, _tz).getTime(); + } + + public static Calendar getStartOfWeekCal(Date _dt, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY); + return cal; + } + + public static Date getEndOfWeek(TimeZone _tz) { + return getEndOfWeek(new Date(), _tz); + } + + public static Date getEndOfWeek(Date _dt, TimeZone _tz) { + return getEndOfWeekCal(_dt, _tz).getTime(); + } + + public static Calendar getEndOfWeekCal(Date _dt, TimeZone _tz) { + Calendar cal = getStartOfWeekCal(_dt, _tz); + cal.add(Calendar.DAY_OF_YEAR, 7); + return cal; + } + + public static Date getStartOfMonth(TimeZone _tz) { + return getStartOfMonth(new Date(), _tz); + } + + public static Date getStartOfMonth(Date _dt, TimeZone _tz) { + return getStartOfMonthCal(_dt, _tz).getTime(); + } + + public static Calendar getStartOfMonthCal(Date _dt, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.DAY_OF_MONTH, 1); + return cal; + } + + public static Date getEndOfMonth(TimeZone _tz) { + return getEndOfMonth(new Date(), _tz); + } + + public static Date getEndOfMonth(Date _dt, TimeZone _tz) { + return getEndOfMonthCal(_dt, _tz).getTime(); + } + + public static Calendar getEndOfMonthCal(Date _dt, TimeZone _tz) { + Calendar cal = getStartOfMonthCal(_dt, _tz); + cal.add(Calendar.MONTH, 1); + return cal; + } + + public static Date getStartOfYear(TimeZone _tz) { + return getStartOfYear(new Date(), _tz); + } + + public static Date getStartOfYear(Date _dt, TimeZone _tz) { + return getStartOfYearCal(_dt, _tz).getTime(); + } + + public static Calendar getStartOfYearCal(Date _dt, TimeZone _tz) { + Calendar cal = toCalendar(_dt, _tz); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.DAY_OF_YEAR, 1); + return cal; + } + + public static Date getEndOfYear(TimeZone _tz) { + return getEndOfYear(new Date(), _tz); + } + + public static Date getEndOfYear(Date _dt, TimeZone _tz) { + return getEndOfYearCal(_dt, _tz).getTime(); + } + + public static Calendar getEndOfYearCal(Date _dt, TimeZone _tz) { + Calendar cal = getStartOfYearCal(_dt, _tz); + cal.add(Calendar.YEAR, 1); + return cal; + } + +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DebugTimer.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DebugTimer.java new file mode 100644 index 0000000..2323f61 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DebugTimer.java @@ -0,0 +1,147 @@ +package com.lanternsoftware.util; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceLoader; + +import com.lanternsoftware.util.tracing.TraceLog; +import com.lanternsoftware.util.tracing.TraceTags; +import com.lanternsoftware.util.tracing.TracerConfig; +import com.lanternsoftware.util.tracing.ITracer; +import com.lanternsoftware.util.tracing.TraceContext; +import com.lanternsoftware.util.tracing.TraceDuration; +import org.slf4j.Logger; + +public class DebugTimer { + private final Logger LOG; + private final String name; + private final TraceContext context; + private Date start; + private TraceDuration duration; + private TraceTags tags; + private List logs; + private final boolean suppressLocalLogs; + + private static ITracer tracer = null; + private static TracerConfig config; + private static final ThreadLocal traceContexts = new ThreadLocal<>(); + + public DebugTimer(String _name) { + this(null, _name, null, false); + } + + public DebugTimer(TraceContext _parent, String _name) { + this(_parent, _name, null, false); + } + + public DebugTimer(String _name, Logger _log) { + this(null, _name, _log, false); + } + + public DebugTimer(String _context, String _name) { + this(TraceContext.deserialize(_context), _name, null, false); + } + + public DebugTimer(String _context, String _name, Logger _log) { + this(TraceContext.deserialize(_context), _name, _log, false); + } + + public DebugTimer(TraceContext _parent, String _name, Logger _log) { + this(_parent, _name, _log, false); + } + + public DebugTimer(TraceContext _parent, String _name, Logger _log, boolean _suppressLocalLogs) { + TraceContext parent = _parent; + name = _name; + LOG = _log; + suppressLocalLogs = _suppressLocalLogs; + start = new Date(); + if ((parent == null) && (config != null) && config.isUseThreadContext()) { + parent = traceContexts.get(); + traceContexts.set(getContext()); + } + context = parent; + } + + public static void setTracerConfig(TracerConfig _config) { + config = _config; + if (tracer == null) { + Iterator iter = ServiceLoader.load(ITracer.class).iterator(); + if (iter.hasNext()) { + tracer = iter.next(); + } + } + tracer.config(config); + } + + private ITracer getTracer() { + if (config == null) + return null; + return tracer; + } + + public void tag(String _name, String _value) { + if (tags == null) + tags = TraceTags.tag(_name, _value); + else + tags.put(_name, _value); + } + + public void log(String _event) { + if (logs == null) + logs = new ArrayList<>(); + logs.add(new TraceLog(traceDuration().currentTimeOffset(), _event)); + } + + private TraceDuration traceDuration() { + if (duration != null) + return duration; + ITracer t = getTracer(); + duration = (t == null)?new TraceDuration(start):t.createDuration(context, name, start); + return duration; + } + + public void start() { + if (duration == null) + start = new Date(); + traceDuration().start(); + } + + public TraceContext stop() { + traceDuration().stop(); + return print(); + } + + public void stopDoNotPrint() { + traceDuration().stop(); + } + + public long duration() { + return traceDuration().duration(); + } + + public TraceContext getContext() { + return traceDuration().getContext(); + } + + public TraceContext print() { + StringBuilder b = new StringBuilder(name); + b.append(": "); + b.append(traceDuration().duration()); + b.append("ms"); + TraceContext newContext = traceDuration().getContext(); + ITracer t = getTracer(); + if (t != null) + t.trace(name, duration, tags, logs); + if (!suppressLocalLogs && ((config == null) || !config.isSuppressLocalLog())) { + if (LOG != null) + LOG.debug(b.toString()); + else + System.out.println(b.toString()); + } + traceContexts.set(context); + return newContext; + } +} \ No newline at end of file diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IAggregator.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IAggregator.java new file mode 100644 index 0000000..0df527f --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IAggregator.java @@ -0,0 +1,7 @@ +package com.lanternsoftware.util; + +import java.util.List; + +public interface IAggregator { + List aggregate(T _t); +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IEditor.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IEditor.java new file mode 100644 index 0000000..68c4aca --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IEditor.java @@ -0,0 +1,5 @@ +package com.lanternsoftware.util; + +public interface IEditor { + void edit(T _t); +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IEquals.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IEquals.java new file mode 100644 index 0000000..b50c7f2 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IEquals.java @@ -0,0 +1,5 @@ +package com.lanternsoftware.util; + +public interface IEquals { + boolean equals(T _t1, T _t2); +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IFilter.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IFilter.java new file mode 100644 index 0000000..b19ab18 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IFilter.java @@ -0,0 +1,5 @@ +package com.lanternsoftware.util; + +public interface IFilter { + boolean isFiltered(T _t); +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IQualifier.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IQualifier.java new file mode 100644 index 0000000..cbcf5fd --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/IQualifier.java @@ -0,0 +1,5 @@ +package com.lanternsoftware.util; + +public interface IQualifier { + boolean qualifies(T _t); +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ISupplier.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ISupplier.java new file mode 100644 index 0000000..2230798 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ISupplier.java @@ -0,0 +1,5 @@ +package com.lanternsoftware.util; + +public interface ISupplier { + T get(); +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ITransformer.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ITransformer.java new file mode 100644 index 0000000..e2482d2 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ITransformer.java @@ -0,0 +1,5 @@ +package com.lanternsoftware.util; + +public interface ITransformer { + V transform(T _t); +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/LanternFiles.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/LanternFiles.java new file mode 100644 index 0000000..f1e27e2 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/LanternFiles.java @@ -0,0 +1,6 @@ +package com.lanternsoftware.util; + +public abstract class LanternFiles { + public static final String SOURCE_PATH = "C:\\lantern\\wc\\opensource\\LanternPowerMonitor\\"; + public static final String OPS_PATH = "D:\\zwave\\"; +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/MapUtils.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/MapUtils.java new file mode 100644 index 0000000..4b663a3 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/MapUtils.java @@ -0,0 +1,23 @@ +package com.lanternsoftware.util; + +public class MapUtils { + private static final double RADIUS_EARTH = 6371000; + + /** + * @return Distance between the two points in meters + */ + public static double distance(double _latitude1, double _longitude1, double _latitude2, double _longitude2) + { + double latitude = Math.toRadians(_latitude2 - _latitude1); + double longitude = Math.toRadians(_longitude2 - _longitude1); + double a = Math.sin(latitude / 2) * Math.sin(latitude / 2) + Math.cos(Math.toRadians(_latitude1)) * Math.cos(Math.toRadians(_latitude2)) * Math.sin(longitude / 2) * Math.sin(longitude / 2); + return RADIUS_EARTH * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + } + + /** + * @return Time in ms to travel from one point to another as the crow flies at the given speed + */ + public static long travelTime(double _latitude1, double _longitude1, double _latitude2, double _longitude2, double _speedMetersPerSecond) { + return (long)(1000*distance(_latitude1, _longitude1, _latitude2, _longitude2)/_speedMetersPerSecond); + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/MemoryStats.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/MemoryStats.java new file mode 100644 index 0000000..db85f1d --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/MemoryStats.java @@ -0,0 +1,84 @@ +package com.lanternsoftware.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class MemoryStats { + public static long size(Object _o) { + return size(_o, new HashSet()); + } + + private static long size(Object _o, Set _counted) { + int hash = System.identityHashCode(_o); + if (_counted.contains(hash)) + return 0; + _counted.add(hash); + long size = 0; + for (Field f : allFields(_o.getClass())) { + if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) + continue; + f.setAccessible(true); + try { + Object child = f.get(_o); + if (child != null) { + if (f.getType().equals(String.class)) + size += ((String)child).length(); + else if (f.getType().equals(Date.class)) + size += 8; + else if (f.getType().equals(Double.class) || f.getType().equals(Double.TYPE)) + size += 8; + else if (f.getType().equals(Float.class) || f.getType().equals(Float.TYPE)) + size += 4; + else if (f.getType().equals(Long.class) || f.getType().equals(Long.TYPE)) + size += 8; + else if (f.getType().equals(Integer.class) || f.getType().equals(Integer.TYPE)) + size += 4; + else if (f.getType().equals(Short.class) || f.getType().equals(Short.TYPE)) + size += 2; + else if (f.getType().equals(Byte.class) || f.getType().equals(Byte.TYPE)) + size += 1; + else if (f.getType().equals(Character.class) || f.getType().equals(Character.TYPE)) + size += 1; + else if (f.getType().equals(Boolean.class) || f.getType().equals(Boolean.TYPE)) + size += 1; + else if (f.getType().equals(byte[].class)) + size += ((byte[])child).length; + else if (f.getType().equals(char[].class)) + size += ((char[])child).length; + else if (Collection.class.isAssignableFrom(f.getType())) { + for (Object childElement : ((Collection)child)) { + size += size(childElement, _counted); + } + } + else if (Map.class.isAssignableFrom(f.getType())) { + Set> entries = ((Map)child).entrySet(); + for (Entry childElement : entries) { + size += size(childElement.getKey(), _counted); + size += size(childElement.getValue(), _counted); + } + } + else + size += size(child, _counted); + } + } catch (IllegalAccessException _e) { + } + } + return size; + } + + private static List allFields(Class _c) { + if (_c == null) + return Collections.emptyList(); + List fields = CollectionUtils.asArrayList(_c.getDeclaredFields()); + fields.addAll(allFields(_c.getSuperclass())); + return fields; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/NullUtils.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/NullUtils.java new file mode 100644 index 0000000..df59a60 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/NullUtils.java @@ -0,0 +1,397 @@ +package com.lanternsoftware.util; + +import org.apache.commons.codec.binary.Hex; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class NullUtils { + public static boolean isNotEqual(Object a, Object b) { + return !isEqual(a, b); + } + + public static boolean isEqual(Object a, Object b) { + if (a != null) + return (b != null) && a.equals(b); + return (b == null); + } + + public static boolean isNotEqual(T a, T b, IEquals _equals) { + return !isEqual(a, b, _equals); + } + + public static boolean isEqual(T a, T b, IEquals _equals) { + if (a != null) + return (b != null) && _equals.equals(a, b); + return (b == null); + } + + public static boolean equalsIgnoreCase(String a, String b) { + if (a != null) + return a.equalsIgnoreCase(b); + return (b == null); + } + + public static int length(String _val) { + if (_val == null) + return 0; + return _val.length(); + } + + public static boolean isEmpty(String _sVal) { + return (_sVal == null) || (_sVal.length() == 0); + } + + public static boolean isAnyEmpty(String... _vals) { + if (_vals == null) + return true; + for (String val : _vals) { + if (isEmpty(val)) + return true; + } + return false; + } + + public static boolean isNotEmpty(String _sVal) { + return !isEmpty(_sVal); + } + + public static boolean isAnyNotEmpty(String... _vals) { + if (_vals == null) + return false; + for (String val : _vals) { + if (isNotEmpty(val)) + return true; + } + return false; + } + + public static boolean isAnyNull(Object... _o) { + if ((_o == null) || (_o.length == 0)) + return false; + for (Object o : _o) { + if (o == null) + return true; + } + return false; + } + + public static boolean isOneOf(Object _o, Object... _values) { + if ((_o == null) || (_values == null) || (_values.length == 0)) + return false; + for (Object o : _values) { + if (_o.equals(o)) + return true; + } + return false; + } + + public static String trim(String _val) { + if (_val == null) + return null; + return _val.trim(); + } + + public static String toString(byte[] _arrBytes) { + if (_arrBytes == null) + return null; + try { + return new String(_arrBytes, "UTF-8"); + } + catch (UnsupportedEncodingException e) { + return null; + } + } + + public static byte[] toByteArray(String _value) { + if (_value == null) + return null; + try { + return _value.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) { + return null; + } + } + + public static int toInteger(String _value) { + try { + return Integer.valueOf(makeNotNull(_value)); + } + catch (NumberFormatException _e) { + return 0; + } + } + + public static long toLong(String _value) { + try { + return Long.valueOf(makeNotNull(_value)); + } + catch (NumberFormatException _e) { + return 0; + } + } + + public static double toDouble(String _value) { + try { + return Double.valueOf(makeNotNull(_value)); + } + catch (NumberFormatException _e) { + return 0.0; + } + } + + public static float toFloat(String _value) { + try { + return Float.valueOf(makeNotNull(_value)); + } + catch (NumberFormatException _e) { + return 0f; + } + } + + public static String urlEncode(String _url) { + try { + return URLEncoder.encode(makeNotNull(_url), "UTF-8"); + } + catch (UnsupportedEncodingException e) { + return _url; + } + } + + public static String urlDecode(String _url) { + try { + return URLDecoder.decode(makeNotNull(_url), "UTF-8"); + } + catch (UnsupportedEncodingException e) { + return _url; + } + } + + public static String makeNotNull(String _value) { + if (_value != null) + return _value; + return ""; + } + + public static String after(String _value, String _search) { + if (_value == null) + return ""; + int iPos = _value.lastIndexOf(_search); + if (iPos < 0) + return ""; + return iPos < _value.length() - _search.length() ? _value.substring(iPos + _search.length()) : ""; + } + + public static > T toEnum(Class _enumType, String _sValue) { + return toEnum(_enumType, _sValue, null); + } + + public static > T toEnum(Class _enumType, String _sValue, T _default) { + T e = null; + try { + e = Enum.valueOf(_enumType, _sValue); + } + catch (Throwable t) { + return _default; + } + return e; + } + + public static > List toEnums(Class _enumType, Collection _values) { + List listEnums = new ArrayList(); + for (String value : CollectionUtils.makeNotNull(_values)) { + T e = toEnum(_enumType, value, null); + if (e != null) + listEnums.add(e); + } + return listEnums; + } + + public static > int compare(T a, T b) { + return compare(a, b, true); + } + + public static > int compare(T a, T b, boolean _bNullsFirst) { + if (a != null) { + if (b != null) + return a.compareTo(b); + else + return _bNullsFirst ? 1 : -1; + } + if (b != null) + return _bNullsFirst ? -1 : 1; + return 0; + } + + public static int min(int... values) { + int iMin = Integer.MAX_VALUE; + for (int value : values) { + if (value < iMin) + iMin = value; + } + return iMin; + } + + public static String[] cleanSplit(String _sValue, String _sRegex) { + if (_sValue == null) + return new String[0]; + return removeEmpties(_sValue.split(_sRegex)); + } + + public static String[] removeEmpties(String[] _arr) { + if (_arr == null) + return new String[0]; + int valid = 0; + for (String s : _arr) { + if (NullUtils.isNotEmpty(s)) + valid++; + } + if (valid == _arr.length) + return _arr; + String[] ret = new String[valid]; + valid = 0; + for (String s : _arr) { + if (NullUtils.isNotEmpty(s)) + ret[valid++] = s; + } + return ret; + } + + public static String wrap(String _input, int _lineLength) { + return wrap(_input, _lineLength, false); + } + + public static String wrap(String _input, int _lineLength, boolean carriageReturn) { + if (_input == null) + return null; + StringBuilder output = new StringBuilder(); + int i = 0; + while (i < _input.length()) { + if ((i + _lineLength) > _input.length()) + output.append(_input.substring(i, _input.length())); + else { + output.append(_input.substring(i, i + _lineLength)); + if (carriageReturn) + output.append("\r"); + output.append("\n"); + } + i += _lineLength; + } + return output.toString(); + } + + public static Class getClass(String _className, Class _superClass) { + try { + return Class.forName(_className).asSubclass(_superClass); + } + catch (ClassNotFoundException _e) { + return null; + } + } + + public static String terminateWith(String _value, String _suffix) { + if (_value == null) + return _suffix; + if (_value.endsWith(_suffix)) + return _value; + return _value + _suffix; + } + + public static String toUpperCase(String _value) { + if (_value == null) + return null; + return _value.toUpperCase(); + } + + public static String toLowerCase(String _value) { + if (_value == null) + return null; + return _value.toLowerCase(); + } + + public static Map> parseQueryParams(String _queryString) { + Map> queryParameters = new HashMap<>(); + if (isEmpty(_queryString)) { + return queryParameters; + } + String[] parameters = _queryString.split("&"); + for (String parameter : parameters) { + String[] keyValuePair = parameter.split("="); + if (keyValuePair.length > 1) + CollectionUtils.addToMultiMap(keyValuePair[0], keyValuePair[1], queryParameters); + } + return queryParameters; + } + + public static String toQueryString(Map> _queryParameters) { + StringBuilder queryString = null; + for (Entry> entry : CollectionUtils.makeNotNull(_queryParameters).entrySet()) { + for (String param : CollectionUtils.makeNotNull(entry.getValue())) { + if (NullUtils.isEmpty(param)) + continue; + if (queryString == null) + queryString = new StringBuilder(); + else + queryString.append("&"); + queryString.append(entry.getKey()); + queryString.append("="); + queryString.append(param); + } + } + return queryString == null?"":queryString.toString(); + } + + public static String toHex(String _sValue) + { + return toHex(toByteArray(_sValue)); + } + + public static String toHexBytes(byte[] _btData) + { + List bytes = new ArrayList<>(_btData.length); + for (byte b : _btData) { + bytes.add(String.format("%02X ", b)); + } + return CollectionUtils.delimit(bytes, " "); + } + + public static String toHex(byte[] _btData) + { + try + { + return new String(Hex.encodeHex(_btData)); + } + catch (Exception e) + { + return ""; + } + } + + public static byte[] fromHex(String _sValue) + { + try + { + return Hex.decodeHex(makeNotNull(_sValue).toCharArray()); + } + catch (Exception e) + { + return null; + } + } + + public static int bound(int _value, int _min, int _max) { + if (_value < _min) + return _min; + if (_value > _max) + return _max; + return _value; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ResourceLoader.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ResourceLoader.java new file mode 100644 index 0000000..9e649c8 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ResourceLoader.java @@ -0,0 +1,147 @@ +package com.lanternsoftware.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class ResourceLoader { + protected static final Logger LOG = LoggerFactory.getLogger(ResourceLoader.class); + + public static String getStringResource(Class clazz, String _sResourceFileName) { + String sReply = null; + InputStream stream = null; + try { + stream = clazz.getResourceAsStream(_sResourceFileName); + if (stream != null) + sReply = IOUtils.toString(stream); + } + catch (Exception e) { + LOG.error("Failed to load resource: " + _sResourceFileName, e); + } + finally { + IOUtils.closeQuietly(stream); + } + return sReply == null ? "" : sReply; + } + + public static byte[] getByteArrayResource(Class clazz, String _sResourceFileName) { + byte[] btReply = null; + InputStream stream = null; + try { + stream = clazz.getResourceAsStream(_sResourceFileName); + if (stream != null) + btReply = IOUtils.toByteArray(stream); + } + catch (IOException e) { + LOG.error("Failed to load resource: " + _sResourceFileName, e); + } + finally { + IOUtils.closeQuietly(stream); + } + return btReply; + } + + public static String loadFileAsString(String _fileName) { + return loadFileAsString(new File(_fileName)); + } + + public static String loadFileAsString(File _file) { + return NullUtils.toString(loadFile(_file)); + } + + public static List loadFileLines(String _fileName) { + return loadFileLines(new File(_fileName)); + } + + public static List loadFileLines(File _file) { + if ((_file == null) || !_file.exists()) + return null; + FileReader is = null; + try { + is = new FileReader(_file); + BufferedReader reader = new BufferedReader(is); + List lines = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) + { + lines.add(line); + } + return lines; + } + catch (Throwable t) { + LOG.error("Failed to load file: " + _file.getAbsolutePath(), t); + return Collections.emptyList(); + } + finally { + IOUtils.closeQuietly(is); + } + } + + public static byte[] loadFile(String _fileName) { + return loadFile(new File(_fileName)); + } + + public static byte[] loadFile(File _file) { + if ((_file == null) || !_file.exists()) + return null; + InputStream is = null; + try { + is = new FileInputStream(_file); + return IOUtils.toByteArray(is); + } + catch (Throwable t) { + LOG.error("Failed to load file: " + _file.getAbsolutePath(), t); + return null; + } + finally { + IOUtils.closeQuietly(is); + } + } + + public static void writeFile(String _sFile, String _data) { + writeFile(_sFile, NullUtils.toByteArray(_data)); + } + + public static void writeFile(String _sFile, byte[] _btData) { + FileOutputStream os = null; + try { + os = new FileOutputStream(_sFile, false); + os.write(_btData); + } + catch (Throwable t) { + LOG.error("Failed to write file: " + _sFile, t); + } + finally { + IOUtils.closeQuietly(os); + } + } + + public static void writeFileLines(String _sFile, List _lines) { + FileOutputStream os = null; + try { + os = new FileOutputStream(_sFile, false); + for (String line : CollectionUtils.makeNotNull(_lines)) { + os.write(NullUtils.toByteArray(line)); + os.write((char)10); + } + } + catch (Throwable t) { + LOG.error("Failed to write file: " + _sFile, t); + } + finally { + IOUtils.closeQuietly(os); + } + } + +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ZipUtils.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ZipUtils.java new file mode 100644 index 0000000..5aed23e --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/ZipUtils.java @@ -0,0 +1,51 @@ +package com.lanternsoftware.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class ZipUtils { + private static final Logger LOG = LoggerFactory.getLogger(ZipUtils.class); + + public static byte[] zip(byte[] _btData) { + if (_btData == null) + return null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPOutputStream stream = null; + try { + stream = new GZIPOutputStream(out); + stream.write(_btData); + IOUtils.closeQuietly(stream); + return out.toByteArray(); + } + catch (IOException e) { + IOUtils.closeQuietly(stream); + LOG.error("Failed to zip data", e); + return null; + } + } + + public static byte[] unzip(byte[] _btData) { + if ((_btData == null) || (_btData.length == 0)) + return null; + ByteArrayInputStream in = new ByteArrayInputStream(_btData); + GZIPInputStream stream = null; + try { + stream = new GZIPInputStream(in); + return IOUtils.toByteArray(stream); + } + catch (IOException e) { + LOG.error("Failed to unzip data", e); + return null; + } + finally { + IOUtils.closeQuietly(stream); + } + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ConcurrencyUtils.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ConcurrencyUtils.java new file mode 100644 index 0000000..3fa158f --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ConcurrencyUtils.java @@ -0,0 +1,132 @@ +package com.lanternsoftware.util.concurrency; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; + +public abstract class ConcurrencyUtils +{ + private static final Object m_mutex = new Object(); + private static Map mapMutexes = null; + + public static void sleep(long _lDuration) + { + try + { + Thread.sleep(_lDuration); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + public static void wait(Object _object) + { + try + { + synchronized(_object) + { + _object.wait(); + } + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + public static void wait(Object _object, int _iTimeout) + { + try + { + synchronized(_object) + { + _object.wait(_iTimeout); + } + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + public static void notify(Object _object) + { + try + { + synchronized(_object) + { + _object.notify(); + } + } + catch (IllegalMonitorStateException e) + { + e.printStackTrace(); + } + } + + public static void notifyAll(Object _object) + { + try + { + synchronized(_object) + { + _object.notifyAll(); + } + } + catch (IllegalMonitorStateException e) + { + e.printStackTrace(); + } + } + + public static String getMutex(String _sKey) + { + synchronized (m_mutex) + { + if (mapMutexes == null) + mapMutexes = new HashMap(); + String sMutex = mapMutexes.get(_sKey); + if (sMutex != null) + return sMutex; + mapMutexes.put(_sKey, _sKey); + return _sKey; + } + } + + public static void destroy() + { + if (mapMutexes != null) + { + mapMutexes.clear(); + mapMutexes = null; + } + } + + public static void getAll(Future... _futures) + { + if (_futures == null) + return; + getAll(Arrays.asList(_futures)); + } + + public static void getAll(Collection> _collFutures) + { + if (_collFutures == null) + return; + for (Future f : _collFutures) + { + try + { + f.get(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/Execution.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/Execution.java new file mode 100644 index 0000000..acd05bc --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/Execution.java @@ -0,0 +1,16 @@ +package com.lanternsoftware.util.concurrency; + +import java.util.concurrent.Callable; + +public abstract class Execution implements Callable { + public abstract void run() throws Exception; + public final ExecutionResult call(){ + try { + run(); + } + catch (Exception _e) { + return new ExecutionResult(_e); + } + return new ExecutionResult(null); + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutionResult.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutionResult.java new file mode 100644 index 0000000..18800b1 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutionResult.java @@ -0,0 +1,13 @@ +package com.lanternsoftware.util.concurrency; + +public class ExecutionResult { + private final Exception e; + + public ExecutionResult(Exception _e) { + e = _e; + } + + public Exception getException() { + return e; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutionUtil.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutionUtil.java new file mode 100644 index 0000000..b6dcfdf --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutionUtil.java @@ -0,0 +1,88 @@ +package com.lanternsoftware.util.concurrency; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Future; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; + +public abstract class ExecutionUtil { + private static final Logger LOG = LoggerFactory.getLogger(ExecutionUtil.class); + + public static void waitForExecution(Collection> futures) throws Exception { + Exception e = null; + for (Future f : futures) { + ExecutionResult result = f.get(); + if (result.getException() != null) + e = result.getException(); + } + if (e != null) + throw e; + } + + public static void waitForExecution(Logger _logger, Collection> futures) { + for (Future f : futures) { + try { + f.get(); + } + catch (Exception _e) { + _logger.error("Exception occurred during execution", _e); + } + } + } + + public static void waitForExecution(Future... futures) throws Exception { + Exception e = null; + for (Future f : futures) { + ExecutionResult result = f.get(); + if (result.getException() != null) + e = result.getException(); + } + if (e != null) + throw e; + } + + public static void waitForExecution(Logger _logger, Future... futures) { + for (Future f : futures) { + try { + f.get(); + } + catch (Exception _e) { + _logger.error("Exception occurred during execution", _e); + } + } + } + + public static T get(Future _futures) { + return CollectionUtils.getFirst(getAll(Collections.singletonList(_futures))); + } + + public static List getAll(Future... _futures) { + return getAll(Arrays.asList(_futures)); + } + + public static List getAll(Collection> _futures) { + return getAll(_futures, false); + } + + public static List getAll(Collection> _futures, boolean _includeNulls) { + List ret = new ArrayList<>(); + for (Future future : CollectionUtils.makeNotNull(_futures)) { + try { + T t = future.get(); + if (_includeNulls || (t != null)) + ret.add(t); + } + catch (Exception e) { + LOG.error("Exception while getting future", e); + } + } + return ret; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutorUtil.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutorUtil.java new file mode 100644 index 0000000..a49c174 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/concurrency/ExecutorUtil.java @@ -0,0 +1,13 @@ +package com.lanternsoftware.util.concurrency; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public abstract class ExecutorUtil { + public static ThreadPoolExecutor fixedThreadPool(int _threadCount) { + ThreadPoolExecutor executor = new ThreadPoolExecutor(_threadCount, _threadCount,60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); + executor.allowCoreThreadTimeOut(true); + return executor; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/cryptography/AESTool.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/cryptography/AESTool.java new file mode 100644 index 0000000..b1c8b83 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/cryptography/AESTool.java @@ -0,0 +1,284 @@ +package com.lanternsoftware.util.cryptography; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.LongBuffer; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.KeySpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; + +public class AESTool { + private static final Logger LOG = LoggerFactory.getLogger(AESTool.class); + + private final SecretKey key; + private final static SecureRandom rng = rng(); + private final byte[] iv; + + private static SecureRandom rng() { + try { + SecureRandom rng = SecureRandom.getInstance("SHA1PRNG"); + rng.generateSeed(16); + return rng; + } catch (NoSuchAlgorithmException e) { + LOG.error("Failed to initialize SecureRandom with SHA1PRNG", e); + return null; + } + } + + public static byte[] randomIV() { + return rng.generateSeed(16); + } + + /** + * @return a randomly generated AES secret key + */ + public static SecretKey generateRandomSecretKey() { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + KeySpec spec = new PBEKeySpec(Base64.encodeBase64String(new SecureRandom().generateSeed(32)).toCharArray(), new SecureRandom().generateSeed(32), 65536, 256); + SecretKey key = factory.generateSecret(spec); + return new SecretKeySpec(key.getEncoded(), "AES"); + } catch (Exception e) { + LOG.error("Failed to generate a random AES secret key", e); + return null; + } + } + + public static void printRandomSecretKey() { + SecretKey key = generateRandomSecretKey(); + byte[] btKey = key.getEncoded(); + StringBuilder builder = null; + for (long lValue : toLongs(btKey)) { + if (builder == null) + builder = new StringBuilder("new AESTool("); + else + builder.append(","); + builder.append(lValue); + builder.append("L"); + } + builder.append(");"); + System.out.println(builder.toString()); + } + + /** + * @param _btKey the encoded form of a {@link SecretKey} object. See the {@link SecretKey#getEncoded()} method. + */ + public AESTool(byte[] _btKey) { + this(new SecretKeySpec(_btKey, "AES")); + } + + /** + * @param _btKey the encoded form of a {@link SecretKey} object. See the {@link SecretKey#getEncoded()} method. + * @param _iv the initialization vector to use. If this is set, every call of encrypt for a given input will produce the same output. If null is passed, every call of encrypt for a given input will generate a random IV and the output will be different each time (recommended). + */ + public AESTool(byte[] _btKey, byte[] _iv) { + this(new SecretKeySpec(_btKey, "AES"), _iv); + } + + /** + * @param _arrKey the encoded form of a {@link SecretKey} object converted to an array of long values using the {@link AESTool#toLongs(byte[])} method. See the {@link SecretKey#getEncoded()} method. + */ + public AESTool(long... _arrKey) { + this(new SecretKeySpec(toByteArray(_arrKey), "AES")); + } + + /** + * @param _arrKey the encoded form of a {@link SecretKey} object converted to an array of long values using the {@link AESTool#toLongs(byte[])} method. See the {@link SecretKey#getEncoded()} method. + * @param _iv the initialization vector to use. If this is set, every call of encrypt for a given input will produce the same output. If null is passed, every call of encrypt for a given input will generate a random IV and the output will be different each time (recommended). + */ + public AESTool(byte[] _iv, long... _arrKey) { + this(new SecretKeySpec(toByteArray(_arrKey), "AES"), _iv); + } + + public AESTool(SecretKey _key) { + this(_key, null); + } + + public AESTool(SecretKey _key, byte[] _iv) { + key = _key; + if ((_iv != null) && (_iv.length != 16)) + throw new RuntimeException("Initialization Vector must be null or exactly 16 bytes in length"); + iv = _iv; + } + + /** + * @param _data a string to be encrypted with this tool's secret key + * @return the encrypted data as a base64 encoded string + */ + public String encryptToBase64(String _data) { + return encryptToBase64(toByteArray(_data)); + } + + /** + * @param _btData the binary data to be encrypted with this tool's secret key + * @return the encrypted data as a base64 encoded string + */ + public String encryptToBase64(byte[] _btData) { + if (_btData == null) + return null; + return Base64.encodeBase64String(encrypt(_btData)); + } + + /** + * @param _data a string to be encrypted with this tool's secret key + * @return the encrypted data as a url safe base64 encoded string + */ + public String encryptToUrlSafeBase64(String _data) { + return encryptToUrlSafeBase64(toByteArray(_data)); + } + + /** + * @param _btData the binary data to be encrypted with this tool's secret key + * @return the encrypted data as a url safe base64 encoded string + */ + public String encryptToUrlSafeBase64(byte[] _btData) { + if (_btData == null) + return null; + return Base64.encodeBase64URLSafeString(encrypt(_btData)); + } + + /** + * @param _data a string to be encrypted with this tool's secret key + * @return the encrypted data in binary form + */ + public byte[] encrypt(String _data) { + return encrypt(toByteArray(_data)); + } + + /** + * @param _btData the binary data to be encrypted with this tool's secret key + * @return the encrypted data in binary form + */ + public byte[] encrypt(byte[] _btData) { + if (_btData == null) + return null; + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + byte[] btIV = (iv != null) ? iv : randomIV(); + cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(btIV)); + if (iv != null) + return cipher.doFinal(_btData); + else { + byte[] btSalt = rng.generateSeed(16); + return CollectionUtils.merge(btIV, cipher.doFinal(CollectionUtils.merge(btSalt, _btData))); + } + } catch (Exception e) { + LOG.error("Failed to encrypt data", e); + return null; + } + } + + /** + * @param _base64 the base64 encoded representation of the aes encrypted byte array to be decrypted with this tool's + * secret key + * @return the decrypted byte array transformed to a string. + */ + public String decryptFromBase64ToString(String _base64) { + return toString(decryptFromBase64(_base64)); + } + + /** + * @param _base64 the base64 encoded representation of the aes encrypted byte array to be decrypted with this tool's + * secret key + * @return the decrypted byte array + */ + public byte[] decryptFromBase64(String _base64) { + return _base64 == null ? null : decrypt(Base64.decodeBase64(_base64)); + } + + /** + * @param _btData the encrypted byte array to be decrypted with this tool's secret key + * @return the decrypted byte array transformed to a string + */ + public String decryptToString(byte[] _btData) { + return toString(decrypt(_btData)); + } + + /** + * @param _btData the encrypted byte array to be decrypted with this tool's secret key + * @return the decrypted byte array + */ + public byte[] decrypt(byte[] _btData) { + if (_btData == null) + return null; + try { + Cipher decipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + if (iv == null) { + decipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(Arrays.copyOfRange(_btData, 0, 16))); + byte[] btData = decipher.doFinal(Arrays.copyOfRange(_btData, 16, _btData.length)); + return Arrays.copyOfRange(btData, 16, btData.length); + } else { + decipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); + return decipher.doFinal(_btData); + } + } catch (Exception e) { + LOG.error("Failed to decrypt data", e); + return null; + } + } + + /** + * @param _btData a byte array to convert to an array of longs + * @return the array of long values that contains the data from the byte array. + */ + public static long[] toLongs(byte[] _btData) { + if (_btData == null) + return null; + long[] lData = new long[_btData.length / 8]; + LongBuffer data = ByteBuffer.wrap(_btData).order(ByteOrder.BIG_ENDIAN).asLongBuffer(); + data.get(lData); + return lData; + } + + /** + * @param _arrLongs an array of longs to convert into a byte array representing the same data + * @return the converted byte array + */ + public static byte[] toByteArray(long... _arrLongs) { + ByteBuffer input = ByteBuffer.allocate(_arrLongs.length * 8).order(ByteOrder.BIG_ENDIAN); + for (long lInput : _arrLongs) { + input.putLong(lInput); + } + return input.array(); + } + + /** + * Handles and logs the missing encoding exception that will never happen. + * + * @param _btString the UTF-8 encoded representation of a string + * @return the String object created from the byte array + */ + public static String toString(byte[] _btString) { + if (_btString == null) + return null; + return new String(_btString, StandardCharsets.UTF_8); + } + + /** + * Handles and logs the missing encoding exception that will never happen. + * + * @param _value the string to turn into a byte array + * @return the UTF-8 encoded byte array representation of the string + */ + public static byte[] toByteArray(String _value) { + if (_value == null) + return null; + return _value.getBytes(StandardCharsets.UTF_8); + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/cryptography/RSAUtils.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/cryptography/RSAUtils.java new file mode 100644 index 0000000..3fc05b6 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/cryptography/RSAUtils.java @@ -0,0 +1,220 @@ +package com.lanternsoftware.util.cryptography; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateFactory; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import com.lanternsoftware.util.NullUtils; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; + +public abstract class RSAUtils { + private static final Logger LOG = LoggerFactory.getLogger(RSAUtils.class); + + public static KeyPair generateRandomRSAKeyPair() { + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + return keyPairGenerator.genKeyPair(); + } + catch (NoSuchAlgorithmException _e) { + LOG.error("Failed to generate RSA key pair", _e); + return null; + } + } + + public static String toString(RSAPrivateKey _key) { + if (_key == null) + return null; + StringBuilder b = new StringBuilder(Base64.encodeBase64String(_key.getModulus().toByteArray())); + b.append(","); + b.append(Base64.encodeBase64String(_key.getPrivateExponent().toByteArray())); + return b.toString(); + } + + public static String toString(RSAPublicKey _key) { + if (_key == null) + return null; + StringBuilder b = new StringBuilder(Base64.encodeBase64String(_key.getModulus().toByteArray())); + b.append(","); + b.append(Base64.encodeBase64String(_key.getPublicExponent().toByteArray())); + return b.toString(); + } + + public static String toPEM(RSAPublicKey _key) { + StringBuilder pem = new StringBuilder("-----BEGIN PUBLIC KEY-----\r\n"); + pem.append(NullUtils.wrap(Base64.encodeBase64String(_key.getEncoded()), 64, true)); + pem.append("\r\n-----END PUBLIC KEY-----"); + return pem.toString(); + } + + public static String toPEM(Certificate _cert) { + try { + StringBuilder pem = new StringBuilder("-----BEGIN CERTIFICATE-----\r\n"); + pem.append(NullUtils.wrap(Base64.encodeBase64String(_cert.getEncoded()), 64, true)); + pem.append("\r\n-----END CERTIFICATE-----"); + return pem.toString(); + } catch (CertificateEncodingException _e) { + LOG.error("Failed to generate certificate PEM", _e); + return null; + } + } + + public static String toPEM(RSAPrivateKey _key) { + StringBuilder pem = new StringBuilder("-----BEGIN RSA PRIVATE KEY-----\r\n"); + pem.append(NullUtils.wrap(Base64.encodeBase64String(_key.getEncoded()), 64, true)); + pem.append("\r\n-----END RSA PRIVATE KEY-----"); + return pem.toString(); + } + + public static RSAPrivateKey toPrivateKey(String _privateKey64) { + try { + String[] parts = NullUtils.makeNotNull(_privateKey64).split(","); + if (CollectionUtils.size(parts) == 2) { + KeyFactory fact = KeyFactory.getInstance("RSA"); + RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(new BigInteger(Base64.decodeBase64(parts[0])), new BigInteger(Base64.decodeBase64(parts[1]))); + return (RSAPrivateKey) fact.generatePrivate(keySpec); + } + } + catch (Exception _e) { + LOG.error("Failed to generate RSA private key", _e); + } + return null; + } + + public static RSAPublicKey toPublicKey(String _publicKey64) { + try { + String[] parts = NullUtils.makeNotNull(_publicKey64).split(","); + if (CollectionUtils.size(parts) == 2) { + KeyFactory fact = KeyFactory.getInstance("RSA"); + RSAPublicKeySpec keySpec = new RSAPublicKeySpec(new BigInteger(Base64.decodeBase64(parts[0])), new BigInteger(Base64.decodeBase64(parts[1]))); + return (RSAPublicKey) fact.generatePublic(keySpec); + } + } + catch (Exception _e) { + LOG.error("Failed to generate RSA public key", _e); + } + return null; + } + + public static RSAPublicKey fromPEMtoPublicKey(String _pem) { + if (_pem == null) + return null; + String pem = _pem.replaceAll("(-+BEGIN PUBLIC KEY-+|-+END PUBLIC KEY-+|\\r|\\n)", ""); + X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decodeBase64(pem)); + try { + KeyFactory fact = KeyFactory.getInstance("RSA"); + return (RSAPublicKey) fact.generatePublic(spec); + } + catch (Exception _e) { + LOG.error("Failed to generate RSA public key", _e); + return null; + } + } + + public static Certificate fromPEMtoCertificate(String _pem) { + String pem = _pem.replaceAll("(-+BEGIN CERTIFICATE-+|-+END CERTIFICATE-+|\\r|\\n)", ""); + ByteArrayInputStream is = null; + try { + is = new ByteArrayInputStream(Base64.decodeBase64(pem)); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return cf.generateCertificate(is); + } + catch (Exception _e) { + LOG.error("Failed to generate RSA certificate", _e); + return null; + } + finally { + IOUtils.closeQuietly(is); + } + } + + public static Certificate loadCert(String _keystoreFileName, String _keystorePassword, String _certAlias) { + return loadCert(loadKeystore(_keystoreFileName, _keystorePassword), _certAlias); + } + + public static Certificate loadCert(InputStream _is, String _keystorePassword, String _certAlias) { + return loadCert(loadKeystore(_is, _keystorePassword), _certAlias); + } + + public static Certificate loadCert(KeyStore _keystore, String _certAlias) { + try { + return _keystore.getCertificate(_certAlias); + } + catch (Exception e) { + LOG.error("Failed to load certificate {}", e.getMessage(), e); + return null; + } + } + + public static PrivateKey loadPrivateKey(String _keystoreFileName, String _password, String _certAlias) { + return loadPrivateKey(_keystoreFileName, _password, _password, _certAlias); + } + + public static PrivateKey loadPrivateKey(InputStream _is, String _password, String _certAlias) { + return loadPrivateKey(_is, _password, _password, _certAlias); + } + + public static PrivateKey loadPrivateKey(String _keystoreFileName, String _keystorePassword, String _certPassword, String _certAlias) { + return getPrivateKey(loadKeystore(_keystoreFileName, _keystorePassword), _certPassword, _certAlias); + } + + public static PrivateKey loadPrivateKey(InputStream _is, String _keystorePassword, String _certPassword, String _certAlias) { + return getPrivateKey(loadKeystore(_is, _keystorePassword), _certPassword, _certAlias); + } + + public static PrivateKey getPrivateKey(KeyStore _keystore, String _certPassword, String _certAlias) { + try { + return (PrivateKey) _keystore.getKey(_certAlias, _certPassword.toCharArray()); + } + catch (Exception e) { + LOG.error("Failed to load key: {}", e.getMessage(), e); + return null; + } + } + + public static KeyStore loadKeystore(String _keystoreFileName, String _keystorePassword) { + try { + return loadKeystore(new FileInputStream(_keystoreFileName), _keystorePassword); + } + catch (Exception e) { + LOG.error("Failed to load keystore: {}", e.getMessage(), e); + return null; + } + } + + public static KeyStore loadKeystore(InputStream _is, String _keystorePassword) { + try { + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(_is, _keystorePassword.toCharArray()); + return keystore; + } + catch (Exception e) { + LOG.error("Failed to load keystore: {}", e.getMessage(), e); + return null; + } + finally { + IOUtils.closeQuietly(_is); + } + } + +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSV.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSV.java new file mode 100644 index 0000000..730b456 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSV.java @@ -0,0 +1,102 @@ +package com.lanternsoftware.util.csv; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; + +import java.util.ArrayList; +import java.util.List; + +public class CSV { + public final int columns; + public final int rows; + + private final List header; + private final List data; + + public CSV() { + this(null, null, 0); + } + + public CSV(List _header, List> _data, int _columnCount) { + header = _header; + if (_data == null) { + columns = 0; + rows = 0; + data = new ArrayList<>(0); + } + else { + rows = _data.size(); + columns = _columnCount; + data = new ArrayList<>(rows); + for (List listSourceRow : _data) { + CSVCell[] row = new CSVCell[columns]; + int iCol = 0; + for (String sCol : CollectionUtils.makeNotNull(listSourceRow)) { + row[iCol] = new CSVCell(sCol, sCol); + iCol++; + } + while (iCol < columns) { + row[iCol] = new CSVCell("", ""); + iCol++; + } + data.add(row); + } + } + } + + public CSV(List _header, List> _data) { + header = _header; + if (_data == null) { + columns = 0; + rows = 0; + data = new ArrayList<>(0); + } + else { + rows = _data.size(); + data = new ArrayList<>(rows); + int iMaxColumn = CollectionUtils.size(header); + for (List listSourceRow : _data) { + iMaxColumn = Math.max(iMaxColumn, CollectionUtils.size(listSourceRow)); + } + columns = iMaxColumn; + for (List listSourceRow : _data) { + CSVCell[] row = new CSVCell[columns]; + int iCol = 0; + for (CSVCell cell : CollectionUtils.makeNotNull(listSourceRow)) { + row[iCol] = cell == null ? new CSVCell("", "") : cell; + iCol++; + } + while (iCol < columns) { + row[iCol] = new CSVCell("", ""); + iCol++; + } + data.add(row); + } + } + } + + public String cell(int _row, int _column) { + if ((_row < 0) || (_row >= rows) || (_column < 0) || (_column >= columns)) + return ""; + CSVCell cell = data.get(_row)[_column]; + if ((cell == null) || (cell.display == null)) + return ""; + return cell.display; + } + + public List getHeaders() { + return header; + } + + public String getHeader(int _column) { + return NullUtils.makeNotNull(CollectionUtils.get(header, _column)); + } + + public int getRows() { + return rows; + } + + public int getColumns() { + return columns; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSVCell.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSVCell.java new file mode 100644 index 0000000..f835d19 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSVCell.java @@ -0,0 +1,59 @@ +package com.lanternsoftware.util.csv; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.lanternsoftware.util.NullUtils; + +public class CSVCell implements Comparable { + public final String display; + public final Object comparable; + public final boolean reverseSort; + + public CSVCell(String _display) { + this(_display, _display); + } + + public CSVCell(String _display, Object _comparable) { + this(_display, _comparable, false); + } + + public CSVCell(String _display, Object _comparable, boolean _reverseSort) { + display = _display; + comparable = _comparable; + reverseSort = _reverseSort; + } + + @Override + public int compareTo(CSVCell _o) { + if (_o == null) + return 1; + Object type = (comparable == null) ? _o.comparable : comparable; + if (type instanceof String) + return NullUtils.compare((String) comparable, (String) _o.comparable, false); + if (type instanceof Date) + return NullUtils.compare((Date) comparable, (Date) _o.comparable, false); + if (type instanceof Integer) + return NullUtils.compare((Integer) comparable, (Integer) _o.comparable, false); + if (type instanceof Long) + return NullUtils.compare((Long) comparable, (Long) _o.comparable, false); + if (type instanceof Double) + return NullUtils.compare((Double) comparable, (Double) _o.comparable, false); + if (type instanceof Boolean) + return NullUtils.compare((Boolean) comparable, (Boolean) _o.comparable, false); + if (type instanceof Float) + return NullUtils.compare((Float) comparable, (Float) _o.comparable, false); + return 0; + } + + public static List asList(String... _data) { + if (_data == null) + return new ArrayList(0); + List listCells = new ArrayList(_data.length); + for (String data : _data) { + listCells.add(new CSVCell(data)); + } + return listCells; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSVReader.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSVReader.java new file mode 100644 index 0000000..9bda0eb --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/csv/CSVReader.java @@ -0,0 +1,132 @@ +package com.lanternsoftware.util.csv; + +import org.apache.commons.io.IOUtils; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +public abstract class CSVReader +{ + public static CSV loadCSVFromFile(String _filename) + { + return loadCSVFromFile(_filename, false); + } + + public static CSV loadCSVFromFile(String _filename, boolean _firstRowIsHeader) + { + FileInputStream is = null; + try + { + int iMaxColumns = 0; + List> listLines = new LinkedList>(); + is = new FileInputStream(_filename); + BufferedReader r = new BufferedReader(new InputStreamReader(is)); + List listHeader = null; + List listCurLine = new ArrayList(); + String sRemainder = null; + String sLine = r.readLine(); + while (sLine != null) + { + if (sRemainder == null) + sRemainder = parseLine(sLine, listCurLine); + else + sRemainder = parseLine(sRemainder+sLine, listCurLine); + if (sRemainder == null) + { + if (_firstRowIsHeader && (listHeader == null)) + listHeader = listCurLine; + else + listLines.add(listCurLine); + iMaxColumns = Math.max(iMaxColumns, listCurLine.size()); + listCurLine = new ArrayList(); + } + sLine = r.readLine(); + } + return new CSV(listHeader, listLines, iMaxColumns); + } + catch (Throwable t) + { + t.printStackTrace(); + return null; + } + finally + { + IOUtils.closeQuietly(is); + } + } + + public static CSV parseCSV(String _csv) + { + return parseCSV(_csv, false); + } + + public static CSV parseCSV(String _csv, boolean _bFirstRowIsHeader) + { + _csv = _csv.replace("\r",""); + int iMaxColumns = 0; + List> listLines = new LinkedList>(); + List listHeader = null; + List listCurLine = new ArrayList(); + String sRemainder = null; + for (String sLine : _csv.split("\n")) + { + if (sRemainder == null) + sRemainder = parseLine(sLine, listCurLine); + else + sRemainder = parseLine(sRemainder+sLine, listCurLine); + if (sRemainder == null) + { + if (_bFirstRowIsHeader && (listHeader == null)) + listHeader = listCurLine; + else + listLines.add(listCurLine); + iMaxColumns = Math.max(iMaxColumns, listCurLine.size()); + listCurLine = new ArrayList(); + } + } + return new CSV(listHeader, listLines, iMaxColumns); + } + + public static String parseLine(String _sLine, List _listCurLine) + { + int i=0; + while (i < _sLine.length()) + { + if (_sLine.charAt(i) == '"') + { + int iPos = _sLine.indexOf("\",",i+1); + if (iPos < 0) + { + if (!_sLine.endsWith("\"")) + return _sLine.substring(i); + else + { + _listCurLine.add(_sLine.substring(i+1, _sLine.length()-1)); + return null; + } + } + else + { + _listCurLine.add(_sLine.substring(i+1, iPos)); + i = iPos+2; + } + } + else + { + int iPos = _sLine.indexOf(",",i); + if (iPos < 0) + { + _listCurLine.add(_sLine.substring(i)); + return null; + } + _listCurLine.add(_sLine.substring(i, iPos)); + i = iPos+1; + } + } + return null; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/email/EmailValidator.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/email/EmailValidator.java new file mode 100644 index 0000000..ff4de33 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/email/EmailValidator.java @@ -0,0 +1,528 @@ +package com.lanternsoftware.util.email; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EmailValidator { + private static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; + private static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]"; + private static final String QUOTED_USER = "(\"[^\"]*\")"; + private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; + + private static final String LEGAL_ASCII_REGEX = "^\\p{ASCII}+$"; + private static final String EMAIL_REGEX = "^\\s*?(.+)@(.+?)\\s*$"; + private static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; + private static final String USER_REGEX = "^\\s*" + WORD + "(\\." + WORD + ")*$"; + + private static final Pattern MATCH_ASCII_PATTERN = Pattern.compile(LEGAL_ASCII_REGEX); + private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); + private static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX); + private static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX); + + private static final String IPV4_REGEX = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"; + private final RegexValidator ipv4Validator = new RegexValidator(IPV4_REGEX); + + private static final String DOMAIN_LABEL_REGEX = "\\p{Alnum}(?>[\\p{Alnum}-]*\\p{Alnum})*"; + private static final String TOP_LABEL_REGEX = "\\p{Alpha}{2,}"; + private static final String DOMAIN_NAME_REGEX = "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + ")$"; + + /** RegexValidator for matching domains. */ + private final RegexValidator domainRegex = new RegexValidator(DOMAIN_NAME_REGEX); + + private static final String[] INFRASTRUCTURE_TLDS = new String[] { "arpa", "root" }; + + private static final String[] GENERIC_TLDS = new String[] { "aero", // air transport industry + "asia", // Pan-Asia/Asia Pacific + "biz", // businesses + "cat", // Catalan linguistic/cultural community + "com", // commercial enterprises + "coop", // cooperative associations + "info", // informational sites + "jobs", // Human Resource managers + "mobi", // mobile products and services + "museum", // museums, surprisingly enough + "name", // individuals' sites + "net", // internet support infrastructure/business + "org", // noncommercial organizations + "pro", // credentialed professionals and entities + "tel", // contact data for businesses and individuals + "travel", // entities in the travel industry + "gov", // United States Government + "edu", // accredited postsecondary US education entities + "mil", // United States Military + "int" // organizations established by international treaty + }; + + private static final String[] COUNTRY_CODE_TLDS = new String[] { "ac", // Ascension Island + "ad", // Andorra + "ae", // United Arab Emirates + "af", // Afghanistan + "ag", // Antigua and Barbuda + "ai", // Anguilla + "al", // Albania + "am", // Armenia + "an", // Netherlands Antilles + "ao", // Angola + "aq", // Antarctica + "ar", // Argentina + "as", // American Samoa + "at", // Austria + "au", // Australia (includes Ashmore and Cartier Islands and Coral Sea Islands) + "aw", // Aruba + "ax", // Åland + "az", // Azerbaijan + "ba", // Bosnia and Herzegovina + "bb", // Barbados + "bd", // Bangladesh + "be", // Belgium + "bf", // Burkina Faso + "bg", // Bulgaria + "bh", // Bahrain + "bi", // Burundi + "bj", // Benin + "bm", // Bermuda + "bn", // Brunei Darussalam + "bo", // Bolivia + "br", // Brazil + "bs", // Bahamas + "bt", // Bhutan + "bv", // Bouvet Island + "bw", // Botswana + "by", // Belarus + "bz", // Belize + "ca", // Canada + "cc", // Cocos (Keeling) Islands + "cd", // Democratic Republic of the Congo (formerly Zaire) + "cf", // Central African Republic + "cg", // Republic of the Congo + "ch", // Switzerland + "ci", // Côte d'Ivoire + "ck", // Cook Islands + "cl", // Chile + "cm", // Cameroon + "cn", // China, mainland + "co", // Colombia + "cr", // Costa Rica + "cu", // Cuba + "cv", // Cape Verde + "cx", // Christmas Island + "cy", // Cyprus + "cz", // Czech Republic + "de", // Germany + "dj", // Djibouti + "dk", // Denmark + "dm", // Dominica + "do", // Dominican Republic + "dz", // Algeria + "ec", // Ecuador + "ee", // Estonia + "eg", // Egypt + "er", // Eritrea + "es", // Spain + "et", // Ethiopia + "eu", // European Union + "fi", // Finland + "fj", // Fiji + "fk", // Falkland Islands + "fm", // Federated States of Micronesia + "fo", // Faroe Islands + "fr", // France + "ga", // Gabon + "gb", // Great Britain (United Kingdom) + "gd", // Grenada + "ge", // Georgia + "gf", // French Guiana + "gg", // Guernsey + "gh", // Ghana + "gi", // Gibraltar + "gl", // Greenland + "gm", // The Gambia + "gn", // Guinea + "gp", // Guadeloupe + "gq", // Equatorial Guinea + "gr", // Greece + "gs", // South Georgia and the South Sandwich Islands + "gt", // Guatemala + "gu", // Guam + "gw", // Guinea-Bissau + "gy", // Guyana + "hk", // Hong Kong + "hm", // Heard Island and McDonald Islands + "hn", // Honduras + "hr", // Croatia (Hrvatska) + "ht", // Haiti + "hu", // Hungary + "id", // Indonesia + "ie", // Ireland (Éire) + "il", // Israel + "im", // Isle of Man + "in", // India + "io", // British Indian Ocean Territory + "iq", // Iraq + "ir", // Iran + "is", // Iceland + "it", // Italy + "je", // Jersey + "jm", // Jamaica + "jo", // Jordan + "jp", // Japan + "ke", // Kenya + "kg", // Kyrgyzstan + "kh", // Cambodia (Khmer) + "ki", // Kiribati + "km", // Comoros + "kn", // Saint Kitts and Nevis + "kp", // North Korea + "kr", // South Korea + "kw", // Kuwait + "ky", // Cayman Islands + "kz", // Kazakhstan + "la", // Laos (currently being marketed as the official domain for Los Angeles) + "lb", // Lebanon + "lc", // Saint Lucia + "li", // Liechtenstein + "lk", // Sri Lanka + "lr", // Liberia + "ls", // Lesotho + "lt", // Lithuania + "lu", // Luxembourg + "lv", // Latvia + "ly", // Libya + "ma", // Morocco + "mc", // Monaco + "md", // Moldova + "me", // Montenegro + "mg", // Madagascar + "mh", // Marshall Islands + "mk", // Republic of Macedonia + "ml", // Mali + "mm", // Myanmar + "mn", // Mongolia + "mo", // Macau + "mp", // Northern Mariana Islands + "mq", // Martinique + "mr", // Mauritania + "ms", // Montserrat + "mt", // Malta + "mu", // Mauritius + "mv", // Maldives + "mw", // Malawi + "mx", // Mexico + "my", // Malaysia + "mz", // Mozambique + "na", // Namibia + "nc", // New Caledonia + "ne", // Niger + "nf", // Norfolk Island + "ng", // Nigeria + "ni", // Nicaragua + "nl", // Netherlands + "no", // Norway + "np", // Nepal + "nr", // Nauru + "nu", // Niue + "nz", // New Zealand + "om", // Oman + "pa", // Panama + "pe", // Peru + "pf", // French Polynesia With Clipperton Island + "pg", // Papua New Guinea + "ph", // Philippines + "pk", // Pakistan + "pl", // Poland + "pm", // Saint-Pierre and Miquelon + "pn", // Pitcairn Islands + "pr", // Puerto Rico + "ps", // Palestinian territories (PA-controlled West Bank and Gaza Strip) + "pt", // Portugal + "pw", // Palau + "py", // Paraguay + "qa", // Qatar + "re", // Réunion + "ro", // Romania + "rs", // Serbia + "ru", // Russia + "rw", // Rwanda + "sa", // Saudi Arabia + "sb", // Solomon Islands + "sc", // Seychelles + "sd", // Sudan + "se", // Sweden + "sg", // Singapore + "sh", // Saint Helena + "si", // Slovenia + "sj", // Svalbard and Jan Mayen Islands Not in use (Norwegian dependencies; see .no) + "sk", // Slovakia + "sl", // Sierra Leone + "sm", // San Marino + "sn", // Senegal + "so", // Somalia + "sr", // Suriname + "st", // São Tomé and Príncipe + "su", // Soviet Union (deprecated) + "sv", // El Salvador + "sy", // Syria + "sz", // Swaziland + "tc", // Turks and Caicos Islands + "td", // Chad + "tf", // French Southern and Antarctic Lands + "tg", // Togo + "th", // Thailand + "tj", // Tajikistan + "tk", // Tokelau + "tl", // East Timor (deprecated old code) + "tm", // Turkmenistan + "tn", // Tunisia + "to", // Tonga + "tp", // East Timor + "tr", // Turkey + "tt", // Trinidad and Tobago + "tv", // Tuvalu + "tw", // Taiwan, Republic of China + "tz", // Tanzania + "ua", // Ukraine + "ug", // Uganda + "uk", // United Kingdom + "um", // United States Minor Outlying Islands + "us", // United States of America + "uy", // Uruguay + "uz", // Uzbekistan + "va", // Vatican City State + "vc", // Saint Vincent and the Grenadines + "ve", // Venezuela + "vg", // British Virgin Islands + "vi", // U.S. Virgin Islands + "vn", // Vietnam + "vu", // Vanuatu + "wf", // Wallis and Futuna + "ws", // Samoa (formerly Western Samoa) + "ye", // Yemen + "yt", // Mayotte + "yu", // Serbia and Montenegro (originally Yugoslavia) + "za", // South Africa + "zm", // Zambia + "zw", // Zimbabwe + }; + + private static final List INFRASTRUCTURE_TLD_LIST = Arrays.asList(INFRASTRUCTURE_TLDS); + private static final List GENERIC_TLD_LIST = Arrays.asList(GENERIC_TLDS); + private static final Set COUNTRY_CODE_TLD_LIST = new HashSet(Arrays.asList(COUNTRY_CODE_TLDS)); + + private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator(); + + public static EmailValidator getInstance() + { + return EMAIL_VALIDATOR; + } + + public boolean isValid(String email) + { + if (email == null) + return false; + + Matcher asciiMatcher = MATCH_ASCII_PATTERN.matcher(email); + if (!asciiMatcher.matches()) + return false; + + // Check the whole email address structure + Matcher emailMatcher = EMAIL_PATTERN.matcher(email); + if (!emailMatcher.matches()) + return false; + + if (email.endsWith(".")) + return false; + + if (!isValidUser(emailMatcher.group(1))) + return false; + + if (!isValidDomain(emailMatcher.group(2))) + return false; + return true; + } + + protected boolean isValidDomain(String domain) + { + // see if domain is an IP address in brackets + Matcher ipDomainMatcher = IP_DOMAIN_PATTERN.matcher(domain); + + if (ipDomainMatcher.matches()) + return isValidInet4Address(ipDomainMatcher.group(1)); + else + return isValidTldDomain(domain); + } + + protected boolean isValidInet4Address(String inet4Address) + { + // verify that address conforms to generic IPv4 format + String[] groups = ipv4Validator.match(inet4Address); + + if (groups == null) + return false; + + // verify that address subgroups are legal + for (int i = 0; i <= 3; i++) + { + String ipSegment = groups[i]; + if (ipSegment == null || ipSegment.length() <= 0) + return false; + + int iIpSegment = 0; + + try + { + iIpSegment = Integer.parseInt(ipSegment); + } + catch (NumberFormatException e) + { + return false; + } + if (iIpSegment > 255) + return false; + } + return true; + } + + protected boolean isValidUser(String user) + { + return USER_PATTERN.matcher(user).matches(); + } + + protected boolean isValidTldDomain(String domain) + { + String[] groups = domainRegex.match(domain); + if (groups != null && groups.length > 0) + return isValidTld(groups[0]); + return false; + } + + protected boolean isValidTld(String tld) + { + return isValidInfrastructureTld(tld) || isValidGenericTld(tld) || isValidCountryCodeTld(tld); + } + + protected boolean isValidInfrastructureTld(String iTld) + { + return INFRASTRUCTURE_TLD_LIST.contains(chompLeadingDot(iTld.toLowerCase())); + } + + protected boolean isValidGenericTld(String gTld) + { + return GENERIC_TLD_LIST.contains(chompLeadingDot(gTld.toLowerCase())); + } + + protected boolean isValidCountryCodeTld(String ccTld) + { + return COUNTRY_CODE_TLD_LIST.contains(chompLeadingDot(ccTld.toLowerCase())); + } + + private String chompLeadingDot(String str) + { + if (str.startsWith(".")) + return str.substring(1); + else + return str; + } + + private class RegexValidator implements Serializable + { + private static final long serialVersionUID = -8832409930574867162L; + + private final Pattern[] patterns; + + /** Construct a case sensitive validator for a single regular expression. + * + * @param regex + * The regular expression this validator will validate against */ + public RegexValidator(String regex) + { + this(regex, true); + } + + /** Construct a validator for a single regular expression with the specified case sensitivity. + * + * @param regex + * The regular expression this validator will validate against + * @param caseSensitive + * when true matching is case sensitive, otherwise matching is case in-sensitive */ + public RegexValidator(String regex, boolean caseSensitive) + { + this(new String[] { regex }, caseSensitive); + } + + /** Construct a validator that matches any one of the set of regular expressions with the specified case sensitivity. + * + * @param regexs + * The set of regular expressions this validator will validate against + * @param caseSensitive + * when true matching is case sensitive, otherwise matching is case in-sensitive */ + public RegexValidator(String[] regexs, boolean caseSensitive) + { + if (regexs == null || regexs.length == 0) + { + throw new IllegalArgumentException("Regular expressions are missing"); + } + patterns = new Pattern[regexs.length]; + int flags = (caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); + for (int i = 0; i < regexs.length; i++) + { + if (regexs[i] == null || regexs[i].length() == 0) + { + throw new IllegalArgumentException("Regular expression[" + i + "] is missing"); + } + patterns[i] = Pattern.compile(regexs[i], flags); + } + } + + /** Validate a value against the set of regular expressions returning the array of matched groups. + * + * @param value + * The value to validate. + * @return String array of the groups matched if valid or null if invalid */ + public String[] match(String value) + { + if (value == null) + { + return null; + } + for (int i = 0; i < patterns.length; i++) + { + Matcher matcher = patterns[i].matcher(value); + if (matcher.matches()) + { + int count = matcher.groupCount(); + String[] groups = new String[count]; + for (int j = 0; j < count; j++) + { + groups[j] = matcher.group(j + 1); + } + return groups; + } + } + return null; + } + + /** Provide a String representation of this validator. + * + * @return A String representation of this validator */ + public String toString() + { + StringBuffer buffer = new StringBuffer(); + buffer.append("RegexValidator{"); + for (int i = 0; i < patterns.length; i++) + { + if (i > 0) + { + buffer.append(","); + } + buffer.append(patterns[i].pattern()); + } + buffer.append("}"); + return buffer.toString(); + } + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/AbstractHashTool.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/AbstractHashTool.java new file mode 100644 index 0000000..beb0a82 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/AbstractHashTool.java @@ -0,0 +1,116 @@ +package com.lanternsoftware.util.hash; + +import java.security.MessageDigest; + +import com.lanternsoftware.util.NullUtils; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; + +public abstract class AbstractHashTool { + private final Logger LOG = LoggerFactory.getLogger(getClass()); + + protected final MessageDigest digest; + protected final byte[] staticSalt; + protected final boolean prependSalt; + protected final int iterations; + + public AbstractHashTool(String _algorithm, String _staticSalt, boolean _prependSalt, int _iterationCount) { + prependSalt = _prependSalt; + MessageDigest digestInstance = null; + try { + digestInstance = MessageDigest.getInstance(_algorithm); + } + catch (Exception e) { + LOG.error("Failed to create digest: " + _algorithm, e); + } + digest = digestInstance; + staticSalt = NullUtils.toByteArray(_staticSalt); + iterations = Math.max(1, _iterationCount); + } + + /** + * @param _value a string value to hash using the static salt and algorithm of this hash tool + * @return the hex encoded hashed value + */ + public synchronized String hashHex(String _value) { + return hashHex(_value, null); + } + + /** + * @param _value a string value to hash using the static salt and algorithm of this hash tool + * @param _salt a salt to use for this hash operation along with the static salt of this hash tool + * @return the hex encoded hashed value + */ + public synchronized String hashHex(String _value, String _salt) { + return new String(Hex.encodeHex(hash(_value, _salt))); + } + + /** + * @param _value a string value to hash using the static salt and algorithm of this hash tool + * @return the base64 encoded hashed value + */ + public synchronized String hash64(String _value) { + return hash64(_value, null); + } + + /** + * @param _value a string value to hash using the static salt and algorithm of this hash tool + * @param _salt a salt to use for this hash operation along with the static salt of this hash tool + * @return the base64 encoded hashed value + */ + public synchronized String hash64(String _value, String _salt) { + return new String(Base64.encodeBase64(hash(_value, _salt))); + } + + /** + * @param _value a string value to hash using the static salt and algorithm of this hash tool + * @return the hashed value + */ + public synchronized byte[] hash(String _value) { + return hash(_value, null); + } + + /** + * @param _value a string value to hash using the static salt and algorithm of this hash tool + * @param _salt a salt to use for this hash operation along with the static salt of this hash tool + * @return the hashed value + */ + public synchronized byte[] hash(String _value, String _salt) { + byte[] btValue = NullUtils.toByteArray(_value); + byte[] btSalt = NullUtils.toByteArray(_salt); + for (int i = 0; i < iterations; i++) + btValue = hash(salt(btValue, btSalt)); + return btValue; + } + + /** + * @param _value a byte array to hash using the static salt and algorithm of this hash tool + * @return the base64 encoded hashed value + */ + public synchronized String hash64(byte[] _value) { + return new String(Base64.encodeBase64(hash(_value))); + } + + /** + * @param _value a byte array to hash using the static salt and algorithm of this hash tool + * @return the hashed value + */ + public synchronized byte[] hash(byte[] _value) { + return digest.digest(_value); + } + + /** + * @param _value a byte array to hash using the static salt and algorithm of this hash tool + * @param _salt a salt to use for this hash operation along with the static salt of this hash tool + * @return the hashed value + */ + public synchronized byte[] salt(byte[] _value, byte[] _salt) { + if (prependSalt) + return CollectionUtils.merge(staticSalt, _salt, _value); + return CollectionUtils.merge(_value, staticSalt, _salt); + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/MD5HashTool.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/MD5HashTool.java new file mode 100644 index 0000000..abe8297 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/MD5HashTool.java @@ -0,0 +1,27 @@ +package com.lanternsoftware.util.hash; + +public class MD5HashTool extends AbstractHashTool { + /** + * Creates an MD5 hash tool with no static salt that performs only one hash iteration + */ + public MD5HashTool() { + this(null, 1); + } + + /** + * Creates an MD5 hash tool with the specified static salt that performs only one hash iteration + * @param _salt the salt to attach each time a hash is performed with this tool + */ + public MD5HashTool(String _salt) { + this(_salt, 1); + } + + /** + * Creates an MD5 hash tool with the specified static salt that performs the specified number of iterations each time a hash is performed + * @param _salt the salt to attach each time a hash is performed with this tool + * @param _iterations the number of times to hash values + */ + public MD5HashTool(String _salt, int _iterations) { + super("MD5", _salt, false, _iterations); + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/SHA1HashTool.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/SHA1HashTool.java new file mode 100644 index 0000000..4d4e0ad --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/SHA1HashTool.java @@ -0,0 +1,27 @@ +package com.lanternsoftware.util.hash; + +public class SHA1HashTool extends AbstractHashTool { + /** + * Creates an SHA-1 hash tool with no static salt that performs only one hash iteration + */ + public SHA1HashTool() { + this(null, 1); + } + + /** + * Creates an SHA-1 hash tool with the specified static salt that performs only one hash iteration + * @param _salt the salt to attach each time a hash is performed with this tool + */ + public SHA1HashTool(String _salt) { + this(_salt, 1); + } + + /** + * Creates an SHA-1 hash tool with the specified static salt that performs the specified number of iterations each time a hash is performed + * @param _salt the salt to attach each time a hash is performed with this tool + * @param _iterations the number of times to hash values + */ + public SHA1HashTool(String _salt, int _iterations) { + super("SHA-1", _salt, true, _iterations); + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/SHA512HashTool.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/SHA512HashTool.java new file mode 100644 index 0000000..fcdab4f --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/hash/SHA512HashTool.java @@ -0,0 +1,27 @@ +package com.lanternsoftware.util.hash; + +public class SHA512HashTool extends AbstractHashTool { + /** + * Creates an SHA-512 hash tool with no static salt that performs only one hash iteration + */ + public SHA512HashTool() { + this(null, 1); + } + + /** + * Creates an SHA-512 hash tool with the specified static salt that performs only one hash iteration + * @param _salt the salt to attach each time a hash is performed with this tool + */ + public SHA512HashTool(String _salt) { + this(_salt, 1); + } + + /** + * Creates an SHA-512 hash tool with the specified static salt that performs the specified number of iterations each time a hash is performed + * @param _salt the salt to attach each time a hash is performed with this tool + * @param _iterations the number of times to hash values + */ + public SHA512HashTool(String _salt, int _iterations) { + super("SHA-512", _salt, true, _iterations); + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/ITracer.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/ITracer.java new file mode 100644 index 0000000..1f328d7 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/ITracer.java @@ -0,0 +1,10 @@ +package com.lanternsoftware.util.tracing; + +import java.util.Date; +import java.util.List; + +public interface ITracer { + void config(TracerConfig _config); + TraceDuration createDuration(TraceContext _parent, String _name, Date _start); + void trace(String _name, TraceDuration _duration, TraceTags _tags, List _logs); +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceContext.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceContext.java new file mode 100644 index 0000000..3291236 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceContext.java @@ -0,0 +1,32 @@ +package com.lanternsoftware.util.tracing; + +import java.util.HashMap; +import java.util.Map; + +import com.lanternsoftware.util.NullUtils; + +public class TraceContext extends HashMap { + public String serialize() { + StringBuilder s = null; + for (Map.Entry e : entrySet()) { + if (s == null) + s = new StringBuilder(); + else + s.append("~"); + s.append(e.getKey()); + s.append("."); + s.append(e.getValue()); + } + return (s == null)?"":s.toString(); + } + + public static TraceContext deserialize(String _context) { + TraceContext context = new TraceContext(); + for (String key : NullUtils.cleanSplit(_context, "~")) { + String[] parts = NullUtils.cleanSplit(key, "\\."); + if (parts.length == 2) + context.put(parts[0], parts[1]); + } + return context.isEmpty()?null:context; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceDuration.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceDuration.java new file mode 100644 index 0000000..e6ac21b --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceDuration.java @@ -0,0 +1,39 @@ +package com.lanternsoftware.util.tracing; + +import java.util.Date; + +public class TraceDuration { + private long start; + private long curStart; + private long duration = 0; + + public TraceDuration(Date _start) { + start = curStart = _start.getTime(); + } + + public void start() { + curStart = new Date().getTime(); + if (duration == 0) + start = curStart; + } + + public void stop() { + duration += (new Date().getTime()-curStart); + } + + public Date currentTimeOffset() { + return new Date(start + duration + (new Date().getTime()-curStart)); + } + + public long duration() { + return duration; + } + + public long end() { + return start + duration; + } + + public TraceContext getContext() { + return null; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceFrequencyType.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceFrequencyType.java new file mode 100644 index 0000000..ee441c6 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceFrequencyType.java @@ -0,0 +1,8 @@ +package com.lanternsoftware.util.tracing; + +public enum TraceFrequencyType { + ALL, + PERCENTAGE, + MAX_TRACES_PER_SECOND, + REMOTE_CONTROLLED +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceLog.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceLog.java new file mode 100644 index 0000000..450f554 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceLog.java @@ -0,0 +1,21 @@ +package com.lanternsoftware.util.tracing; + +import java.util.Date; + +public class TraceLog { + private final Date timeStamp; + private final String event; + + public TraceLog(Date _timeStamp, String _event) { + timeStamp = _timeStamp; + event = _event; + } + + public Date getTimeStamp() { + return timeStamp; + } + + public String getEvent() { + return event; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceSpan.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceSpan.java new file mode 100644 index 0000000..c747f30 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceSpan.java @@ -0,0 +1,5 @@ +package com.lanternsoftware.util.tracing; + +public interface TraceSpan { + void setTag(String _name, String _value); +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceTags.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceTags.java new file mode 100644 index 0000000..0787352 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TraceTags.java @@ -0,0 +1,16 @@ +package com.lanternsoftware.util.tracing; + +import java.util.HashMap; + +public class TraceTags extends HashMap { + public static TraceTags tag(String _name, String _value) { + TraceTags tags = new TraceTags(); + tags.put(_name, _value); + return tags; + } + + public TraceTags withTag(String _name, String _value) { + put(_name, _value); + return this; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TracerConfig.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TracerConfig.java new file mode 100644 index 0000000..5ac42ff --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/tracing/TracerConfig.java @@ -0,0 +1,77 @@ +package com.lanternsoftware.util.tracing; + +public class TracerConfig { + private final String appName; + private final String endpoint; + private final boolean suppressLocalLog; + private TraceFrequencyType frequencyType; + private double frequency; + private boolean useThreadContext = false; + + public TracerConfig(String _appName, String _endpoint) { + this(_appName, _endpoint, true); + } + + public TracerConfig(String _appName, String _endpoint, boolean _suppressLocalLog) { + appName = _appName; + endpoint = _endpoint; + suppressLocalLog = _suppressLocalLog; + frequencyType = TraceFrequencyType.ALL; + frequency = 0.0; + } + + public TracerConfig withFrequency(TraceFrequencyType _type, double _frequency) { + frequencyType = _type; + frequency = _frequency; + return this; + } + + public TracerConfig tracePercentage(double _percentage) { + return withFrequency(TraceFrequencyType.PERCENTAGE, _percentage); + } + + public TracerConfig traceMaximumPerSecond(int _max) { + return withFrequency(TraceFrequencyType.MAX_TRACES_PER_SECOND, _max); + } + + public TracerConfig traceRateControlledRemotely() { + return withFrequency(TraceFrequencyType.REMOTE_CONTROLLED, 0.0); + } + + public TracerConfig traceAll() { + return withFrequency(TraceFrequencyType.ALL, 1.0); + } + + public TracerConfig useThreadContext(boolean _useThreadContext) { + setUseThreadContext(_useThreadContext); + return this; + } + + public String getAppName() { + return appName; + } + + public String getEndpoint() { + return endpoint; + } + + public boolean isSuppressLocalLog() { + return suppressLocalLog; + } + + public TraceFrequencyType getFrequencyType() { + return frequencyType; + } + + public double getFrequency() { + return frequency; + } + + public boolean isUseThreadContext() { + return useThreadContext; + } + + public void setUseThreadContext(boolean _useThreadContext) { + useThreadContext = _useThreadContext; + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/xml/XmlNode.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/xml/XmlNode.java new file mode 100644 index 0000000..6dff24b --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/xml/XmlNode.java @@ -0,0 +1,99 @@ +package com.lanternsoftware.util.xml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; + +public class XmlNode +{ + private String content; + private final Map attributes = new HashMap(); + private final Map> children = new HashMap>(); + + public String getContent() + { + return content; + } + + public void setContent(String _content) + { + content = _content; + } + + public Map getAttributes() + { + return attributes; + } + + public Map> getChildren() + { + return children; + } + + public void addChild(String _name, XmlNode _node) + { + CollectionUtils.addToMultiMap(_name, _node, children); + } + + public XmlNode getChild(List _path) + { + if (CollectionUtils.isEmpty(_path)) + return this; + XmlNode node = CollectionUtils.getFirst(children.get(CollectionUtils.getFirst(_path))); + if (node == null) + return null; + return node.getChild(_path.subList(1, _path.size())); + } + + public List getChildren(List _path) + { + if (CollectionUtils.size(_path) == 1) + return CollectionUtils.makeNotNull(children.get(_path.get(0))); + List nodes = new ArrayList(); + for (XmlNode node : CollectionUtils.makeNotNull(children.get(CollectionUtils.getFirst(_path)))) + { + nodes.addAll(node.getChildren(_path.subList(1, _path.size()))); + } + return nodes; + } + + + public XmlNode getChild(List _path, String _attributeName, String _attributeValue) + { + for (XmlNode node : getChildren(_path)) + { + if (NullUtils.isEqual(node.getAttributes().get(_attributeName), _attributeValue)) + return node; + } + return null; + } + + public String getChildContent(List _path, String _attributeName, String _attributeValue) + { + XmlNode node = getChild(_path, _attributeName, _attributeValue); + if (node == null) + return null; + return node.getContent(); + } + + public String getChildAttribute(List _path, String _attributeName) + { + XmlNode child = getChild(_path); + if (child == null) + return null; + return child.getAttributes().get(_attributeName); + } + + + public String getChildContent(List _path) + { + XmlNode node = getChild(_path); + if (node == null) + return null; + return node.getContent(); + } +} diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/xml/XmlParser.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/xml/XmlParser.java new file mode 100644 index 0000000..96751a0 --- /dev/null +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/xml/XmlParser.java @@ -0,0 +1,83 @@ +package com.lanternsoftware.util.xml; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.Stack; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.lanternsoftware.util.NullUtils; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class XmlParser { + protected static final Logger LOG = LoggerFactory.getLogger(XmlParser.class); + + public static XmlNode loadXmlFile(String _filePath) { + FileInputStream is = null; + try { + is = new FileInputStream(_filePath); + return parseXml(is); + } + catch (Exception _e) { + LOG.error("Failed to load xml file", _e); + return null; + } + finally { + IOUtils.closeQuietly(is); + } + } + + public static XmlNode parseXml(InputStream _is) { + XMLStreamReader reader = null; + try { + XmlNode node = null; + StringBuilder content = null; + Stack stack = new Stack(); + reader = XMLInputFactory.newInstance().createXMLStreamReader(_is); + while (reader.hasNext()) { + switch (reader.next()) { + case XMLStreamConstants.START_ELEMENT: { + node = new XmlNode(); + content = new StringBuilder(); + for (int i = 0; i < reader.getAttributeCount(); i++) { + node.getAttributes().put(reader.getAttributeLocalName(i), reader.getAttributeValue(i)); + } + stack.push(node); + break; + } + case XMLStreamConstants.CHARACTERS: { + content.append(NullUtils.makeNotNull(reader.getText())); + break; + } + case XMLStreamConstants.END_ELEMENT: { + node = stack.pop(); + if (stack.empty()) + return node; + stack.peek().addChild(reader.getLocalName(), node); + if (content != null) + node.setContent(content.toString().trim()); + break; + } + } + } + } + catch (Exception _e) { + LOG.error("Failed to parse XML", _e); + } + finally { + try { + reader.close(); + } + catch (XMLStreamException _e) { + LOG.error("Failed to close XML stream", _e); + } + IOUtils.closeQuietly(_is); + } + return null; + } +} diff --git a/util/lantern-util-dao-ephemeral/pom.xml b/util/lantern-util-dao-ephemeral/pom.xml new file mode 100644 index 0000000..73d7db5 --- /dev/null +++ b/util/lantern-util-dao-ephemeral/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + com.lanternsoftware.util + lantern-util-dao-ephemeral + lantern-util-dao-ephemeral + 1.0.0 + jar + + + + ${project.groupId} + lantern-util-common + ${project.version} + + + ${project.groupId} + lantern-util-dao + ${project.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar + + + + + + + diff --git a/util/lantern-util-dao-ephemeral/src/main/java/com/lanternsoftware/util/dao/ephemeral/EphemeralProxy.java b/util/lantern-util-dao-ephemeral/src/main/java/com/lanternsoftware/util/dao/ephemeral/EphemeralProxy.java new file mode 100644 index 0000000..d0b68a0 --- /dev/null +++ b/util/lantern-util-dao-ephemeral/src/main/java/com/lanternsoftware/util/dao/ephemeral/EphemeralProxy.java @@ -0,0 +1,284 @@ +package com.lanternsoftware.util.dao.ephemeral; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +import com.lanternsoftware.util.dao.AbstractDaoProxy; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoQuery; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.DaoSort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.IFilter; +import com.lanternsoftware.util.ITransformer; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; + +public class EphemeralProxy extends AbstractDaoProxy { + private static final Logger LOG = LoggerFactory.getLogger(EphemeralProxy.class); + private final Map> tables = new HashMap<>(); + private final Map> tableClasses = new HashMap<>(); + private final DaoProxyType serializerType; + private long genericSequence = 100; + + public static EphemeralProxy loadFromDisk(String _path) { + return loadFromDisk(_path, DaoProxyType.MONGO); + } + + public static EphemeralProxy loadFromDisk(String _path, DaoProxyType _serializerType) { + EphemeralProxy proxy = new EphemeralProxy(_serializerType); + try { + File file = new File(_path); + if (file.isDirectory()) { + for (File child : file.listFiles()) { + if (child.getName().endsWith(".json")) { + Class clazz = null; + try { + clazz = Class.forName(child.getName().substring(0, child.getName().length()-5)); + } catch (ClassNotFoundException _e) { + continue; + } + List entities = DaoSerializer.parseList(NullUtils.toString(ResourceLoader.loadFile(child))); + proxy.save(clazz, entities); + } + } + } + } catch (Exception _e) { + LOG.error("Failed to load directory: " + _path); + } + return proxy; + } + + public EphemeralProxy() { + this(DaoProxyType.MONGO); + } + + public EphemeralProxy(DaoProxyType _serializerType) { + serializerType = _serializerType; + } + + public void writeToDisk(String _path) { + writeToDisk(_path, null); + } + + public void writeToDisk(String _path, String _fileNameSuffix) { + File file = new File(_path); + file.mkdirs(); + if (!_path.endsWith(File.separator)) + _path += File.separator; + for (Entry> e : tables.entrySet()) { + try { + String json = DaoSerializer.toJson(e.getValue().values()); + String filename = _path + tableClasses.get(e.getKey()).getCanonicalName(); + if (_fileNameSuffix != null) + filename += _fileNameSuffix; + ResourceLoader.writeFile(filename, NullUtils.toByteArray(json)); + } + catch (Throwable t) { + LOG.error("Failed to write collection " + e.getKey() + " to disk", t); + } + } + } + + @Override + public DaoProxyType getType() { + return serializerType; + } + + @Override + public synchronized List queryForEntities(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count) { + Map table = tables.get(_tableName); + if (table == null) + return new ArrayList<>(); + return CollectionUtils.filter(table.values(), new QueryFilter(_query)); + } + + @Override + public synchronized void update(Class _class, DaoQuery _query, DaoEntity _changes) { + for (DaoEntity entity : queryForEntities(DaoSerializer.getTableName(_class, getType()), _query)) { + for (Entry change : _changes.entrySet()) { + entity.put(change.getKey(), change.getValue()); + } + } + } + + @Override + public synchronized T updateOne(Class _class, DaoQuery _query, DaoEntity _changes) { + DaoEntity entity = CollectionUtils.getFirst(queryForEntities(DaoSerializer.getTableName(_class, getType()), _query)); + for (Entry change : _changes.entrySet()) { + entity.put(change.getKey(), change.getValue()); + } + return DaoSerializer.fromDaoEntity(entity, _class); + } + + @Override + public String saveEntity(String _collection, DaoEntity _entity) { + return saveEntity(_collection, CollectionUtils.asArrayList("id"), _entity); + } + + @Override + public synchronized String saveEntity(Class _class, DaoEntity _entity) { + String tableName = DaoSerializer.getTableName(_class, getType()); + tableClasses.put(tableName, _class); + return saveEntity(tableName, DaoSerializer.getFieldsByAnnotation(_class, PrimaryKey.class), _entity); + } + + private String saveEntity(String _tableName, List _primaryKeys, DaoEntity _entity) { + if (_entity == null) + return null; + String pk; + if (!_entity.containsKey("_id")) { + for (String key : CollectionUtils.makeNotNull(_primaryKeys)) { + Object value = _entity.remove(key); + if ((value instanceof String) || (value == null)) { + if (NullUtils.isEmpty((String) value)) { + value = UUID.randomUUID().toString(); + } + } else if (value instanceof Long) { + if (((Long) value) == 0) { + value = getNextSequence(); + } + } else if (value instanceof Integer) { + if (((Integer) value) == 0) { + value = new Long(getNextSequence()).intValue(); + + } + } + _entity.put(key, value); + } + pk = CollectionUtils.commaSeparated(CollectionUtils.transform(CollectionUtils.getAll(_entity, _primaryKeys), new ITransformer() { + @Override + public String transform(Object _o) { + return DaoSerializer.toString(_o); + } + })); + } + else + pk = DaoSerializer.getString(_entity, "_id"); + Map table = tables.get(_tableName); + if (table != null) + table.remove(pk); + else { + table = new HashMap<>(); + tables.put(_tableName, table); + } + table.put(pk, _entity); + return pk; + } + + private long getNextSequence() { + return genericSequence++; + } + + @Override + public synchronized boolean delete(String _tableName, DaoQuery _query) { + IFilter filter = new QueryFilter(_query); + Map table = tables.get(_tableName); + if (table != null) { + Iterator iter = table.values().iterator(); + while (iter.hasNext()) { + DaoEntity entity = iter.next(); + if (filter.isFiltered(entity)) + iter.remove(); + } + } + return true; + } + + @Override + public int count(String _tableName, DaoQuery _query) { + return queryForEntities(_tableName, _query).size(); + } + + private class QueryFilter implements IFilter { + private final DaoQuery query; + + QueryFilter(DaoQuery _query) { + query = _query; + } + + @Override + public boolean isFiltered(DaoEntity _daoEntity) { + if (query == null) + return true; + for (Entry qual : query.entrySet()) { + if (qual.getValue() instanceof DaoQuery) { + DaoQuery child = (DaoQuery) qual.getValue(); + Object comp = child.get("$ne"); + if ((comp != null) && DaoSerializer.compare(_daoEntity, qual.getKey(), comp) == 0) + return false; + comp = child.get("$gt"); + if ((comp != null) && DaoSerializer.compare(_daoEntity, qual.getKey(), comp) <= 0) + return false; + comp = child.get("$lt"); + if ((comp != null) && DaoSerializer.compare(_daoEntity, qual.getKey(), comp) >= 0) + return false; + comp = child.get("$gte"); + if ((comp != null) && DaoSerializer.compare(_daoEntity, qual.getKey(), comp) < 0) + return false; + comp = child.get("$lte"); + if ((comp != null) && DaoSerializer.compare(_daoEntity, qual.getKey(), comp) > 0) + return false; + comp = child.get("$contains"); + if ((comp != null) && !DaoSerializer.getString(_daoEntity, qual.getKey()).contains((String) comp)) + return false; + comp = child.get("$startsWith"); + if ((comp != null) && !DaoSerializer.getString(_daoEntity, qual.getKey()).startsWith((String) comp)) + return false; + comp = child.get("$containsIgnoreCase"); + if ((comp != null) && !DaoSerializer.getString(_daoEntity, qual.getKey()).toLowerCase().contains(((String) comp).toLowerCase())) + return false; + comp = child.get("$equalssIgnoreCase"); + if ((comp != null) && !DaoSerializer.getString(_daoEntity, qual.getKey()).toLowerCase().equals(((String) comp).toLowerCase())) + return false; + comp = child.get("$startsWithIgnoreCase"); + if ((comp != null) && !DaoSerializer.getString(_daoEntity, qual.getKey()).toLowerCase().startsWith(((String) comp).toLowerCase())) + return false; + comp = child.get("$in"); + if ((comp != null) && !in(_daoEntity.get(qual.getKey()), (Collection) comp)) + return false; + comp = child.get("$nin"); + if ((comp != null) && in(_daoEntity.get(qual.getKey()), (Collection) comp)) + return false; + } + else if ((qual.getValue() instanceof String) && NullUtils.isEqual(qual.getValue(), "$null")) { + if (_daoEntity.get(qual.getKey()) != null) + return false; + } + else if ((qual.getValue() instanceof String) && NullUtils.isEqual(qual.getValue(), "$notnull")) { + if (_daoEntity.get(qual.getKey()) == null) + return false; + } + else if (DaoSerializer.compare(_daoEntity, qual.getKey(), qual.getValue()) != 0) + return false; + } + return true; + } + } + + private boolean in(Object field, Collection qual) { + for (Object qualObject : qual) { + if (field instanceof Collection) { + for (Object fieldObject : (Collection) field) { + if (NullUtils.isEqual(fieldObject, qualObject)) + return true; + } + } + else if (NullUtils.isEqual(field, qualObject)) + return true; + } + return false; + } +} diff --git a/util/lantern-util-dao-mongo/pom.xml b/util/lantern-util-dao-mongo/pom.xml new file mode 100644 index 0000000..aa028c6 --- /dev/null +++ b/util/lantern-util-dao-mongo/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + com.lanternsoftware.util + lantern-util-dao-mongo + lantern-util-dao-mongo + 1.0.0 + jar + + + + ${project.groupId} + lantern-util-common + ${project.version} + + + ${project.groupId} + lantern-util-dao + ${project.version} + + + org.mongodb + mongodb-driver + 3.12.5 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar + + + + + + + diff --git a/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/BsonUtils.java b/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/BsonUtils.java new file mode 100644 index 0000000..e03fc28 --- /dev/null +++ b/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/BsonUtils.java @@ -0,0 +1,108 @@ +package com.lanternsoftware.util.dao.mongo; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Collection; + +import org.apache.commons.io.IOUtils; +import org.bson.BsonBinaryReader; +import org.bson.BsonBinaryWriter; +import org.bson.Document; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.DocumentCodec; +import org.bson.codecs.EncoderContext; +import org.bson.io.BasicOutputBuffer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; + +public class BsonUtils { + private static final Logger LOG = LoggerFactory.getLogger(BsonUtils.class); + + public static Document parse(String _json) + { + try + { + return Document.parse(_json); + } + catch (Exception _e) + { + LOG.error("Failed to parse json", _e); + return null; + } + } + + public static String toJson(Document _d) + { + try + { + if (_d != null) + return _d.toJson(); + } + catch (Exception _e) + { + LOG.error("Failed to convert bson document to json", _e); + } + return null; + } + + public static String toJson(Collection _collDocs) + { + if (CollectionUtils.isEmpty(_collDocs)) + return ""; + StringBuilder b = null; + for (Document d : _collDocs) + { + if (b == null) + b = new StringBuilder("["); + else + b.append(","); + b.append(toJson(d)); + } + b.append("]"); + return b.toString(); + } + + public static byte[] toByteArray(Document _d) + { + BsonBinaryWriter writer = null; + try + { + BasicOutputBuffer buffer = new BasicOutputBuffer(); + writer = new BsonBinaryWriter(buffer); + new DocumentCodec().encode(writer, _d, EncoderContext.builder().build()); + return buffer.toByteArray(); + } + catch (Throwable _t) + { + LOG.error("Failed to convert bson document to a byte array", _t); + return null; + } + finally + { + IOUtils.closeQuietly(writer); + } + } + + public static Document fromByteArray(byte[] _data) + { + if (_data == null) + return null; + BsonBinaryReader reader = null; + try + { + reader = new BsonBinaryReader(ByteBuffer.wrap(_data).order(ByteOrder.LITTLE_ENDIAN)); + return new DocumentCodec().decode(reader, DecoderContext.builder().build()); + } + catch (Throwable _t) + { + LOG.error("Failed to convert byte array into bson document", _t); + return null; + } + finally + { + IOUtils.closeQuietly(reader); + } + } +} diff --git a/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/MongoConfig.java b/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/MongoConfig.java new file mode 100644 index 0000000..e84a095 --- /dev/null +++ b/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/MongoConfig.java @@ -0,0 +1,149 @@ +package com.lanternsoftware.util.dao.mongo; + +import java.util.Collections; +import java.util.List; + +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import org.apache.commons.codec.binary.Base64; + +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.cryptography.AESTool; + +@DBSerializable +public class MongoConfig { + private static final AESTool aes = new AESTool(4501188070455102914L,4127218394209583290L,8065326024699768144L,6272281743831953728L); + private List hosts; + private String username; + private String password; + private String clientKeystorePath; + private String clientKeystorePassword; + private String caKeystorePath; + private String caKeystorePassword; + private String databaseName; + private String authenticationDatabase; + + public MongoConfig() { + } + + public MongoConfig(String _host, String _username, String _password, String _databaseName) { + this(Collections.singletonList(_host), _username, _password, null, null, null, null, _databaseName); + } + + public MongoConfig(List _hosts, String _username, String _password, String _clientKeystorePath, String _clientKeystorePassword, String _caKeystorePath, String _caKeystorePassword, String _databaseName) { + hosts = _hosts; + username = _username; + password = _password; + clientKeystorePath = _clientKeystorePath; + clientKeystorePassword = _clientKeystorePassword; + caKeystorePath = _caKeystorePath; + caKeystorePassword = _caKeystorePassword; + databaseName = _databaseName; + } + + public static AESTool getAes() { + return aes; + } + + public List getHosts() { + return hosts; + } + + public void setHosts(List _hosts) { + hosts = _hosts; + } + + public String getUsername() { + return username; + } + + public void setUsername(String _username) { + username = _username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String _password) { + password = _password; + } + + public String getClientKeystorePath() { + return clientKeystorePath; + } + + public void setClientKeystorePath(String _clientKeystorePath) { + clientKeystorePath = _clientKeystorePath; + } + + public String getClientKeystorePassword() { + return clientKeystorePassword; + } + + public void setClientKeystorePassword(String _clientKeystorePassword) { + clientKeystorePassword = _clientKeystorePassword; + } + + public String getCaKeystorePath() { + return caKeystorePath; + } + + public void setCaKeystorePath(String _caKeystorePath) { + caKeystorePath = _caKeystorePath; + } + + public String getCaKeystorePassword() { + return caKeystorePassword; + } + + public void setCaKeystorePassword(String _caKeystorePassword) { + caKeystorePassword = _caKeystorePassword; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String _databaseName) { + databaseName = _databaseName; + } + + public String getAuthenticationDatabase() { + return authenticationDatabase; + } + + public void setAuthenticationDatabase(String _authenticationDatabase) { + authenticationDatabase = _authenticationDatabase; + } + + public void saveToDisk(String _filePath) { + ResourceLoader.writeFile(_filePath, encrypt()); + } + + public byte[] encrypt() { + return aes.encrypt(BsonUtils.toByteArray(DaoSerializer.toDaoEntity(this).toDocument())); + } + + public String encryptToString() { + return Base64.encodeBase64String(encrypt()); + } + + public static MongoConfig fromDisk(String _path) { + return decrypt(ResourceLoader.loadFile(_path)); + } + + public static MongoConfig decrypt(byte[] _configData) { + if ((_configData == null) || (_configData.length == 0)) + return null; + return DaoSerializer.fromDaoEntity(new DaoEntity(BsonUtils.fromByteArray(aes.decrypt(_configData))), MongoConfig.class); + } + + public static MongoConfig decryptFromString(String _config) { + if (NullUtils.isEmpty(_config)) + return null; + return decrypt(Base64.decodeBase64(_config)); + } +} diff --git a/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/MongoProxy.java b/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/MongoProxy.java new file mode 100644 index 0000000..7b736e3 --- /dev/null +++ b/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/MongoProxy.java @@ -0,0 +1,464 @@ +package com.lanternsoftware.util.dao.mongo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import com.lanternsoftware.util.dao.AbstractDaoProxy; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoQuery; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.DaoSort; +import com.lanternsoftware.util.dao.DaoSortField; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; +import com.mongodb.bulk.BulkWriteResult; +import com.mongodb.client.model.DeleteOneModel; +import com.mongodb.client.model.ReplaceOptions; +import org.bson.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.ITransformer; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.cryptography.RSAUtils; +import com.lanternsoftware.util.hash.MD5HashTool; +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.IndexOptions; +import com.mongodb.client.model.ReplaceOneModel; +import com.mongodb.client.model.UpdateOptions; +import com.mongodb.client.model.WriteModel; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; + +public class MongoProxy extends AbstractDaoProxy { + private static final Logger LOG = LoggerFactory.getLogger(MongoProxy.class); + private final MongoClient client; + private final String dbName; + private final Map> textIndexes = new HashMap<>(); + private final MD5HashTool hash = new MD5HashTool(); + + public MongoProxy(MongoConfig _config) { + this(_config.getHosts(), _config.getUsername(), _config.getPassword(), _config.getClientKeystorePath(), _config.getClientKeystorePassword(), _config.getCaKeystorePath(), _config.getCaKeystorePassword(), _config.getDatabaseName(), _config.getAuthenticationDatabase()); + } + + public MongoProxy(List _hosts, String _userName, String _password, String _clientKeystorePath, String _clientKeystorePassword, String _caKeystorePath, String _caKeystorePassword, String _dbName) { + this(_hosts, _userName, _password, _clientKeystorePath, _clientKeystorePassword, _caKeystorePath, _caKeystorePassword, _dbName, null); + } + + public MongoProxy(List _hosts, String _userName, String _password, String _clientKeystorePath, String _clientKeystorePassword, String _caKeystorePath, String _caKeystorePassword, String _dbName, String _authDbName) { + List listAddresses = new LinkedList<>(); + for (String addr : _hosts) { + int portIdx = addr.indexOf(":"); + if (portIdx > 0) + listAddresses.add(new ServerAddress(addr.substring(0, portIdx), DaoSerializer.toInteger(addr.substring(portIdx + 1)))); + else + listAddresses.add(new ServerAddress(addr, 27017)); + } + MongoClientOptions options; + if (NullUtils.isEmpty(_clientKeystorePath) && NullUtils.isEmpty(_caKeystorePath)) { + options = MongoClientOptions.builder().sslEnabled(false).build(); + } + else { + try { + KeyManager[] keyManagers = null; + if (NullUtils.isNotEmpty(_clientKeystorePath)) { + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(RSAUtils.loadKeystore(_clientKeystorePath, _clientKeystorePassword), _clientKeystorePassword.toCharArray()); + keyManagers = kmf.getKeyManagers(); + } + TrustManager[] trustManagers = null; + if (NullUtils.isNotEmpty(_caKeystorePath)) { + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + if (NullUtils.isEqual(_caKeystorePath, "aws")) + tmf.init(RSAUtils.loadKeystore(getClass().getResourceAsStream("/ca.jks"), _caKeystorePassword)); + else + tmf.init(RSAUtils.loadKeystore(_caKeystorePath, _caKeystorePassword)); + trustManagers = tmf.getTrustManagers(); + } + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(keyManagers, trustManagers, null); + options = MongoClientOptions.builder().sslEnabled(true).sslContext(sslContext).build(); + } + catch (Exception _e) { + LOG.error("Failed to load keystores for MongoClient", _e); + options = MongoClientOptions.builder().sslEnabled(false).build(); + } + } + client = new MongoClient(listAddresses, MongoCredential.createCredential(_userName, NullUtils.isNotEmpty(_authDbName) ? _authDbName : "admin", _password.toCharArray()), options); + dbName = _dbName; + } + + @Override + public DaoProxyType getType() { + return DaoProxyType.MONGO; + } + + @Override + public void shutdown() { + client.close(); + } + + @Override + public List query(final Class _class, DaoQuery _query, Collection _fields, DaoSort _sort, int _first, int _count) { + return toObjects(queryForEntities(DaoSerializer.getTableName(_class, getType()), CollectionUtils.getFirst(DaoSerializer.getFieldsByAnnotation(_class, PrimaryKey.class)), _query, _fields, _sort, _first, _count), _class); + } + + @Override + public List queryForField(Class _class, DaoQuery _query, final String _field) { + String pk = CollectionUtils.getFirst(DaoSerializer.getFieldsByAnnotation(_class, PrimaryKey.class)); + return CollectionUtils.transform(queryForEntities(DaoSerializer.getTableName(_class, getType()), pk, _query, Collections.singletonList(_field), null, 0, -1), new ITransformer() { + @Override + public String transform(DaoEntity _daoEntity) { + return DaoSerializer.getString(_daoEntity, _field); + } + }); + } + + @Override + public List queryForEntities(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count) { + return queryForEntities(_tableName, null, _query, _fields, _sort, _offset, _count); + } + + public List queryForEntities(String _tableName, final String _primaryKey, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count) { + final String pk = NullUtils.isEmpty(_primaryKey) ? "_id" : _primaryKey; + FindIterable iter; + if (_query != null) { + DaoQuery query = new DaoQuery(); + for (Entry entry : _query.entrySet()) { + if (NullUtils.isEqual(entry.getKey(), pk)) + query.put("_id", entry.getValue()); + else { + boolean keyChanged = false; + if (entry.getValue() instanceof DaoQuery) { + DaoQuery child = (DaoQuery) entry.getValue(); + Map newChildren = new HashMap<>(); + Iterator> entryIter = child.entrySet().iterator(); + while (entryIter.hasNext()) { + Entry childEntry = entryIter.next(); + if (childEntry.getKey().startsWith("$contains")) { + boolean caseSensitive = !NullUtils.isEqual(childEntry.getKey(), "$containsIgnoreCase"); + if (isTextIndex(_tableName, entry.getKey())) { + DaoQuery value = new DaoQuery(); + value.put("$search", childEntry.getValue()); + value.put("$caseSensitive", caseSensitive); + value.put("$diacriticSensitive", caseSensitive); + query.put("$text", value); + entryIter.remove(); + keyChanged = true; + break; + } + else { + if (childEntry.getValue() instanceof String) { + newChildren.put("$regex", childEntry.getValue()); + if (!caseSensitive) + newChildren.put("$options", "i"); + } + entryIter.remove(); + } + } + } + child.putAll(newChildren); + } + if (!keyChanged) + query.put(entry.getKey(), entry.getValue()); + } + } + query = prepareQuery(query); + iter = db().getCollection(_tableName).find(query); + } + else + iter = db().getCollection(_tableName).find(); + if (_fields != null) { + List fields = new ArrayList<>(); + for (String field : _fields) { + if (NullUtils.isEqual(field, pk)) + fields.add("_id"); + else + fields.add(field); + } + _fields = fields; + } + Document projection = toProjection(_fields); + if (projection != null) + iter.projection(projection); + Document sort = toSort(_sort); + if (sort != null) + iter.sort(sort); + if (_offset > 0) + iter.skip(_offset); + if (_count > 0) + iter.limit(_count); + return CollectionUtils.transform(iter, new ITransformer() { + @Override + public DaoEntity transform(Document _document) { + return new DaoEntity(_document); + } + }); + } + + @Override + public void update(Class _class, DaoQuery _query, DaoEntity _changes) { + DaoQuery query = prepareQuery(_query); + coll(_class).updateMany(query, _changes.toDocument()); + } + + @Override + public T updateOne(Class _class, DaoQuery _query, DaoEntity _changes) { + return DaoSerializer.fromDaoEntity(new DaoEntity(coll(_class).findOneAndUpdate(_query, _changes.toDocument())), _class); + } + + @Override + public String saveEntity(Class _class, DaoEntity _entity) { + if (_entity == null) + return null; + String id = DaoSerializer.getString(_entity, "_id"); + if (NullUtils.isEmpty(id)) { + String primaryKeyField = CollectionUtils.getFirst(DaoSerializer.getFieldsByAnnotation(_class, PrimaryKey.class)); + if (NullUtils.isEmpty(primaryKeyField)) { + primaryKeyField = "_id"; + } + id = (String) _entity.remove(primaryKeyField); + if (NullUtils.isEmpty(id)) + id = UUID.randomUUID().toString(); + _entity.put("_id", id); + } + Document doc = _entity.toDocument(); + UpdateResult result = coll(_class).replaceOne(new Document("_id", id), doc, new ReplaceOptions().upsert(true)); + if (result.wasAcknowledged()) + return id; + return null; + } + + public String saveEntity(String _tableName, DaoEntity _entity) { + String id = DaoSerializer.getString(_entity, "_id"); + if (NullUtils.isEmpty(id)) { + id = UUID.randomUUID().toString(); + _entity.put("_id", id); + } + Document doc = _entity.toDocument(); + UpdateResult result = db().getCollection(_tableName).replaceOne(new Document("_id", id), doc, new ReplaceOptions().upsert(true)); + if (result.wasAcknowledged()) + return id; + return null; + } + + @Override + public Map save(Collection _objects) { + if (CollectionUtils.isEmpty(_objects)) + return new HashMap<>(); + Iterator iter = _objects.iterator(); + while (iter.hasNext()) { + T t = iter.next(); + if (t == null) + iter.remove(); + } + T t = CollectionUtils.getFirst(_objects); + if (t == null) + return new HashMap<>(); + Map, List> classes = CollectionUtils.transformToMultiMap(_objects, new ITransformer>() { + @Override + public Class transform(T _t) { + return _t.getClass(); + } + }); + final Map ids = new HashMap<>(); + for (Entry, List> e : classes.entrySet()) { + String primaryKeyField = CollectionUtils.getFirst(DaoSerializer.getFieldsByAnnotation(e.getKey(), PrimaryKey.class)); + if (NullUtils.isEmpty(primaryKeyField)) { + primaryKeyField = "_id"; + } + final String pk = primaryKeyField; + for (Collection entities : CollectionUtils.split(e.getValue(), 5000)) { + List> updates = CollectionUtils.transform(entities, (_t) -> { + DaoEntity entity = DaoSerializer.toDaoEntity(_t, getType()); + if (entity == null) + return null; + String id = DaoSerializer.getString(entity, "_id"); + if (NullUtils.isEmpty(id)) { + id = (String) entity.remove(pk); + if (NullUtils.isEmpty(id)) + id = UUID.randomUUID().toString(); + entity.put("_id", id); + } + ids.put(id, _t); + return new ReplaceOneModel<>(new Document("_id", id), entity.toDocument(), new ReplaceOptions().upsert(true)); + }, true); + if (!updates.isEmpty()) + coll(e.getKey()).bulkWrite(updates); + } + } + return ids; + } + + @Override + public Map save(Class _class, Collection _entities) { + String primaryKeyField = CollectionUtils.getFirst(DaoSerializer.getFieldsByAnnotation(_class, PrimaryKey.class)); + if (NullUtils.isEmpty(primaryKeyField)) { + primaryKeyField = "_id"; + } + final Map ids = new HashMap<>(); + final String pk = primaryKeyField; + List entities = (_entities instanceof List) ? (List) _entities : new ArrayList<>(_entities); + for (Collection curEntities : CollectionUtils.split(entities, 5000)) { + List> updates = CollectionUtils.transform(curEntities, (_t) -> { + String id = (String) _t.remove(pk); + if (NullUtils.isEmpty(id)) + id = UUID.randomUUID().toString(); + _t.put("_id", id); + ids.put(id, _t); + return new ReplaceOneModel<>(new Document("_id", id), _t.toDocument(), new ReplaceOptions().upsert(true)); + }, true); + if (!updates.isEmpty()) + coll(_class).bulkWrite(updates); + } + return ids; + } + + public T queryOneAndDelete(final Class _class, DaoQuery _query) { + Document doc = coll(_class).findOneAndDelete(_query); + if (doc == null) + return null; + return toObject(new DaoEntity(doc), _class); + } + + public int deleteById(Class _class, List _ids) { + BulkWriteResult result = coll(_class).bulkWrite(CollectionUtils.transform(_ids, _t -> new DeleteOneModel<>(new Document("_id", _t)))); + if (result.wasAcknowledged()) + return result.getDeletedCount(); + return 0; + } + + @Override + public boolean delete(Class _class, DaoQuery _query) { + if (_query != null) { + String primaryKey = CollectionUtils.getFirst(DaoSerializer.getFieldsByAnnotation(_class, PrimaryKey.class)); + DaoQuery query = new DaoQuery(); + for (Entry entry : _query.entrySet()) { + if (NullUtils.isEqual(entry.getKey(), primaryKey)) + query.put("_id", entry.getValue()); + else + query.put(entry.getKey(), entry.getValue()); + } + return delete(DaoSerializer.getTableName(_class, getType()), query); + } + return false; + } + + @Override + public boolean delete(String _tableName, DaoQuery _query) { + DeleteResult result = db().getCollection(_tableName).deleteMany(prepareQuery(_query)); + return result.wasAcknowledged(); + } + + @Override + public int count(String _tableName, DaoQuery _query) { + return (int) db().getCollection(_tableName).count(prepareQuery(_query)); + } + + public void ensureIndex(Class _class, DaoSort _indexOrder) { + Document index = new Document(); + for (DaoSortField field : _indexOrder.getFields()) { + index.put(field.getField(), field.isAscending() ? 1 : -1); + } + String indexName = CollectionUtils.transformAndDelimit(_indexOrder.getFields(), new ITransformer() { + @Override + public String transform(DaoSortField _daoSortField) { + return _daoSortField.getField(); + } + }, "_"); + LOG.debug("Ensuring index: " + indexName); + String tableName = NullUtils.makeNotNull(DaoSerializer.getTableName(_class, getType())); + if ((tableName.length() + indexName.length()) > 60) { + indexName = hash.hash64(indexName); + LOG.debug("Shortening index name to : " + indexName); + } + IndexOptions options = new IndexOptions(); + options.name(indexName); + options.background(true); + if (!index.isEmpty()) + db().getCollection(tableName).createIndex(index, options); + } + + private MongoCollection coll(Class _class) { + return db().getCollection(DaoSerializer.getTableName(_class, getType())); + } + + private MongoDatabase db() { + return client.getDatabase(dbName); + } + + private Document toProjection(Collection _listFields) { + if (CollectionUtils.isEmpty(_listFields)) + return null; + Document proj = new Document(); + for (String field : CollectionUtils.makeNotNull(_listFields)) { + proj.put(field, 1); + } + return proj; + } + + private Document toSort(DaoSort _sort) { + if ((_sort == null) || CollectionUtils.isEmpty(_sort.getFields())) + return null; + Document sort = new Document(); + for (DaoSortField field : CollectionUtils.makeNotNull(_sort.getFields())) { + sort.put(field.getField(), field.isAscending() ? 1 : -1); + } + return sort; + } + + private boolean isTextIndex(String _collection, String _field) { + Set fields = textIndexes.get(_collection); + if (fields == null) { + fields = new HashSet<>(); + for (Document index : db().getCollection(_collection).listIndexes()) { + for (Entry field : index.entrySet()) { + if (field.getValue() instanceof String && field.getValue().equals("text")) + fields.add(field.getKey()); + } + } + textIndexes.put(_collection, fields); + } + return fields.contains(_field); + } + + @Override + protected DaoQuery prepareQuery(DaoQuery _query) { + DaoQuery query = super.prepareQuery(_query); + prepareDates(query); + return query; + } + + private void prepareDates(DaoQuery _query) { + for (Entry e : _query.entrySet()) { + if (e.getValue() instanceof Date) + e.setValue(((Date) e.getValue()).getTime()); + if (e.getValue() instanceof DaoQuery) + prepareDates((DaoQuery) e.getValue()); + } + } +} \ No newline at end of file diff --git a/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/dao/MongoConfigSerializer.java b/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/dao/MongoConfigSerializer.java new file mode 100644 index 0000000..b615123 --- /dev/null +++ b/util/lantern-util-dao-mongo/src/main/java/com/lanternsoftware/util/dao/mongo/dao/MongoConfigSerializer.java @@ -0,0 +1,51 @@ +package com.lanternsoftware.util.dao.mongo.dao; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; + +import com.lanternsoftware.util.dao.mongo.MongoConfig; + + +public class MongoConfigSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return MongoConfig.class; + } + + @Override + public DaoEntity toDaoEntity(MongoConfig _o) + { + DaoEntity d = new DaoEntity(); + d.put("hosts", CollectionUtils.commaSeparated(_o.getHosts())); + d.put("username", _o.getUsername()); + d.put("password", _o.getPassword()); + d.put("client_keystore_path", _o.getClientKeystorePath()); + d.put("client_keystore_password", _o.getClientKeystorePassword()); + d.put("ca_keystore_path", _o.getCaKeystorePath()); + d.put("ca_keystore_password", _o.getCaKeystorePassword()); + d.put("database_name", _o.getDatabaseName()); + d.put("authentication_database", _o.getAuthenticationDatabase()); + return d; + } + + @Override + public MongoConfig fromDaoEntity(DaoEntity _d) + { + MongoConfig o = new MongoConfig(); + o.setHosts(CollectionUtils.asArrayList(NullUtils.cleanSplit(DaoSerializer.getString(_d, "hosts"), ","))); + o.setUsername(DaoSerializer.getString(_d, "username")); + o.setPassword(DaoSerializer.getString(_d, "password")); + o.setClientKeystorePath(DaoSerializer.getString(_d, "client_keystore_path")); + o.setClientKeystorePassword(DaoSerializer.getString(_d, "client_keystore_password")); + o.setCaKeystorePath(DaoSerializer.getString(_d, "ca_keystore_path")); + o.setCaKeystorePassword(DaoSerializer.getString(_d, "ca_keystore_password")); + o.setDatabaseName(DaoSerializer.getString(_d, "database_name")); + o.setAuthenticationDatabase(DaoSerializer.getString(_d, "authentication_database")); + return o; + } +} \ No newline at end of file diff --git a/util/lantern-util-dao-mongo/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer b/util/lantern-util-dao-mongo/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer new file mode 100644 index 0000000..7d2baa9 --- /dev/null +++ b/util/lantern-util-dao-mongo/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer @@ -0,0 +1 @@ +com.lanternsoftware.util.dao.mongo.dao.MongoConfigSerializer diff --git a/util/lantern-util-dao-mongo/src/main/resources/ca.jks b/util/lantern-util-dao-mongo/src/main/resources/ca.jks new file mode 100644 index 0000000000000000000000000000000000000000..bbd7a98ba32c802aec1b34479cfbe27b88b3d3b5 GIT binary patch literal 19752 zcmdU%bx_^c_P23&4el;ExH|-QcXxLW?m>gQyM_=vIKef+5+DS3cYA5t{z7}-wrT#j zlRIb59M(R-?D?)e&wQ5bb+>!B3j_oN^6*Ca`bn#At?yxLL+D`SNa*ZH=W6WeM8`mA zs82}w=nDs1Tc?LLDj*#ZdZOKULmOCiS%Ra?tXSA$>5E&WI_2@A9_{;0(%Bt? zLNnJglUg6$XO4puFqPv_RPke3!Re>7ay@sE`8k&Ah*HvL0J~S+&Jf_o3OPFoz**=4 z3X{jcTmg6em5Do{mJN6o-j$GHfhf-P4UC70nYU=8K-A9oBbcKt_emUwL9;}5QD+Uw zm=*;ac?wUH7~#&J&?HDquJ9;DRW-y;Fob~bgDmhNlDx*ED+U(6hnP1Zh73v@9w1TS zK32pMkFm~uF3Svt%f)fHd3S5mPwjXFw#m>fqzM8F3NwA%J0kp}o#R+(jqQgkh&vXX<}R+mQYF#uh;E%hx1e zPXHLcXfhys<-BL;eNwx$r{EpY>48w<0gpZLaJC;j0Sp9f?`!C~09?Wu?`jcUm8`+t zG)P84TXAiTtBx=fpzJX!0+A%jG&zcY8+CAi-1biBY+k5kOvED0zM)D`i>TqqE!Ob8 z30TFa1hl3SxN7d5w|pPlLs!cMggC}~L&S646)1e=N#xyzLiRLp55Z5cI#Cq>F>W!{e zLWYTB9bbX}q)`@>7-5W*fd|^GCQrQF_2PAHwoy?2MUznAv^UCv@W_#?xv)}oA$xvc zG`Eh5%P|kTjJK7rot(sc=5nQg5x#qol>M5i#fl@@UqXcO?;$b@`PUGU{L_Q}i6O!Q z_ya^9%%2M)jK5&-pCB?skcA=|jkd)dC`@Oo&#`CwoHpf7SU~w`IDhR`La>_EVhHOO3?|0LD^=bgFaZ;IUjWAVBVdpkK{q`yqkyIg8`V2V#=^P zdJyea^?37er4|}<4do#5w_Gwf=Y78EYxcp(RA=p0k-3Rm(!^AIXNZXQ9;J`gm&l>S z;$1{brp|f2V|~&!C5tFj2miq2$y=(coK7DHA(GXgw-|#Sx*2i(E>a;0vve5{Ri^;& z=2syrjm%~cwDFyXKMIkX$^iBRtW`AU7d%FelNBtu`fgQ{htx2fv>93FbSmtmOQpx-R|%ufj~a04sKMmHFpb zyrzr#r+z_e1{OpFiB+Rp3->69EPlDk8Ci$#+2(4Vkq%vV#=5q)OI;3sKxL=4Mp)!2 zNArdmlsr4+QV${wQ#JuhT2!wYqn}=G%L`)eJ>l=*IaY@dWn>J z#cOV(7#z1cNG8gf5~0hUTU*mZ->H`lP>z~-aMx zWGV?oJq|g-N8ytwMmsVFTD%3?;`==DVLZ^3@}}UtB*7JBt9(12dGF@5L-=v$>ettZ zRbn*GLK$K);=TTDdYjGD8zzfoPYKeC@GQb0o#j0CORE_8m=TI81cj9<%E}Y;Gh6$w zbhD$HLD_P#`t#SynRzF~;1b547GUe=uQr4I2<#1oY2+KU@02rYYHf!q7C$ge z*&o3Ode}-Va&Y^Bl2c#Zd4|d5(Iv;*$x?;+h`lAtWZ0e1Sd28E6Bw{_BxFBrPLC&z z*W=%ps)!WvxGPGN^a^c)x&PQ-Ewa8E;M6}Xw~Dm=Wh*tUcxr`l3Gf6^hzx>W3jL!v zfo)y9BVEUs;_SnNp1r#%GI+L8@k2N>Mc~YpV=(U&&N_P!} zo~0(F0t!wa_ItYgo%pH@r&Hv?-clIIIQKN=_PqvN43439KniNA_4g4kUfwszX3Thz zsWy(psJbm+9jqytNt4ik@l4rRO<(tu{(Go1PIb*Im62kgITb!%S;r01GgrU~BqiC} zJK)!}Usj*BO?vSbM92|vD7BM$-u^vK{$7}zEXw~iPM-bm;DnL+4>)-+e?pvmmpV!H z14^qHt1mgl~`O#$k7USpu9X6L7HYg|$?zMJknW@38n7Ar=`kD1NA2n##1 zDjD$`&)kgl-_OD4TH$ejS+jzor^gLPD}MPpr|lRl*fYNkGmb|>LuFh80<)vEf@5`g z>m)j|)<3vnWq=W#nQM9_Hd_P2a1H}HQ}=WvOAm7ol(H#u?al2)DD&(DelnE#kK!au zE+Y~KFQ{mWl|F?+T>pmTX8PcVaKhZ7%z3B7V$c1~F}T*zH$Xo!(RCga-7Mtf=~|a? zwoEVqRMsW*#cLM686K>aPo_lueS+jLh@h7|uCn*U7+^r5p$ObTDo`I~1QQne+(hb) zoeE^+2#lJn?oHxz3N<0>y)Fcx64HydL11ds*vH2rsrXA6v@7GH#L{y|C56zfDMZ@8 zPz1l1IUY*A+gfDqv^sn%NFiPjy@)DKGgYgMw(SQYmw1Vj&)VIqL(am`v?+IP1>;fg zoC+E3bN6}PJ0&`f#9)f`LaF zS11v~abi%p}h{Toa`A5tav587n? zH*F&NzXKB{hCjgM!36ll{dctq34rKVZG!zsoBY8M-=R!;Uf*T}5$~BDhc2aV^H{=s zUgOE>1}^gQ&G(id3kyAmk)w)jqa->*%`4uC0r0OVt?OD&dQV;`zA+4fjkhD~rWl3_o`IC^Jwt)OicezYbWyJ}(IQS@IMB@S zbm{NOOcR|!fTH{9H`|3M&zcNN5HCx3iG3pa_nFShKoHNe6kaT9FpbY^D$rgbms+fK@3=C-0*+TPmsz)>d|522fe73|OL>$_xs-Wo!#?$K! zF~jZuA(Y_1BzISWFB#JFhSY=$ZIpZQ7PVz|K)eE!68?h|&0C1p45u%(VF*$S+3WY& zxU`-xf)<$5+ifnmwKV+LMv$6{sgkbcVw<$`CiLIi?W|{kuFTE~p;{26CJ8x`H(Y>p zNvPQ!r|vdbO*Xt`_+ZD4gZZ&K2;n0kQ25?~=Ri-SnRMz@7BO<({BHe~k;WG~r5@49 zd@SXY&OCT*_-DJG1;$;&;gJFsSKg5fgkcDT1J!_cvf1K}zR{_#_iau^XTODgYYH@aCfybFci~Q_T58X#pcp*&U4@z&vU&Tl@QnHI5L% zSI;=CzoLZh5lVhn*Z%~PGtF-xc?fVoZy!Li`Wr~3AA0$2b{j0}t%jEvtv z@~a8(i}~+@gy`$ReMk^Lc?6P2Zukz2Oco_RsbQAhia3)>WN$<{svywX_Z;1E0N3uK zB6LZF4~_$J{6yfzD4y?zhhj?C3%y4V6BBKvkc%d2Am1;Oh+$QZv;3}4;2 z*&V-sEmMrG=YnRXsf%R-KD>l|BQRn?B`!+J#SdkjgiD& zZmM{f%g#`AUd*FbpplxO-J*2L*fOQW*d~}n5`78CtPgbzAIK_3OV3Cb^tS9HGj+Oo$6OW#X^Qp+!Xe9~5IaHk*^xUa!b?#uVGsLqQr~eMF{-lba=Qf zv0<-P!}}sxk?ci;U6@^tHSr;z)wCbQ$lOyle$T+9bEnwVp6e=6=2ZRNu78D*hr|6f z=}KKhU~CAI3R)T%!ztA`3uISql6Q@sjA!#Kt>!6A*;o_$i`dULh=tI}gTsA@&q&Pm zx373>#QB6HaP(VbMP{UVn6lvs9IM~kZIx>fa9P|Cfm~S$LA|NxQWFKw{s5upnoh2H zZfDCSfFvf)Wl1AZc7FGYyQX-Zc5y^6+*2FZn5SXnvvlmb;>V7@H1b=Ib3w zijhB6ZoVT`a%%JO8%6$HxeMCivZ>Bl z`9K}5-F3}bt~UR4Xf{1Yjptwo^Gk60tHJY6)9`$+zEJ79<1iwj&j?rht-IV?O}q%y26dyYt{wTUrJ_hW@ZJK+sqwZDsFqaIQ9wmmUB!{slccOqv%?V%3iNbW zAXczccV2Jd*2}@WUbPf`K+bgmJRypLq~Eb3r#@X34N+j68I5Cc)mB=nUXd(oI^2K? zWaQ$N9DOm-7{lX?vF*bp&6!jSHATNM@gi%((c#=+5a)Vt+2r6^&5uF^h2MHI`CXHE z#X#d(GmR_r0*$jKmu;G}6WsFd||2dI*eC*09lX``R zlX>5hI|y0JkRuQmQiX1Nk|UFeO2tsGJ&WTp9HgE2J`13EhW$ zQjI;sO*xQR2Z%kv;#~JS<1ytvlr7B^RW@2O3o@Po6r;{)VA1`NQea;0W*?+ollXZ= z!F5B+(-l;tB&pq2DeJr)-H|~-?+>S9>;`d1M7U5PNf2^=QJx-8U@!qq*dd|g)svxI4flm?c3 z7aAxphZjWGTH!z7?k4n6J-|t-mVENe+*BNl%xdwV*dNnI;*+l`WR86+$84Jwmr@rT zs09&`w(5dlSUt|$G2GP`Qbge;MRra2Ca>O{?j)(21Z{slgM`bwjuwjaf(v36cDY(l zw~=2Ip;j(Sz{hmBGD^JhrscyPL*Fd9yO+2Vw2cmp3|aL|`D`9b7S_v%ofwEgW7y;x z^}&>CkZAD}p||)H$eFN_?NOkXudy}QtKRyPhKNmJiFMQCD9fg9R#LWc=PSMZ zIfvq@Xpz=-4JfTvLLE^mba;3OtxsC9{LB}FNNxsYJ2e)e(1VxeUph3y2z#8~h`cvI zFHM>I_ze9|>aH4W~d&o~Zis-?7o9~cFLVDOSpF5$q z5@mQBc0~`gNGyQ0V%nOU#v844XlYOyWowC#_`ZljpNw&=uyqN`oLtW|>AHw2ij^{f z2A!!T@7DA*f$sK99pY8)oI5~SHQZFS5@V+ufdFA+`RI_ybDJ6XLsMl>3HZtM8iK#tObEXp7dyW>& zFcCGenAwYFE>(=|q|+km`-wMc&P1t+K65K-7qO7U`j;9> zs1z;mp~vDieJM;Fy>A*2(r;!QqG9bgblAZ{O(=#Lpjx@}!l*A^6LFtFUKCb!`qfxJ ziA)e*_ZzaTt4o5-78{uZ!1AIxWvnT^74yO{`yA4V8u%+j{#5?8v32;q@^5tiHd72koA{ueX&h~6xnCakjzW&wP(wlB{!*+7s

&70 ziLt~Y@>s5*aqFvq>Wr2@0Cy9NRX^t%dUD*}X0MyE1YZQkQS@@velPMxU!Uu;$7KcH zu;m`{@FLF}Vf1>D#FpJUD&)(E4tfaoBAb9}P}nBItdIdxVkQwof6g_OwwWQt!Gk_X z#4|L!4T~2*MX<7d`xo@PHq;aV$r+}sy|uH_q27AX?yG#J7*e5jY5vb3CQ7nzZxr6m zrh6WU5qfJ|DWdKgK=l&5O&BC;dNT21s~Qc!x3y+I3QUSl2rZio&jX0;np&Gh zuUwI@oL!C>Z|h_OXBjxs(LZLrFUAhja^{u_dqH2uv|@7skI%HxzxO#$Ay~p8;igyY zBGag7bW(~pl&xbj4sseuv#!rECB(AQqw{T_TDr1*fT*+s33UJGk4!MGD*g`I;bN*s z>t)E^y#1>d9;aYsSDY=BBklo+^u=(*N>|fawmhdimiAmCdWxr;#>?32%zvp#{voiT zw*E$vKW9he9+Twf3~U$x4=*-mX6A1>*5AyZOOt$8U^C+IQIhOB4Kip5;Frj9Bqkw< z2h|bQw=)>psS&24CM1Xru=|yOTMSLq*oyyixb^EDF z@&6S>eh+LE3F8I1z!#i>fSxQOryxO|f=8fHG?;eXp^QcBAg-8;5zA*-~~Qf5l4ZmGcS(_dPtf`$;16*)3nF zVR?l&&H!EoZnLu!i%Wl+o*Si5#S&0JsFo6ukK^WNH>#P6&Rq^pFrtkjO5crWvTclR zR7@=kP*{yVeFmAmp#wycka!Aot(f5&Mtw)`Vt5lgx8SM0=u48dm{)bPunQpr&$Rrk zj<-69$#>x-zuI+0 zS&@01KtL~}g;J^tmC^Q?ftPm%r=30q&9e~GMyx!*jIzI{veDr4i!Bi_yxw}6X%lzq zh+0y3WAbQ~H?j9*bN)v;h0pPA?eBV?6Q++8UoU@fR>Eew>JO&iPKh@tn{|s?L=l8= zQb(M#;d=#(g56=8&nH5oGWfpSMUGQVR~%Ai?({IFZ7RScyeSdrb)i=Kk#idns(`=@ zyfP+5IBMQ{Hv7F{5+VWUsFPUJy&EZla4Ng?Nrh>QSL-H6XX$XwoI1BuA*K^N@qOu< z$LG1br}T|NFURdUcdIgWVpf^Lw~{(#i%((rp@wiNUdt5ktQ&K_941oMlK)Yhj6~)U zjHx#`s%su+XB|xllrSH?{Z}~oJ;Zq#t7pM_6YJx`r)e$JuH~A;*G^>duAW~T>t+3y zsj4RPBoCu^CvSXG%Wf^yb~n=k>h6S)q2{aMFd2u6CtVlQ@8)yNTf%ajF9vh>o9MDBzEtaiR+R0d|T8N4w`A*#?s~T;&Y1Y z!y2)LS6EO7&vKf!<%ZJDPty85b+S#d@A!Fd!pndzjV2OemU3VxNvGJ%TwL5X5SuV< ziRqtS@BkU2KTQ&)6SDH-FY7@{dXKQ)&`606QZKBLfo$ z<2RE0X8v57gz0`s3LR(>G0(|0n^a2U&EsX2>M(p6J}k!BVrfu5 zNgn%@5&7f{F42po1$}EUZRG=mtfCEu&Bn{Vj7YB5H>4#9BmkEYffs`q zK9sYr+M`nXk$6Gcd{r*sv`NDxrz(8x>2XC4WG$~@s)@-nD!y2+1s#=>eL37jV;p6+ z7v96Ro&$B_;>I_A@e;9&2VZ$wBQ#=7Dof^*)H%ZbyZo?MOf1VWDp^+nsy>H@HzJKM zNaeQ~*ev-=tH2vLlUv(cpi11loYNs|;?#0cr8mzv}sNFw&~uQkaZ z$~HfxCShY>dPS1?yrbTygTRyLfX+-&RuEg#y zF~d6<&%e6&7Z^`gj2566r=NdA7%Hhaj8`DA@AzbJw;>gW9yPJC?6~L^aj!p;ojW-0 zLN$e4RcVV}OHxN!&JoJz02ne}?t?+OF;1i3gbQ60^t{mq>^I~qR;;dC%XB}>TWw9MSSMY2KMkoVLHw$8p8d(HJ%#JsA(!bJumE?g<5Jcz{1O(3vX#WL~{ z3eQ73t$6L6xTs8REAz^k=8f{k`pf6~w9dRyDd*k)I(g;8IP$FGsX zmVh@-KHw%LP*Tcj4)-NZZAkSgvJ{Jym8fjEvQRM!TU#4YQnlbsh$r&K0S_-Nlhdt9 zYYJ9F1X41A-Yb27X{ti&^Cc{=k8in88jc8^r1eh_z(}nPfxSreauo){$FSm5++5C$ zk-Bhb>sT=^uPZJpdhA`F3HTkZRkDl`;>MeQ{85Npg((-MU8lxAtw8oJ911zO z?v$MTA&6k;bzQ;+#3`@Oh@pglF{o(<-SKwoV_zr=qkP zZyzAUnHf{$AdqCe0wu0wN{l*RaW{%=DOy%%F?D~wX~d_pYPenvu$8V3_T+fmHgasc zCN=w#^1Q#TQwW!kpU{~&HvuoJ8IgvbpCrgnx0avlH9OXs2TUjMezpwWt5?iaiMV;$ zt9)nMz!dMK&fXM$mSmg6rSZHA5!8?l0Awa*^yT!Lx%vAPn~aX#eM$b%34SF^5^Dw*~c9DDPtQ}0LQ~DfnRd2-6T(2M8ikzh>fHKMcOX-~ zsp2q6C8m?lgEDyVmW8vLIoPqdd$@56iw0qD{M5MVXD2^U4|{Bao(^8CN%1{1fwn~3 znY~A`T!hGS)OA%a%DV#Q+(%f$R?@f=Lzt7bSIx)XC`POEp?%Lgn&?J{nS3SoC4D3m*TuA9MucX87aNQn+lA>LTUVG4h&4CRhGzr~A zr9a~s@Fn8iwBHOGh2CpR!WFDK%#Zw$kv6=W^P0#Ft95TwNo%cGuv(wMmUBvcfx{4Y z{!x@{=vxU-sZ=NBmT!D~T2M3g$sn-)U!mmp@CN6ePDp|>!p%%Du@Nc#9{z(R72z22Zil-Dt?Y zPrkSaA$gK6&s(e_gM9FXDw|^WId_{ls#r z`&Cwa6MKnmrE-EK5>mnVG&rhsrz|HnD`D-gC}Doo9B=1f>|$>0O7}1kgAk zO4~P{Ks{Up27Bm{-}(Rb$p4hOt!2) zM?LcY>5cDz$`CVZKes$ak)$YJgi>=CgYNoj=ZSY(+8!0sv~axPW$oMtSn}|o6aivCXE*|@P4?#Ht%ZbBz zRrowt+_9U{MFI9`Hy=Z2%?XzGzWBu|N{2M9z%O`KjH}-Cs;0J-6n>ryRzaQFLC;un z)V3z4R&^%=dDZ+0tf zCU7hDwl%);0fiVUv}|dej6@GdeeF?ndr0=_lY8b=w=4PZ_i)Ar2P z!Gof`Nv+az>-M=)U_7>=$XP5nxjMQX!x$XvmhW=HtxlCB;G%mp`Fj z8&4;a{m4)1nC>l2-Yz;$UXbxQO_B*X2-0+@En+1v@EQ3OaN5M?+FGtrM@I!)91Nc< z|041`!kpJT6*@1^@6iL^pB^3Jmk3K<)CSkK6%dw0euWCkBdGi~%joMUqZ8+V|3e1o z#ly9llLusc`VAR^k0r=YiHxtI%!3SJcyvGSH}mHjnetBqNE-MkDzeaxbti5$KQdW`27Q+UN6JawuR(E$0xwf}&cl#|vf=Xr07(Lc@2N+SavaxoFj~ zpB44>I~iMrQU7a^NEIg_siv(>LgF4&4@f&xjr_G+*^EsAf|`8iR*P9&9}nF?j# z31wnR`OS`mJLYL`ezWnLwYD8=M+bj zyLm`jYE(4~yeZR=N>0m+3Tkj1Ga~eGk>+AgyulDz?_aJo%u2hHcFce1LNx5=`Sd~k zb$Bbkm+>5>>O*x3ZmM&EEl5= + + 4.0.0 + + com.lanternsoftware.util + lantern-util-dao + lantern-util-dao + 1.0.0 + jar + + + + ${project.groupId} + lantern-util-common + ${project.version} + + + org.mongodb + bson + 4.0.4 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar + + + + + + + diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AbstractDaoProxy.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AbstractDaoProxy.java new file mode 100644 index 0000000..f44ca6e --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AbstractDaoProxy.java @@ -0,0 +1,323 @@ +package com.lanternsoftware.util.dao; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.ITransformer; +import com.lanternsoftware.util.NullUtils; + +public abstract class AbstractDaoProxy implements IDaoProxy { + private ExecutorService executor; + private int maxThreads = 50; + protected QueryPreparer queryPreparer = null; + + @Override + public void shutdown() { + if (executor != null) + executor.shutdownNow(); + } + + public void setQueryPreparer(QueryPreparer _queryPreparer) { + queryPreparer = _queryPreparer; + } + + @Override + public List query(Class _class, DaoQuery _query) { + return query(_class, _query, (DaoSort) null); + } + + @Override + public List query(final Class _class, DaoQuery _query, DaoSort _sort) { + return query(_class, _query, null, _sort); + } + + @Override + public List query(Class _class, DaoQuery _query, Collection _fields) { + return query(_class, _query, _fields, null); + } + + @Override + public List query(final Class _class, DaoQuery _query, Collection _fields, DaoSort _sort) { + return query(_class, _query, _fields, _sort, 0, -1); + } + + @Override + public List query(final Class _class, DaoQuery _query, Collection _fields, DaoSort _sort, int _first, int _count) { + return toObjects(queryForEntities(DaoSerializer.getTableName(_class, getType()), _query, _fields, _sort, _first, _count), _class); + } + + @Override + public Future> queryAsync(Class _class, DaoQuery _query) { + return submit(new QueryExecution(this, _class, _query)); + } + + @Override + public Future> queryAsync(Class _class, DaoQuery _query, DaoSort _sort) { + return submit(new QueryExecution(this, _class, _query, _sort)); + } + + @Override + public Future> queryAsync(Class _class, DaoQuery _query, Collection _fields) { + return submit(new QueryExecution(this, _class, _query, _fields)); + } + + @Override + public Future> queryAsync(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort) { + return submit(new QueryExecution(this, _class, _query, _fields, _sort)); + } + + @Override + public Future> queryWithFinalizer(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort, QueryFinalizer _finalizer) { + return submit(new QueryFinalizerExecution(this, _class, _query, _fields, _sort, _finalizer)); + } + + @Override + public DaoPage queryPage(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count) { + return new DaoPage(query(_class, _query, _fields, _sort, _offset, _count), count(_class, _query)); + } + + @Override + public DaoPage queryForEntitiesPage(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count) { + return new DaoPage(queryForEntities(_tableName, _query, _fields, _sort, _offset, _count), count(_tableName, _query)); + } + + @Override + public T queryOne(Class _class, DaoQuery _query) { + return queryOne(_class, _query, null, null); + } + + @Override + public T queryOne(Class _class, DaoQuery _query, DaoSort _sort) { + return queryOne(_class, _query, null, _sort); + } + + @Override + public T queryOne(Class _class, DaoQuery _query, Collection _fields) { + return queryOne(_class, _query, _fields, null); + } + + @Override + public T queryOne(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort) { + return CollectionUtils.getFirst(query(_class, _query, _fields, _sort, 0, 1)); + } + + @Override + public Future queryOneAsync(Class _class, DaoQuery _query) { + return submit(new QueryOneExecution(this, _class, _query)); + } + + @Override + public Future queryOneAsync(Class _class, DaoQuery _query, DaoSort _sort) { + return submit(new QueryOneExecution(this, _class, _query, _sort)); + } + + @Override + public Future queryOneAsync(Class _class, DaoQuery _query, Collection _fields) { + return submit(new QueryOneExecution(this, _class, _query, _fields)); + } + + @Override + public Future queryOneAsync(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort) { + return submit(new QueryOneExecution(this, _class, _query, _fields, _sort)); + } + + @Override + public List queryImportant(Class _class, DaoQuery _query) { + return queryImportant(_class, _query, null); + } + + @Override + public List queryImportant(Class _class, DaoQuery _query, DaoSort _sort) { + return query(_class, _query, DaoSerializer.getImportantFields(_class), _sort); + } + + @Override + public List queryImportant(Class _class, DaoQuery _query, DaoSort _sort, int _first, int _count) { + return query(_class, _query, DaoSerializer.getImportantFields(_class), _sort, _first, _count); + } + + @Override + public Future> queryImportantAsync(Class _class, DaoQuery _query) { + return queryAsync(_class, _query, DaoSerializer.getImportantFields(_class)); + } + + @Override + public Future> queryImportantAsync(Class _class, DaoQuery _query, DaoSort _sort) { + return queryAsync(_class, _query, DaoSerializer.getImportantFields(_class), _sort); + } + + @Override + public DaoPage queryImportantPage(Class _class, DaoQuery _query, DaoSort _sort, int _offset, int _count) { + return new DaoPage(queryImportant(_class, _query, _sort, _offset, _count), count(_class, _query)); + } + + @Override + public List queryAll(Class _class) { + return query(_class, null); + } + + @Override + public boolean exists(Class _class, DaoQuery _query) { + return exists(DaoSerializer.getTableName(_class, getType()), _query); + } + + @Override + public boolean exists(String _tableName, DaoQuery _query) { + return count(_tableName, _query) > 0; + } + + @Override + public List queryForEntities(String _tableName, DaoQuery _query) { + return queryForEntities(_tableName, _query, (DaoSort) null); + } + + @Override + public List queryForEntities(String _tableName, DaoQuery _query, Collection _fields) { + return queryForEntities(_tableName, _query, _fields, null); + } + + @Override + public List queryForEntities(String _tableName, DaoQuery _query, DaoSort _sort) { + return queryForEntities(_tableName, _query, null, _sort); + } + + @Override + public List queryForEntities(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort) { + return queryForEntities(_tableName, _query, _fields, _sort, 0, -1); + } + + @Override + public DaoEntity queryForEntity(String _tableName, DaoQuery _query) { + return CollectionUtils.getFirst(queryForEntities(_tableName, _query, null, null, 0, 1)); + } + + @Override + public DaoEntity queryForEntity(String _tableName, DaoQuery _query, DaoSort _sort) { + return CollectionUtils.getFirst(queryForEntities(_tableName, _query, null, _sort, 0, 1)); + } + + @Override + public DaoEntity queryForEntity(String _tableName, DaoQuery _query, Collection _fields) { + return CollectionUtils.getFirst(queryForEntities(_tableName, _query, _fields, null, 0, 1)); + } + + @Override + public DaoEntity queryForEntity(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort) { + return CollectionUtils.getFirst(queryForEntities(_tableName, _query, _fields, _sort, 0, 1)); + } + + @Override + public String queryForOneField(Class _class, DaoQuery _query, String _field) { + return CollectionUtils.getFirst(queryForField(_class, _query, _field)); + } + + @Override + public List queryForField(Class _class, DaoQuery _query, String _field) { + return queryForField(DaoSerializer.getTableName(_class, getType()), _query, _field); + } + + @Override + public List queryForField(Class _class, DaoQuery _query, final String _field, DaoSort _sort) { + return CollectionUtils.transform(queryForEntities(DaoSerializer.getTableName(_class, getType()), _query, Arrays.asList(_field), _sort), new ITransformer() { + @Override + public String transform(DaoEntity _daoEntity) { + return DaoSerializer.getString(_daoEntity, _field); + } + }); + } + + @Override + public List queryForField(String _tableName, DaoQuery _query, final String _field) { + return CollectionUtils.transform(queryForEntities(_tableName, _query, Arrays.asList(_field)), new ITransformer() { + @Override + public String transform(DaoEntity _daoEntity) { + return DaoSerializer.getString(_daoEntity, _field); + } + }); + } + + @Override + public String save(Object _object) { + return saveEntity(_object.getClass(), DaoSerializer.toDaoEntity(_object, getType())); + } + + @Override + public Map save(Collection _objects) { + Map ids = new HashMap(); + for (T o : _objects) { + String id = save(o); + if (NullUtils.isNotEmpty(id)) + ids.put(id, o); + } + return ids; + } + + @Override + public Map save(Class _class, Collection _entities) { + Map ids = new HashMap<>(); + for (DaoEntity e : _entities) { + ids.put(saveEntity(_class, e), e); + } + return ids; + } + + @Override + public boolean delete(Class _class, DaoQuery _query) { + return delete(DaoSerializer.getTableName(_class, getType()), _query); + } + + @Override + public int count(Class _class, DaoQuery _query) { + return count(DaoSerializer.getTableName(_class, getType()), _query); + } + + private Future> submit(Callable> _execution) { + return executor().submit(_execution); + } + + private Future submit(QueryOneExecution _execution) { + return executor().submit(_execution); + } + + public void setMaxThreads(int _maxThreads) { + maxThreads = _maxThreads; + } + + @Override + public void setExecutor(ExecutorService _executor) { + executor = _executor; + } + + private synchronized ExecutorService executor() { + if (executor == null) + executor = Executors.newFixedThreadPool(maxThreads); + return executor; + } + + protected T toObject(DaoEntity _entity, Class _class) { + return CollectionUtils.getFirst(toObjects(Collections.singletonList(_entity), _class)); + } + + protected List toObjects(List _entities, final Class _class) { + return CollectionUtils.transform(_entities, new ITransformer() { + @Override + public T transform(DaoEntity _daoEntity) { + return DaoSerializer.fromDaoEntity(_daoEntity, _class, getType()); + } + }); + } + protected DaoQuery prepareQuery(DaoQuery _query) { + if (queryPreparer == null) + return _query; + return queryPreparer.prepareQuery(_query); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AbstractDaoSerializer.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AbstractDaoSerializer.java new file mode 100644 index 0000000..176068a --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AbstractDaoSerializer.java @@ -0,0 +1,253 @@ +package com.lanternsoftware.util.dao; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.annotations.CaseFormat; +import com.lanternsoftware.util.dao.annotations.DBClob; +import com.lanternsoftware.util.dao.annotations.DBIgnore; +import com.lanternsoftware.util.dao.annotations.DBName; +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.DBType; +import com.lanternsoftware.util.dao.annotations.Important; +import com.lanternsoftware.util.dao.annotations.TimestampDates; +import com.lanternsoftware.util.dao.annotations.Unimportant; + +public abstract class AbstractDaoSerializer implements IDaoSerializer { + protected final Map, List> annotations = new HashMap, List>(); + protected final List importantFields = new ArrayList(); + protected final Map fieldTypes = new HashMap(); + + public AbstractDaoSerializer() { + addFields(getSupportedClass()); + } + + public void addFields(Class _class) { + if (_class == null) { + return; + } + List important = new ArrayList(); + List unimportant = new ArrayList(); + List normal = new ArrayList(); + for (Field f : _class.getDeclaredFields()) { + if (!isSerializable(f)) + continue; + String dbName = fieldToDatabaseName(f); + if (f.isAnnotationPresent(Important.class)) + important.add(dbName); + else if (f.isAnnotationPresent(Unimportant.class)) + unimportant.add(dbName); + else + normal.add(dbName); + Class type = getType(f); + if (NullUtils.isOneOf(f.getType(), Byte.TYPE, byte.class)) + fieldTypes.put(dbName, Types.INTEGER); + if (NullUtils.isOneOf(f.getType(), Short.TYPE, Short.class)) + fieldTypes.put(dbName, Types.INTEGER); + else if (NullUtils.isOneOf(f.getType(), Integer.TYPE, Integer.class)) + fieldTypes.put(dbName, Types.INTEGER); + else if (NullUtils.isOneOf(f.getType(), Long.TYPE, Long.class)) + fieldTypes.put(dbName, Types.BIGINT); + else if (NullUtils.isOneOf(f.getType(), Double.TYPE, Double.class, Float.TYPE, Float.class)) + fieldTypes.put(dbName, Types.DOUBLE); + else if (NullUtils.isOneOf(f.getType(), Boolean.TYPE, Boolean.class)) + fieldTypes.put(dbName, Types.BIT); + else if (f.getType().equals(String.class) || f.getType().isEnum()) { + if (f.isAnnotationPresent(DBClob.class)) + fieldTypes.put(dbName, Types.CLOB); + else + fieldTypes.put(dbName, Types.VARCHAR); + } + else if (f.getType().equals(Date.class)) { + if (DaoSerializer.isAnnotationPresent(_class, TimestampDates.class)) + fieldTypes.put(dbName, Types.TIMESTAMP); + else + fieldTypes.put(dbName, Types.BIGINT); + } + for (Annotation a : f.getAnnotations()) { + CollectionUtils.addToMultiMap(a.annotationType(), dbName, annotations); + } + } + if (!important.isEmpty()) + importantFields.addAll(important); + else { + normal.removeAll(unimportant); + importantFields.addAll(normal); + } + addFields(_class.getSuperclass()); + } + + @Override + public List getFieldsByAnnotation(Class _fieldAnnotation) { + return CollectionUtils.makeNotNull(annotations.get(_fieldAnnotation)); + } + + @Override + public List getImportantFields() { + return importantFields; + } + + @Override + public int getSqlType(String _fieldName) { + Integer type = fieldTypes.get(_fieldName); + if (type == null) + return Types.NULL; + return type; + } + + @Override + public String getTableName() { + DBSerializable table = getSupportedClass().getAnnotation(DBSerializable.class); + if ((table != null) && NullUtils.isNotEmpty(table.name())) + return table.name(); + return getterNameToDatabaseName(getSupportedClass().getSimpleName()); + } + + public static String fieldToGetterName(Field _field) { + String name = _field.getName(); + return Character.toUpperCase(name.charAt(0)) + name.substring(1); + } + + public static String getterNameToDatabaseName(String _name) { + return toSnake(_name); + } + + public static String getterNameToDatabaseName(String _name, CaseFormat _format) { + return convertCase(_name, CaseFormat.PASCAL, _format); + } + + public static String convertCase(String _name, CaseFormat _inFormat, CaseFormat _outFormat) { + if (_inFormat == _outFormat) + return _name; + String pascal; + if (_inFormat == CaseFormat.SNAKE) + pascal = toPascal(_name); + else if (_inFormat == CaseFormat.CAMEL) + pascal = Character.toUpperCase(_name.charAt(0)) + _name.substring(1); + else + pascal = _name; + if (_outFormat == CaseFormat.SNAKE) + return toSnake(pascal); + if (_outFormat == CaseFormat.CAMEL) + return Character.toLowerCase(pascal.charAt(0)) + pascal.substring(1); + return pascal; + } + + private static String toPascal(String _snake) { + StringBuilder field = new StringBuilder(); + boolean charWasWordStart = true; + for (int i = 0; i < _snake.length(); i++) { + if (_snake.charAt(i) == '_') + charWasWordStart = true; + else { + field.append(charWasWordStart?Character.toUpperCase(_snake.charAt(i)):_snake.charAt(i)); + charWasWordStart = false; + } + } + return field.toString(); + } + + private static String toSnake(String _pascal) { + StringBuilder field = null; + boolean charWasUpper = false; + for (int i = 0; i < _pascal.length(); i++) { + if (Character.isUpperCase(_pascal.charAt(i))) { + if (field == null) { + field = new StringBuilder(); + field.append(Character.toLowerCase(_pascal.charAt(i))); + } + else if (!charWasUpper) { + field.append("_"); + field.append(Character.toLowerCase(_pascal.charAt(i))); + } + else { + field.append(Character.toLowerCase(_pascal.charAt(i))); + } + charWasUpper = true; + } + else { + charWasUpper = false; + if (field == null) { + field = new StringBuilder(); + } + field.append(_pascal.charAt(i)); + } + } + return field.toString(); + } + + public static String fieldToDatabaseName(Field _field) { + return fieldToDatabaseName(_field, CaseFormat.SNAKE); + } + + public static String fieldToDatabaseName(Field _field, CaseFormat _format) { + DBName name = _field.getAnnotation(DBName.class); + if (name != null) + return name.name(); + if (_format == CaseFormat.CAMEL) + return _field.getName(); + return getterNameToDatabaseName(fieldToGetterName(_field), _format); + } + + public static Class getType(Field _f) { + DBType type = _f.getAnnotation(DBType.class); + if (type != null) + return type.type(); + return _f.getType(); + } + + public static boolean isSerializable(Field _f) { + return isSerializable(_f, false); + } + + public static boolean isSerializable(Field _f, boolean _serializeObjects) { + if (Modifier.isStatic(_f.getModifiers()) || Modifier.isTransient(_f.getModifiers()) || _f.isAnnotationPresent(DBIgnore.class)) + return false; + if (_serializeObjects) + return true; + return !requiresCustomSerializer(_f); + } + + public static boolean requiresCustomSerializer(Field _f) { + Class type = getType(_f); + if (Collection.class.isAssignableFrom(type)) + type = getCollectionType(_f); + return !(type.isPrimitive() || type.isEnum() || NullUtils.isOneOf(type, String.class, Date.class, BigDecimal.class, byte[].class, Boolean.class, Double.class, Long.class, Integer.class, Float.class)); + } + + public static Class getCollectionType(Field _f) { + if (Collection.class.isAssignableFrom(getType(_f)) && (_f.getGenericType() instanceof ParameterizedType)) { + ParameterizedType t = (ParameterizedType) _f.getGenericType(); + if (t.getActualTypeArguments().length > 0) { + Type t2 = t.getActualTypeArguments()[0]; + if (t2 instanceof Class) + return (Class)t2; + } + } + return null; + } + + @Override + public List getSupportedProxies() { + return Collections.emptyList(); + } + + @Override + public List getIndexes() { + return null; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AnnotationFinder.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AnnotationFinder.java new file mode 100644 index 0000000..10f3088 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/AnnotationFinder.java @@ -0,0 +1,79 @@ +package com.lanternsoftware.util.dao; + +import java.io.File; +import java.io.FileInputStream; +import java.lang.annotation.Annotation; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.commons.io.IOUtils; + +import com.lanternsoftware.util.NullUtils; + +public abstract class AnnotationFinder { + public static Map findAnnotatedClasses(String _codePath, Class _annotationClass) { + String annotationName = "@" + _annotationClass.getSimpleName(); + Map mapClasses = new TreeMap<>(); + searchFile(new File(_codePath), mapClasses, annotationName); + Iterator classIter = mapClasses.keySet().iterator(); + while (classIter.hasNext()) { + String className = classIter.next(); + try { + Class clazz = Class.forName(className); + if (!clazz.isAnnotationPresent(_annotationClass)) + classIter.remove(); + } catch (ClassNotFoundException _e) { + //ignore + } + } + return mapClasses; + } + + public static Map findSubclasses(String _codePath, Class _superclass) { + Map mapClasses = new TreeMap<>(); + searchFile(new File(_codePath), mapClasses, _superclass.getSimpleName()); + Iterator classIter = mapClasses.keySet().iterator(); + while (classIter.hasNext()) { + String className = classIter.next(); + Class clazz = NullUtils.getClass(className, _superclass); + if (clazz == null) + classIter.remove(); + } + return mapClasses; + } + + private static void searchFile(File _f, Map _mapClasses, String _searchString) { + if (_f == null) { + return; + } + if (_f.isDirectory()) { + for (File child : _f.listFiles()) { + searchFile(child, _mapClasses, _searchString); + } + } + else if (_f.getName().endsWith(".java")) { + try { + String source = IOUtils.toString(new FileInputStream(_f)); + if (!source.contains(_searchString)) { + return; + } + int packagePos = source.indexOf("package "); + int packageEnd = source.indexOf(";", packagePos); + String packageName = source.substring(packagePos + 8, packageEnd); + int classPos = source.indexOf("public class") + 12; + while (source.charAt(classPos) == ' ') + classPos++; + int newLineN = source.indexOf("\n", classPos); + int newLineR = source.indexOf("\r", classPos); + int space = source.indexOf(" ", classPos); + int classEnd = NullUtils.min((newLineN == -1) ? Integer.MAX_VALUE : newLineN, (newLineR == -1) ? Integer.MAX_VALUE : newLineR, (space == -1) ? Integer.MAX_VALUE : space); + String className = source.substring(classPos, classEnd); + _mapClasses.put(packageName + "." + className, _f.getParent()); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoEntity.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoEntity.java new file mode 100644 index 0000000..51ee2f0 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoEntity.java @@ -0,0 +1,100 @@ +package com.lanternsoftware.util.dao; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.bson.Document; + +public class DaoEntity implements Map { + private final Document map; + + public DaoEntity() { + map = new Document(); + } + + public DaoEntity(Document _doc) { + map = _doc == null?new Document():_doc; + } + + public DaoEntity(Map _map) { + map = new Document(); + for (Entry e : _map.entrySet()) { + map.put(e.getKey(), e.getValue()); + } + } + + public DaoEntity(String _name, Object _o) { + map = new Document(); + put(_name, _o); + } + + public DaoEntity and(String _name, Object _o) { + put(_name, _o); + return this; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public Object get(Object key) { + return map.get(key); + } + + @Override + public Object put(String key, Object value) { + return map.put(key, value); + } + + @Override + public Object remove(Object key) { + return map.remove(key); + } + + @Override + public void putAll(Map m) { + map.putAll(m); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + + public Document toDocument() { + return map; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoPage.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoPage.java new file mode 100644 index 0000000..39ffc6d --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoPage.java @@ -0,0 +1,32 @@ +package com.lanternsoftware.util.dao; + +import java.util.List; + +public class DaoPage { + private List results; + private int totalResultCount; + + public DaoPage() { + } + + public DaoPage(List _results, int _totalResultCount) { + results = _results; + totalResultCount = _totalResultCount; + } + + public List getResults() { + return results; + } + + public void setResults(List _results) { + results = _results; + } + + public int getTotalResultCount() { + return totalResultCount; + } + + public void setTotalResultCount(int _totalResultCount) { + totalResultCount = _totalResultCount; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoProxyType.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoProxyType.java new file mode 100644 index 0000000..85ca82b --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoProxyType.java @@ -0,0 +1,10 @@ +package com.lanternsoftware.util.dao; + +public enum DaoProxyType { + JDBC, + SOLR, + KUDU, + MONGO, + EPHEMERAL, + DYNAMODB +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoQuery.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoQuery.java new file mode 100644 index 0000000..1f031ae --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoQuery.java @@ -0,0 +1,211 @@ +package com.lanternsoftware.util.dao; + +import java.util.Collection; +import java.util.Map; + +import org.bson.Document; + +public class DaoQuery extends Document { + public DaoQuery() { + } + + public DaoQuery(Map map) { + super(map); + } + + public DaoQuery(String _name, Object _o) { + put(_name, _o); + } + + public DaoQuery and(String _name, Object _o) { + put(_name, _o); + return this; + } + + public DaoQuery or(DaoQuery _query) { + put("$or", _query); + return this; + } + + public DaoQuery andIgnoreCase(String _name, Object _o) { + put(_name, new DaoQuery("$equalIgnoreCase", _o)); + return this; + } + + public DaoQuery andNotEquals(String _name, Object _o) { + put(_name, new DaoQuery("$ne", _o)); + return this; + } + + public DaoQuery andIn(String _name, Collection _values) { + put(_name, new DaoQuery("$in", _values)); + return this; + } + + public DaoQuery andNotIn(String _name, Collection _values) { + put(_name, new DaoQuery("$nin", _values)); + return this; + } + + public DaoQuery andInLongs(String _name, Collection _values) { + put(_name, new DaoQuery("$in", _values)); + return this; + } + + public DaoQuery andNotInLongs(String _name, Collection _values) { + put(_name, new DaoQuery("$nin", _values)); + return this; + } + + public DaoQuery andInIntegers(String _name, Collection _values) { + put(_name, new DaoQuery("$in", _values)); + return this; + } + + public DaoQuery andNotInIntegers(String _name, Collection _values) { + put(_name, new DaoQuery("$nin", _values)); + return this; + } + + public DaoQuery andGt(String _name, Object _o) { + put(_name, new DaoQuery("$gt", _o)); + return this; + } + + public DaoQuery andLt(String _name, Object _o) { + put(_name, new DaoQuery("$lt", _o)); + return this; + } + + public DaoQuery andGte(String _name, Object _o) { + put(_name, new DaoQuery("$gte", _o)); + return this; + } + + public DaoQuery andLte(String _name, Object _o) { + put(_name, new DaoQuery("$lte", _o)); + return this; + } + + public DaoQuery andBetween(String _name, Object _lowerBound, Object _upperBound) { + put(_name, new DaoQuery("$gt", _lowerBound).and("$lt", _upperBound)); + return this; + } + + public DaoQuery andBetweenInclusive(String _name, Object _lowerBound, Object _upperBound) { + put(_name, new DaoQuery("$gte", _lowerBound).and("$lte", _upperBound)); + return this; + } + + public DaoQuery andBetweenInclusiveExclusive(String _name, Object _lowerBound, Object _upperBound) { + put(_name, new DaoQuery("$gte", _lowerBound).and("$lt", _upperBound)); + return this; + } + + public DaoQuery andBetweenExclusiveInclusive(String _name, Object _lowerBound, Object _upperBound) { + put(_name, new DaoQuery("$gt", _lowerBound).and("$lte", _upperBound)); + return this; + } + + public DaoQuery andStartsWith(String _name, String _o) { + put(_name, new DaoQuery("$startsWith", _o)); + return this; + } + + public DaoQuery andStartsWithIgnoreCase(String _name, String _o) { + put(_name, new DaoQuery("$startsWithIgnoreCase", _o)); + return this; + } + + public DaoQuery andContains(String _name, String _o) { + put(_name, new DaoQuery("$contains", _o)); + return this; + } + + public DaoQuery andContainsIgnoreCase(String _name, String _o) { + put(_name, new DaoQuery("$containsIgnoreCase", _o)); + return this; + } + + public DaoQuery andNull(String _name) { + put(_name, "$null"); + return this; + } + + public DaoQuery andNotNull(String _name) { + put(_name, "$notnull"); + return this; + } + + public static DaoQuery notEquals(String _name, Object _value) { + return new DaoQuery().andNotEquals(_name, _value); + } + + public static DaoQuery in(String _name, Collection _values) { + return new DaoQuery().andIn(_name, _values); + } + + public static DaoQuery notIn(String _name, Collection _values) { + return new DaoQuery().andNotIn(_name, _values); + } + + public static DaoQuery inLongs(String _name, Collection _values) { + return new DaoQuery().andInLongs(_name, _values); + } + + public static DaoQuery notInLongs(String _name, Collection _values) { + return new DaoQuery().andNotInLongs(_name, _values); + } + + public static DaoQuery inIntegers(String _name, Collection _values) { + return new DaoQuery().andInIntegers(_name, _values); + } + + public static DaoQuery notInIntegers(String _name, Collection _values) { + return new DaoQuery().andNotInIntegers(_name, _values); + } + + public static DaoQuery gt(String _name, Object _value) { + return new DaoQuery().andGt(_name, _value); + } + + public static DaoQuery lt(String _name, Object _value) { + return new DaoQuery().andLt(_name, _value); + } + + public static DaoQuery gte(String _name, Object _value) { + return new DaoQuery().andGte(_name, _value); + } + + public static DaoQuery lte(String _name, Object _value) { + return new DaoQuery().andLte(_name, _value); + } + + public static DaoQuery between(String _name, Object _lowerBound, Object _upperBound) { + return new DaoQuery().andBetween(_name, _lowerBound, _upperBound); + } + + public static DaoQuery startsWith(String _name, String _value) { + return new DaoQuery().andStartsWith(_name, _value); + } + + public static DaoQuery startsWithIgnoreCase(String _name, String _value) { + return new DaoQuery().andStartsWithIgnoreCase(_name, _value); + } + + public static DaoQuery contains(String _name, String _value) { + return new DaoQuery().andContains(_name, _value); + } + + public static DaoQuery containsIgnoreCase(String _name, String _value) { + return new DaoQuery().andContainsIgnoreCase(_name, _value); + } + + public static DaoQuery isNull(String _name) { + return new DaoQuery().andNull(_name); + } + + public static DaoQuery notNull(String _name) { + return new DaoQuery().andNotNull(_name); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSerializer.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSerializer.java new file mode 100644 index 0000000..3ed9a3b --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSerializer.java @@ -0,0 +1,913 @@ +package com.lanternsoftware.util.dao; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; + +import org.bson.BsonBinaryReader; +import org.bson.BsonBinaryWriter; +import org.bson.Document; +import org.bson.codecs.BsonTypeClassMap; +import org.bson.codecs.BsonValueCodecProvider; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.DocumentCodec; +import org.bson.codecs.DocumentCodecProvider; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.IterableCodec; +import org.bson.codecs.ValueCodecProvider; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.io.BasicOutputBuffer; +import org.bson.json.Converter; +import org.bson.json.JsonReader; +import org.bson.json.JsonWriterSettings; +import org.bson.json.StrictJsonWriter; +import org.bson.types.Binary; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.DateUtils; +import com.lanternsoftware.util.ITransformer; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.ZipUtils; +import com.lanternsoftware.util.dao.annotations.DBIndex; +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; + +public class DaoSerializer { + private static final Logger LOG = LoggerFactory.getLogger(DaoSerializer.class); + private static final Map, List> serializers = new HashMap<>(); + + static { + for (IDaoSerializer serializer : ServiceLoader.load(IDaoSerializer.class)) { + CollectionUtils.addToMultiMap(serializer.getSupportedClass(), serializer, serializers); + } + } + + public static void addSerializer(IDaoSerializer _serializer) { + CollectionUtils.addToMultiMap(_serializer.getSupportedClass(), _serializer, serializers); + } + + public static IDaoSerializer getSerializer(Class _class) { + return getSerializer(_class, null); + } + + public static IDaoSerializer getSerializer(Class _class, DaoProxyType _proxyType) { + List classSerializers = serializers.get(_class); + if (classSerializers == null) { + LOG.error("No serializer exists for class " + _class.getCanonicalName()); + return null; + } + if (_proxyType != null) { + for (IDaoSerializer serializer : classSerializers) { + if (serializer.getSupportedProxies().contains(_proxyType)) + return serializer; + } + } + return CollectionUtils.getFirst(classSerializers); + } + + public static DaoEntity toDaoEntity(Object _o) { + return toDaoEntity(_o, null); + } + + public static DaoEntity toDaoEntity(Object _o, DaoProxyType _proxyType) { + if (_o == null) { + return null; + } + if (_o instanceof DaoEntity) + return (DaoEntity) _o; + if (_o instanceof Document) + return new DaoEntity((Document) _o); + IDaoSerializer serializer = getSerializer(_o.getClass(), _proxyType); + if (serializer == null) + return null; + try { + return serializer.toDaoEntity(_o); + } + catch (Exception _e) { + LOG.error("Failed to serialize entity", _e); + return null; + } + } + + public static T fromDaoEntity(DaoEntity _entity, Class _class) { + return fromDaoEntity(_entity, _class, null); + } + + public static T fromDaoEntity(DaoEntity _entity, Class _class, DaoProxyType _proxyType) { + if (_entity == null) + return null; + if (_class == DaoEntity.class) + return _class.cast(_entity); + if (_class == DaoQuery.class) + return _class.cast(new DaoQuery(_entity)); + IDaoSerializer serializer = getSerializer(_class, _proxyType); + if (serializer == null) + return null; + return serializer.fromDaoEntity(_entity); + } + + public static String getTableName(Class _class) { + return getTableName(_class, null); + } + + public static String getTableName(Class _class, DaoProxyType _proxyType) { + IDaoSerializer serializer = getSerializer(_class, _proxyType); + if (serializer == null) + return null; + return serializer.getTableName(); + } + + public static List getFieldsByAnnotation(Class _entityClass, Class _fieldAnnotation) { + return getFieldsByAnnotation(_entityClass, _fieldAnnotation, null); + } + + public static List getFieldsByAnnotation(Class _entityClass, Class _fieldAnnotation, DaoProxyType _proxyType) { + if (_entityClass == null) { + return Collections.emptyList(); + } + IDaoSerializer serializer = getSerializer(_entityClass, _proxyType); + if (serializer == null) + return Collections.emptyList(); + return serializer.getFieldsByAnnotation(_fieldAnnotation); + } + + public static List getImportantFields(Class _entityClass) { + if (_entityClass == null) { + return Collections.emptyList(); + } + IDaoSerializer serializer = getSerializer(_entityClass, null); + if (serializer == null) + return Collections.emptyList(); + return serializer.getImportantFields(); + } + + public static int getSqlType(Class _entityClass, String _fieldName) { + if (_entityClass == null) { + return Types.NULL; + } + IDaoSerializer serializer = getSerializer(_entityClass, DaoProxyType.JDBC); + if (serializer == null) { + return Types.NULL; + } + return serializer.getSqlType(_fieldName); + } + + public static int compare(DaoEntity _e, String _field, Object _comp) { + if (_comp instanceof String) + return NullUtils.compare(getString(_e, _field), (String) _comp); + if (_comp instanceof Date) + return NullUtils.compare(getDate(_e, _field), (Date) _comp); + if (_comp instanceof Long) + return NullUtils.compare(getLong(_e, _field), (Long) _comp); + if (_comp instanceof Short) + return NullUtils.compare(getShort(_e, _field), (Short) _comp); + if (_comp instanceof BigDecimal) + return NullUtils.compare(getBigDecimal(_e, _field), (BigDecimal) _comp); + if (_comp instanceof Double) + return NullUtils.compare(getDouble(_e, _field), (Double) _comp); + if (_comp instanceof Float) + return NullUtils.compare(getFloat(_e, _field), (Float) _comp); + if (_comp instanceof Integer) + return NullUtils.compare(getInteger(_e, _field), (Integer) _comp); + if (_comp instanceof Boolean) + return NullUtils.compare(getBoolean(_e, _field), (Boolean) _comp); + if (_comp instanceof Enum) + return NullUtils.compare(getString(_e, _field), ((Enum) _comp).name()); + return 0; + } + + public static String getString(DaoEntity _e, String _field) { + if (_e == null) { + return null; + } + return toString(_e.get(_field)); + } + + public static String toString(Object _o) { + if (_o instanceof String) + return (String) _o; + if (_o != null) + return String.valueOf(_o); + return null; + } + + public static String getId(DaoEntity _e, Class _entityClass) { + if (_e == null) { + return null; + } + String sPrimaryKeyField = CollectionUtils.getFirst(DaoSerializer.getFieldsByAnnotation(_entityClass, PrimaryKey.class)); + if (NullUtils.isEmpty(sPrimaryKeyField)) { + sPrimaryKeyField = "_id"; + } + return getString(_e, sPrimaryKeyField); + } + + public static Date getDate(DaoEntity _e, String _field, long _lNullValue) { + if ((_e == null) || (!_e.containsKey(_field))) { + return null; + } + Object o = _e.get(_field); + if (o == null) { + return null; + } + if (o instanceof Timestamp) { + return new Date(((Timestamp) o).getTime()); + } + if (o instanceof Date) { + return (Date) o; + } + long lDate = toLong(o); + if (lDate == _lNullValue) { + return null; + } + return new Date(lDate); + } + + public static Date getDate(DaoEntity _e, String _sField) { + return getDate(_e, _sField, Long.MIN_VALUE); + } + + public static Date getDate(DaoEntity _e, String _sField, String _format) { + return DateUtils.parse(_format, getString(_e, _sField)); + } + + public static Timestamp toTimestamp(Date _dt) { + if (_dt == null) { + return null; + } + return new Timestamp(_dt.getTime()); + } + + public static long toLong(Date _dt) { + return toLong(_dt, Long.MIN_VALUE); + } + + public static long toLong(Date _dt, long _lNullValue) { + if (_dt == null) { + return _lNullValue; + } + return _dt.getTime(); + } + + public static short getShort(DaoEntity _e, String _sField) { + if (_e == null) { + return 0; + } + return toShort(_e.get(_sField)); + } + + public static short toShort(Object _o) { + try { + if (_o instanceof Short) { + return (Short) _o; + } + if (_o instanceof Integer) { + return ((Integer) _o).shortValue(); + } + if (_o instanceof Long) { + return ((Long) _o).shortValue(); + } + if (_o instanceof Double) { + return ((Double) _o).shortValue(); + } + if (_o instanceof Boolean) { + return ((Boolean) _o) ? (short) 1 : (short) 0; + } + if (_o instanceof String) { + return Short.valueOf((String) _o); + } + return (short) 0; + } + catch (Exception _e) { + return (short) 0; + } + } + + public static BigDecimal getBigDecimal(DaoEntity _e, String _sField) { + if (_e == null) { + return new BigDecimal(0); + } + return toBigDecimal(_e.get(_sField)); + } + + public static BigDecimal toBigDecimal(Object _o) { + try { + if (_o instanceof BigDecimal) { + return (BigDecimal) _o; + } + if (_o instanceof Double) { + return new BigDecimal((Double) _o); + } + if (_o instanceof Integer) { + return new BigDecimal((Integer) _o); + } + if (_o instanceof Short) { + return new BigDecimal((Short) _o); + } + if (_o instanceof Long) { + return new BigDecimal((Long) _o); + } + if (_o instanceof Boolean) { + return new BigDecimal(((Boolean) _o) ? 1 : 0); + } + if (_o instanceof String) { + return new BigDecimal((String) _o); + } + return new BigDecimal(0); + } + catch (Exception _e) { + return new BigDecimal(0); + } + } + + public static double getDouble(DaoEntity _e, String _sField) { + if (_e == null) { + return 0.0; + } + return toDouble(_e.get(_sField)); + } + + public static double toDouble(Object _o) { + try { + if (_o instanceof Double) { + return ((Double) _o).doubleValue(); + } + if (_o instanceof BigDecimal) { + return ((BigDecimal) _o).doubleValue(); + } + if (_o instanceof Integer) { + return ((Integer) _o).doubleValue(); + } + if (_o instanceof Short) { + return ((Short) _o).doubleValue(); + } + if (_o instanceof Long) { + return ((Long) _o).doubleValue(); + } + if (_o instanceof Boolean) { + return ((Boolean) _o) ? 1.0 : 0.0; + } + if (_o instanceof String) { + return Double.valueOf((String) _o); + } + return 0.0; + } + catch (Exception _e) { + return 0.0; + } + } + + public static float getFloat(DaoEntity _e, String _sField) { + if (_e == null) { + return 0.0f; + } + return toFloat(_e.get(_sField)); + } + + public static float toFloat(Object _o) { + try { + if (_o instanceof Float) { + return ((Float) _o).floatValue(); + } + if (_o instanceof Double) { + return ((Double) _o).floatValue(); + } + if (_o instanceof BigDecimal) { + return ((BigDecimal) _o).floatValue(); + } + if (_o instanceof Integer) { + return ((Integer) _o).floatValue(); + } + if (_o instanceof Short) { + return ((Short) _o).floatValue(); + } + if (_o instanceof Long) { + return ((Long) _o).floatValue(); + } + if (_o instanceof Boolean) { + return ((Boolean) _o) ? 1.0f : 0.0f; + } + if (_o instanceof String) { + return Float.valueOf((String) _o); + } + return 0.0f; + } + catch (Exception _e) { + return 0.0f; + } + } + + public static int getInteger(DaoEntity _e, String _sField) { + if (_e == null) { + return 0; + } + return toInteger(_e.get(_sField)); + } + + public static int toInteger(Object _o) { + try { + if (_o instanceof Integer) { + return (Integer) _o; + } + if (_o instanceof Short) { + return ((Short) _o).intValue(); + } + if (_o instanceof Long) { + return ((Long) _o).intValue(); + } + if (_o instanceof Double) { + return ((Double) _o).intValue(); + } + if (_o instanceof BigDecimal) { + return ((BigDecimal) _o).intValue(); + } + if (_o instanceof Boolean) { + return ((Boolean) _o) ? 1 : 0; + } + if (_o instanceof String) { + return Integer.valueOf((String) _o); + } + return 0; + } + catch (Exception _e) { + return 0; + } + } + + public static long getLong(DaoEntity _e, String _sField) { + if (_e == null) { + return 0l; + } + return toLong(_e.get(_sField)); + } + + public static long toLong(Object _o) { + try { + if (_o instanceof Integer) { + return ((Integer) _o).longValue(); + } + if (_o instanceof Short) { + return ((Short) _o).longValue(); + } + if (_o instanceof Long) { + return (Long) _o; + } + if (_o instanceof BigDecimal) { + return ((BigDecimal) _o).longValue(); + } + if (_o instanceof Double) { + return ((Double) _o).longValue(); + } + if (_o instanceof Boolean) { + return ((Boolean) _o) ? 1L : 0L; + } + if (_o instanceof String) { + return Long.valueOf((String) _o); + } + return 0L; + } + catch (Exception _e) { + return 0L; + } + } + + public static boolean getBoolean(DaoEntity _e, String _field) { + if (_e == null) { + return false; + } + return toBoolean(_e.get(_field)); + } + + public static boolean toBoolean(Object _o) { + return toBoolean(_o, false); + } + + public static boolean toBoolean(Object _o, boolean _default) { + if (_o instanceof Boolean) + return (Boolean) _o; + if (_o instanceof String) + return ((String) _o).equalsIgnoreCase("true") || _o.equals("1"); + if (_o instanceof Integer) + return ((Integer) _o) != 0; + if (_o instanceof Long) + return ((Long) _o) != 0; + if (_o instanceof BigDecimal) + return !(_o).equals(BigDecimal.ZERO); + return _default; + } + + public static byte[] getByteArray(DaoEntity _e, String _sField) { + if (_e == null) { + return null; + } + Object o = _e.get(_sField); + if (o instanceof Binary) + return ((Binary)o).getData(); + if (o instanceof byte[]) + return (byte[]) o; + return null; + } + + public static String toEnumName(Enum _enum) { + if (_enum == null) + return ""; + return _enum.name(); + } + + public static > List toEnumNames(Collection _enums) { + return CollectionUtils.transform(_enums, new ITransformer() { + @Override + public String transform(T _enum) { + return toEnumName(_enum); + } + }); + } + + public static > T getEnum(DaoEntity _e, String _sField, Class _enumType) { + return NullUtils.toEnum(_enumType, getString(_e, _sField)); + } + + public static > T getEnum(DaoEntity _e, String _sField, Class _enumType, T _default) { + return NullUtils.toEnum(_enumType, getString(_e, _sField), _default); + } + + public static > List toEnums(Collection _enumNames, final Class _enumType) { + return CollectionUtils.transform(_enumNames, new ITransformer() { + @Override + public T transform(String _s) { + return NullUtils.toEnum(_enumType, _s); + } + }); + } + + public static DaoEntity getDaoEntity(DaoEntity _e, String _field) { + if (_e == null) + return null; + return asDaoEntity(_e.get(_field)); + } + + public static DaoEntity asDaoEntity(Object _o) { + if (_o instanceof Document) + return new DaoEntity((Document) _o); + if (_o instanceof DaoEntity) + return (DaoEntity) _o; + return null; + } + + public static T getObject(DaoEntity _e, String _field, Class _class) { + return getObject(_e, _field, _class, null); + } + + public static T getObject(DaoEntity _e, String _field, Class _class, DaoProxyType _proxyType) { + return fromDaoEntity(getDaoEntity(_e, _field), _class, _proxyType); + } + + public static List toDaoEntities(Collection _objects) { + return toDaoEntities(_objects, null); + } + + public static List toDaoEntities(Collection _objects, DaoProxyType _proxyType) { + List entities = new ArrayList<>(CollectionUtils.size(_objects)); + for (Object o : CollectionUtils.makeNotNull(_objects)) { + entities.add(toDaoEntity(o, _proxyType)); + } + return entities; + } + + public static List getDaoEntityList(DaoEntity _d, String _field) { + return getDaoEntityList(_d, _field, null); + } + + public static List getDaoEntityList(DaoEntity _d, String _field, DaoProxyType _proxyType) { + Object list = (_d == null) ? null : _d.get(_field); + if (list instanceof Collection) + return toDaoEntities((Collection) list, _proxyType); + return new ArrayList<>(); + } + + public static List getList(DaoEntity _d, String _sField, Class _classOfT) { + return getList(_d, _sField, _classOfT, null); + } + + public static List getList(DaoEntity _d, String _sField, Class _classOfT, DaoProxyType _proxyType) { + if ((_d == null) || (!_d.containsKey(_sField))) + return new ArrayList(); + return fromList(_d.get(_sField), _classOfT, _proxyType); + } + + public static List fromList(Object _list, Class _classOfT) { + return fromList(_list, _classOfT, null); + } + + public static List fromList(Object _list, Class _classOfT, DaoProxyType _proxyType) { + if (_list instanceof List) + return fromList((List) _list, _classOfT, _proxyType); + return new ArrayList<>(); + } + + public static List fromList(List _list, Class _classOfT) { + return fromList(_list, _classOfT, null); + } + + public static List fromList(List _list, Class _classOfT, DaoProxyType _proxyType) { + List objects = new ArrayList<>(CollectionUtils.size(_list)); + for (Object object : CollectionUtils.makeNotNull(_list)) { + if (_classOfT.isInstance(object)) + objects.add(_classOfT.cast(object)); + else if (object instanceof Document) + objects.add(fromDaoEntity(new DaoEntity((Document) object), _classOfT, _proxyType)); + else if (object instanceof DaoEntity) + objects.add(fromDaoEntity((DaoEntity) object, _classOfT, _proxyType)); + } + return objects; + } + + public static List getSerializableFields(Class _class) { + return getSerializableFields(_class, false); + } + + public static List getSerializableFields(Class _class, boolean _serializeObjects) { + List fields = new ArrayList(); + addSerializableFields(_class, fields, _serializeObjects); + return fields; + } + + private static void addSerializableFields(Class _class, List _fields, boolean _serializeObjects) { + if (_class == null) { + return; + } + for (Field field : _class.getDeclaredFields()) { + if (AbstractDaoSerializer.isSerializable(field, _serializeObjects)) + _fields.add(field); + } + addSerializableFields(_class.getSuperclass(), _fields, _serializeObjects); + } + + public static Set getIndexedFields(Class _class) { + return getIndexedFields(_class, null); + } + + public static Set getHashIndexFields(Class _class) { + return getIndexedFields(_class, true); + } + + public static Set getRangeIndexFields(Class _class) { + return getIndexedFields(_class, false); + } + + private static Set getIndexedFields(Class _class, Boolean _hash) { + Set indexedFields = new HashSet(); + DBSerializable def = _class.getAnnotation(DBSerializable.class); + if (def != null) { + for (DBIndex index : def.indexes()) { + if ((_hash == null) || (_hash == index.hash())) { + Collections.addAll(indexedFields, index.columns()); + } + } + } + return indexedFields; + } + + public static boolean isAnnotationPresent(Class _class, Class _fieldAnnotation) { + if (_class == null) + return false; + if (_class.isAnnotationPresent(_fieldAnnotation)) + return true; + return isAnnotationPresent(_class.getSuperclass(), _fieldAnnotation); + } + + public static T getAnnotation(Class _class, Class _fieldAnnotation) { + if (_class == null) + return null; + T a = _class.getAnnotation(_fieldAnnotation); + if (a != null) + return a; + return getAnnotation(_class.getSuperclass(), _fieldAnnotation); + } + + public static String toJson(Object _o) { + return toJson(_o, true); + } + + public static String toSingleLineJson(Object _o) { + return toJson(toDaoEntity(_o), true, false); + } + + public static String toSingleLineJson(DaoEntity _e) { + return toJson(_e, true, false); + } + + public static byte[] toZipBson(Object _o) { + return toZipBson(toDaoEntity(_o)); + } + + public static byte[] toZipBson(DaoEntity _entity) { + if (_entity == null) + return null; + return ZipUtils.zip(toBson(_entity, true)); + } + + public static T fromZipBson(byte[] _btZipBson, Class _class) { + return DaoSerializer.fromDaoEntity(fromZipBson(_btZipBson), _class); + } + + public static DaoEntity fromZipBson(byte[] _btZipBson) { + return fromBson(ZipUtils.unzip(_btZipBson)); + } + + public static T fromBson(byte[] _btBson, Class _class) { + return fromDaoEntity(fromBson(_btBson), _class); + } + + public static DaoEntity fromBson(byte[] _btBson) + { + if (_btBson == null) + return null; + BsonBinaryReader reader = null; + try + { + reader = new BsonBinaryReader(ByteBuffer.wrap(_btBson).order(ByteOrder.LITTLE_ENDIAN)); + Document doc = new DocumentCodec().decode(reader, DecoderContext.builder().build()); + if (doc == null) + return null; + return new DaoEntity(doc); + } + catch (Throwable t) + { + LOG.error("Failed to convert bson to DaoEntity", t); + return null; + } + finally + { + if (reader != null) + reader.close(); + } + } + + public static byte[] toBson(Object _o) { + return toBson(toDaoEntity(_o)); + } + + public static byte[] toBson(Object _o, boolean _removeNulls) { + DaoEntity entity = toDaoEntity(_o); + if (_removeNulls) + removeNulls(entity.values()); + return toBson(entity); + } + + public static byte[] toBson(DaoEntity _entity) { + if (_entity == null) + return null; + BsonBinaryWriter writer = null; + try + { + BasicOutputBuffer buffer = new BasicOutputBuffer(); + writer = new BsonBinaryWriter(buffer); + new DocumentCodec().encode(writer, _entity.toDocument(), EncoderContext.builder().build()); + return buffer.toByteArray(); + } + catch (Throwable t) + { + LOG.error("Failed to convert entity to BSON", t); + return null; + } + finally + { + if (writer != null) + writer.close(); + } + } + + public static String toJson(Object _o, boolean _removeNulls) { + return toJson(toDaoEntity(_o), _removeNulls); + } + + public static String toJson(DaoEntity _e) { + return toJson(_e, true); + } + + public static String toJson(DaoEntity _e, boolean _removeNulls) { + return toJson(_e, _removeNulls, true); + } + + public static String toJson(DaoEntity _e, boolean _removeNulls, boolean _pretty) { + try { + if (_e != null) { + Document doc = _e.toDocument(); + if (_removeNulls) + removeNulls(doc.values()); + JsonWriterSettings.Builder settings = JsonWriterSettings.builder().int64Converter(new Converter() { + @Override + public void convert(Long _long, StrictJsonWriter _writer) { + if (_long != null) + _writer.writeNumber(_long.toString()); + } + }); + return doc.toJson(settings.indent(_pretty).build()); + } + } + catch (Exception e) { + LOG.error("Failed to convert DaoEntity to json", e); + } + return null; + } + + private static void removeNulls(Collection _doc) { + if (_doc == null) + return; + Iterator values = _doc.iterator(); + while (values.hasNext()) { + Object o = values.next(); + if (o == null) + values.remove(); + else if (o instanceof DaoEntity) + removeNulls(((DaoEntity) o).values()); + else if (o instanceof Document) + removeNulls(((Document) o).values()); + else if (o instanceof Collection) { + Collection entities = (Collection) o; + removeNulls(entities); + if (entities.isEmpty()) + values.remove(); + } + } + } + + public static String toJson(Collection _entities) { + StringBuilder b = null; + for (DaoEntity d : CollectionUtils.makeNotNull(_entities)) { + if (b == null) + b = new StringBuilder("["); + else + b.append(","); + b.append(toJson(d)); + } + if (b == null) + return null; + b.append("]"); + return b.toString(); + } + + public static T parse(byte[] _json, Class _class) { + return fromDaoEntity(parse(NullUtils.toString(_json)), _class); + } + + public static T parse(String _json, Class _class) { + return fromDaoEntity(parse(_json), _class); + } + + public static DaoEntity parse(String _json) { + if (NullUtils.isEmpty(_json)) + return null; + try { + return new DaoEntity(Document.parse(_json)); + } + catch (Exception _e) { + LOG.error("Failed to parse json", _e); + return null; + } + } + + public static List parseList(String _json, Class _class) { + return fromList(parseList(_json), _class); + } + + public static List parseList(String _json) { + try { + List entities = new ArrayList<>(); + JsonReader bsonReader = new JsonReader(_json); + for (Object o : new IterableCodec(CodecRegistries.fromProviders(Arrays.asList(new ValueCodecProvider(), new BsonValueCodecProvider(), new DocumentCodecProvider())), new BsonTypeClassMap()).decode(bsonReader, DecoderContext.builder().build())) { + if (o instanceof Document) + entities.add(new DaoEntity((Document) o)); + } + return entities; + } + catch (Exception _e) { + LOG.error("Failed to parse json", _e); + return null; + } + } + + public List getIndexes(Class _class) { + IDaoSerializer serializer = getSerializer(_class); + return (serializer == null) ? new ArrayList() : serializer.getIndexes(); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSerializerException.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSerializerException.java new file mode 100644 index 0000000..1bf2a5c --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSerializerException.java @@ -0,0 +1,15 @@ +package com.lanternsoftware.util.dao; + +public class DaoSerializerException extends RuntimeException { + public DaoSerializerException() { + } + public DaoSerializerException(String message) { + super(message); + } + public DaoSerializerException(String message, Throwable cause) { + super(message, cause); + } + public DaoSerializerException(Throwable cause) { + super(cause); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSort.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSort.java new file mode 100644 index 0000000..043b884 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSort.java @@ -0,0 +1,72 @@ +package com.lanternsoftware.util.dao; + +import java.util.ArrayList; +import java.util.List; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.ITransformer; +import com.lanternsoftware.util.dao.annotations.CaseFormat; + +public class DaoSort { + private final List fields = new ArrayList(); + + public DaoSort then(String _field) { + return then(_field, true); + } + + public DaoSort thenIgnoreCase(String _field) { + return then(_field, true, true); + } + + public DaoSort thenDesc(String _field) { + return then(_field, false); + } + + public DaoSort thenDescIgnoreCase(String _field) { + return then(_field, false, true); + } + + public DaoSort then(String _field, boolean _ascending) { + return then(_field, _ascending, false); + } + + public DaoSort then(String _field, boolean _ascending, boolean _ignoreCase) { + fields.add(new DaoSortField(_field, _ascending, false)); + return this; + } + + public static DaoSort sort(String _field) { + return new DaoSort().then(_field); + } + + public static DaoSort sortIgnoreCase(String _field) { + return new DaoSort().thenIgnoreCase(_field); + } + + public static DaoSort sortDesc(String _field) { + return new DaoSort().thenDesc(_field); + } + + public static DaoSort sortDescIgnoreCase(String _field) { + return new DaoSort().thenDescIgnoreCase(_field); + } + + public List getFields() { + return fields; + } + + public static DaoSort fromQueryParams(List _queryParams) { + return fromQueryParams(_queryParams, CaseFormat.CAMEL, CaseFormat.SNAKE); + } + + public static DaoSort fromQueryParams(List _queryParams, final CaseFormat _paramFormat, final CaseFormat _dbFormat) { + DaoSort sort = new DaoSort(); + sort.fields.addAll(CollectionUtils.transform(_queryParams, new ITransformer() { + @Override + public DaoSortField transform(String _s) { + return DaoSortField.fromQueryParam(_s, _paramFormat, _dbFormat); + } + })); + return sort; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSortField.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSortField.java new file mode 100644 index 0000000..b5f5b53 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/DaoSortField.java @@ -0,0 +1,53 @@ +package com.lanternsoftware.util.dao; + +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.annotations.CaseFormat; + +public class DaoSortField { + private String field; + private boolean ascending; + private boolean ignoreCase; + + public DaoSortField() { + } + + public DaoSortField(String _field, boolean _ascending, boolean _ignoreCase) { + field = _field; + ascending = _ascending; + } + + public String getField() { + return field; + } + + public void setField(String _field) { + field = _field; + } + + public boolean isAscending() { + return ascending; + } + + public void setAscending(boolean _ascending) { + ascending = _ascending; + } + + public boolean isIgnoreCase() { + return ignoreCase; + } + + public void setIgnoreCase(boolean _ignoreCase) { + ignoreCase = _ignoreCase; + } + + public static DaoSortField fromQueryParam(String _param) { + return fromQueryParam(_param, CaseFormat.CAMEL, CaseFormat.SNAKE); + } + + public static DaoSortField fromQueryParam(String _param, CaseFormat _paramFormat, CaseFormat _dbFormat) { + if (NullUtils.isEmpty(_param)) + return null; + String[] parts = _param.split(","); + return new DaoSortField(AbstractDaoSerializer.convertCase(parts[0], _paramFormat, _dbFormat), !(parts.length > 1 && NullUtils.isEqual(parts[1], "desc")), false); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/EntityPreparer.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/EntityPreparer.java new file mode 100644 index 0000000..fe17bfd --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/EntityPreparer.java @@ -0,0 +1,16 @@ +package com.lanternsoftware.util.dao; + +import java.util.Collection; + +import com.lanternsoftware.util.CollectionUtils; + +public abstract class EntityPreparer { + public Collection prepareEntities(Collection _entities) { + for (DaoEntity entity : CollectionUtils.makeNotNull(_entities)) { + prepareEntity(entity); + } + return _entities; + } + + public abstract void prepareEntity(DaoEntity _entity); +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/IDaoProxy.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/IDaoProxy.java new file mode 100644 index 0000000..e29d59f --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/IDaoProxy.java @@ -0,0 +1,66 @@ +package com.lanternsoftware.util.dao; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +public interface IDaoProxy { + void shutdown(); + DaoProxyType getType(); + List queryAll(Class _class); + T queryOne(Class _class, DaoQuery _query); + T queryOne(Class _class, DaoQuery _query, DaoSort _sort); + T queryOne(Class _class, DaoQuery _query, Collection _fields); + T queryOne(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort); + Future queryOneAsync(Class _class, DaoQuery _query); + Future queryOneAsync(Class _class, DaoQuery _query, DaoSort _sort); + Future queryOneAsync(Class _class, DaoQuery _query, Collection _fields); + Future queryOneAsync(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort); + List query(Class _class, DaoQuery _query); + List query(Class _class, DaoQuery _query, DaoSort _sort); + List query(Class _class, DaoQuery _query, Collection _fields); + List query(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort); + Future> queryAsync(Class _class, DaoQuery _query); + Future> queryAsync(Class _class, DaoQuery _query, DaoSort _sort); + Future> queryAsync(Class _class, DaoQuery _query, Collection _fields); + Future> queryAsync(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort); + Future> queryWithFinalizer(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort, QueryFinalizer _finalizer); + List query(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count); + DaoPage queryPage(Class _class, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count); + List queryImportant(Class _class, DaoQuery _query); + List queryImportant(Class _class, DaoQuery _query, DaoSort _sort); + Future> queryImportantAsync(Class _class, DaoQuery _query); + Future> queryImportantAsync(Class _class, DaoQuery _query, DaoSort _sort); + List queryImportant(Class _class, DaoQuery _query, DaoSort _sort, int _offset, int _count); + DaoPage queryImportantPage(Class _class, DaoQuery _query, DaoSort _sort, int _offset, int _count); + DaoEntity queryForEntity(String _tableName, DaoQuery _query); + DaoEntity queryForEntity(String _tableName, DaoQuery _query, DaoSort _sort); + DaoEntity queryForEntity(String _tableName, DaoQuery _query, Collection _fields); + DaoEntity queryForEntity(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort); + DaoPage queryForEntitiesPage(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count); + List queryForEntities(String _tableName, DaoQuery _query); + List queryForEntities(String _tableName, DaoQuery _query, DaoSort _sort); + List queryForEntities(String _tableName, DaoQuery _query, Collection _fields); + List queryForEntities(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort); + List queryForEntities(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count); + String queryForOneField(Class _class, DaoQuery _query, String _field); + List queryForField(Class _class, DaoQuery _query, String _field); + List queryForField(Class _class, DaoQuery _query, String _field, DaoSort _sort); + List queryForField(String _tableName, DaoQuery _query, String _field); + String save(Object _object); + Map save(Collection _objects); + Map save(Class _class, Collection _entities); + void update(Class _class, DaoQuery _query, DaoEntity _changes); + T updateOne(Class _class, DaoQuery _query, DaoEntity _changes); + String saveEntity(Class _class, DaoEntity _entity); + String saveEntity(String _collection, DaoEntity _entity); + boolean delete(Class _class, DaoQuery _query); + boolean delete(String _tableName, DaoQuery _query); + int count(Class _class, DaoQuery _query); + int count(String _tableName, DaoQuery _query); + boolean exists(Class _class, DaoQuery _query); + boolean exists(String _tableName, DaoQuery _query); + void setExecutor(ExecutorService _executor); +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/IDaoSerializer.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/IDaoSerializer.java new file mode 100644 index 0000000..3de3d09 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/IDaoSerializer.java @@ -0,0 +1,16 @@ +package com.lanternsoftware.util.dao; + +import java.lang.annotation.Annotation; +import java.util.List; + +public interface IDaoSerializer { + Class getSupportedClass(); + String getTableName(); + List getFieldsByAnnotation(Class _fieldAnnotation); + List getImportantFields(); + int getSqlType(String _fieldName); + DaoEntity toDaoEntity(T _t); + T fromDaoEntity(DaoEntity _entity); + List getSupportedProxies(); + List getIndexes(); +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryExecution.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryExecution.java new file mode 100644 index 0000000..8e32e48 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryExecution.java @@ -0,0 +1,38 @@ +package com.lanternsoftware.util.dao; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; + +public class QueryExecution implements Callable> { + private final IDaoProxy proxy; + private final Class clazz; + private final DaoQuery query; + private final Collection fields; + private final DaoSort sort; + + public QueryExecution(IDaoProxy _proxy, Class _class, DaoQuery _query) { + this(_proxy, _class, _query, null, null); + } + + public QueryExecution(IDaoProxy _proxy, Class _class, DaoQuery _query, DaoSort _sort) { + this(_proxy, _class, _query, null, _sort); + } + + public QueryExecution(IDaoProxy _proxy, Class _class, DaoQuery _query, Collection _fields) { + this(_proxy, _class, _query, _fields, null); + } + + public QueryExecution(IDaoProxy _proxy, Class _class, DaoQuery _query, Collection _fields, DaoSort _sort) { + proxy = _proxy; + clazz = _class; + query = _query; + fields = _fields; + sort = _sort; + } + + @Override + public List call() throws Exception { + return proxy.query(clazz, query, fields, sort); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryFinalizer.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryFinalizer.java new file mode 100644 index 0000000..27bf77a --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryFinalizer.java @@ -0,0 +1,7 @@ +package com.lanternsoftware.util.dao; + +import java.util.List; + +public abstract class QueryFinalizer { + public abstract List finalize(IDaoProxy _proxy, List _input); +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryFinalizerExecution.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryFinalizerExecution.java new file mode 100644 index 0000000..3d253c0 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryFinalizerExecution.java @@ -0,0 +1,28 @@ +package com.lanternsoftware.util.dao; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; + +public class QueryFinalizerExecution implements Callable> { + private final IDaoProxy proxy; + private final Class clazz; + private final DaoQuery query; + private final Collection fields; + private final DaoSort sort; + private final QueryFinalizer finalizer; + + public QueryFinalizerExecution(IDaoProxy _proxy, Class _class, DaoQuery _query, Collection _fields, DaoSort _sort, QueryFinalizer _finalizer) { + proxy = _proxy; + clazz = _class; + query = _query; + fields = _fields; + sort = _sort; + finalizer = _finalizer; + } + + @Override + public List call() { + return finalizer.finalize(proxy, proxy.query(clazz, query, fields, sort)); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryOneExecution.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryOneExecution.java new file mode 100644 index 0000000..c37883a --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryOneExecution.java @@ -0,0 +1,37 @@ +package com.lanternsoftware.util.dao; + +import java.util.Collection; +import java.util.concurrent.Callable; + +public class QueryOneExecution implements Callable { + private final IDaoProxy proxy; + private final Class clazz; + private final DaoQuery query; + private final Collection fields; + private final DaoSort sort; + + public QueryOneExecution(IDaoProxy _proxy, Class _class, DaoQuery _query) { + this(_proxy, _class, _query, null, null); + } + + public QueryOneExecution(IDaoProxy _proxy, Class _class, DaoQuery _query, DaoSort _sort) { + this(_proxy, _class, _query, null, _sort); + } + + public QueryOneExecution(IDaoProxy _proxy, Class _class, DaoQuery _query, Collection _fields) { + this(_proxy, _class, _query, _fields, null); + } + + public QueryOneExecution(IDaoProxy _proxy, Class _class, DaoQuery _query, Collection _fields, DaoSort _sort) { + proxy = _proxy; + clazz = _class; + query = _query; + fields = _fields; + sort = _sort; + } + + @Override + public V call() throws Exception { + return proxy.queryOne(clazz, query, fields, sort); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryPreparer.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryPreparer.java new file mode 100644 index 0000000..f764cd9 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/QueryPreparer.java @@ -0,0 +1,50 @@ +package com.lanternsoftware.util.dao; + +import java.util.Map; +import java.util.Map.Entry; + +import com.lanternsoftware.util.dao.annotations.CaseFormat; + +public class QueryPreparer { + private final CaseFormat queryCaseFormat; + private final CaseFormat dbCaseFormat; + private final Map fieldReplacements; + private final Map fieldSuffixExceptions; + + public QueryPreparer(CaseFormat _queryCaseFormat, CaseFormat _dbCaseFormat) { + this(_queryCaseFormat, _dbCaseFormat, null); + } + + public QueryPreparer(CaseFormat _queryCaseFormat, CaseFormat _dbCaseFormat, Map _fieldReplacements) { + this(_queryCaseFormat, _dbCaseFormat, _fieldReplacements, null); + } + + public QueryPreparer(CaseFormat _queryCaseFormat, CaseFormat _dbCaseFormat, Map _fieldReplacements, Map _fieldSuffixExceptions) { + queryCaseFormat = _queryCaseFormat; + dbCaseFormat = _dbCaseFormat; + fieldReplacements = _fieldReplacements; + fieldSuffixExceptions = _fieldSuffixExceptions; + } + + public DaoQuery prepareQuery(DaoQuery _query) { + DaoQuery query = new DaoQuery(); + for (Entry e : _query.entrySet()) { + if (fieldReplacements != null) { + String rep = fieldReplacements.get(e.getKey()); + if (rep != null) { + query.put(rep, e.getValue()); + continue; + } + } + String field = AbstractDaoSerializer.convertCase(e.getKey(), queryCaseFormat, dbCaseFormat); + if (fieldSuffixExceptions != null) { + for (Entry entry : fieldSuffixExceptions.entrySet()) { + if (field.endsWith(entry.getKey())) + field = field.substring(0, field.length()-entry.getKey().length()) + entry.getValue(); + } + } + query.put(field, e.getValue()); + } + return query; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/CaseFormat.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/CaseFormat.java new file mode 100644 index 0000000..6310120 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/CaseFormat.java @@ -0,0 +1,7 @@ +package com.lanternsoftware.util.dao.annotations; + +public enum CaseFormat { + SNAKE, + CAMEL, + PASCAL +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBClob.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBClob.java new file mode 100644 index 0000000..cdd9ef1 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBClob.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DBClob { +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBIgnore.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBIgnore.java new file mode 100644 index 0000000..8462e34 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBIgnore.java @@ -0,0 +1,12 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DBIgnore { + String name() default ""; +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBIndex.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBIndex.java new file mode 100644 index 0000000..f5e6b05 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBIndex.java @@ -0,0 +1,14 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DBIndex { + String[] columns(); + boolean hash() default false; +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBName.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBName.java new file mode 100644 index 0000000..1615c77 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBName.java @@ -0,0 +1,12 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DBName { + String name() default ""; +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBSerializable.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBSerializable.java new file mode 100644 index 0000000..f29b96e --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBSerializable.java @@ -0,0 +1,16 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DBSerializable { + String name() default ""; + String seq() default ""; + CaseFormat caseFormat() default CaseFormat.SNAKE; + DBIndex[] indexes() default {}; + boolean autogen() default true; +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBType.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBType.java new file mode 100644 index 0000000..73b0dc1 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/DBType.java @@ -0,0 +1,12 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DBType { + Class type() default String.class; +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/Important.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/Important.java new file mode 100644 index 0000000..f1b2604 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/Important.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Important { +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/NeverUpdate.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/NeverUpdate.java new file mode 100644 index 0000000..472c6c7 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/NeverUpdate.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface NeverUpdate { +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/PrimaryKey.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/PrimaryKey.java new file mode 100644 index 0000000..5cad963 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/PrimaryKey.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface PrimaryKey { +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/StringDates.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/StringDates.java new file mode 100644 index 0000000..fdd1977 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/StringDates.java @@ -0,0 +1,12 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface StringDates { + String format() default "yyyy-MM-dd'T'hh:mm:ss.SSS"; +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/TimestampDates.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/TimestampDates.java new file mode 100644 index 0000000..53f4273 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/TimestampDates.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface TimestampDates { +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/Unimportant.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/Unimportant.java new file mode 100644 index 0000000..dc491e8 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/annotations/Unimportant.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.util.dao.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Unimportant { +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/csv/CSVStream.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/csv/CSVStream.java new file mode 100644 index 0000000..ddf1e03 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/csv/CSVStream.java @@ -0,0 +1,137 @@ +package com.lanternsoftware.util.dao.csv; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.ITransformer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.IDaoSerializer; + +public abstract class CSVStream { + protected static final Logger LOG = LoggerFactory.getLogger(CSVStream.class); + + public static Iterator parse(InputStream _is, Class _class) { + return new CSVIterator(_is, DaoSerializer.getSerializer(_class)); + } + + public static Iterator parse(InputStream _is, Class _class, DaoEntity _entity) { + return new CSVIterator(_is, DaoSerializer.getSerializer(_class), _entity); + } + + public static Iterator parse(InputStream _is, Class _class, ITransformer _headerTransformer) { + return new CSVIterator(_is, DaoSerializer.getSerializer(_class), _headerTransformer); + } + + public static Iterator parse(InputStream _is, Class _class, DaoEntity _entity, ITransformer _headerTransformer) { + return new CSVIterator(_is, DaoSerializer.getSerializer(_class), _entity, _headerTransformer); + } + + public static Iterator parse(InputStream _is, IDaoSerializer _serializer) { + return new CSVIterator(_is, _serializer); + } + + public static Iterator parse(InputStream _is, IDaoSerializer _serializer, DaoEntity _entity) { + return new CSVIterator(_is, _serializer, _entity); + } + + public static Iterator parse(InputStream _is, IDaoSerializer _serializer, ITransformer _headerTransformer) { + return new CSVIterator(_is, _serializer, _headerTransformer); + } + + public static Iterator parse(InputStream _is, IDaoSerializer _serializer, DaoEntity _entity, ITransformer _headerTransformer) { + return new CSVIterator(_is, _serializer, _entity, _headerTransformer); + } + + private static class CSVIterator implements Iterator { + private final BufferedReader reader; + private final String[] headers; + private final IDaoSerializer serializer; + private final DaoEntity metadata; + private String[] line = null; + + public CSVIterator(InputStream _is, IDaoSerializer _serializer) { + this(_is, _serializer, null, null); + } + + public CSVIterator(InputStream _is, IDaoSerializer _serializer, DaoEntity _metadata) { + this(_is, _serializer, _metadata, null); + } + + public CSVIterator(InputStream _is, IDaoSerializer _serializer, ITransformer _headerTransformer) { + this(_is, _serializer, null, _headerTransformer); + } + + public CSVIterator(InputStream _is, IDaoSerializer _serializer, DaoEntity _metadata, ITransformer _headerTransformer){ + reader = new BufferedReader(new InputStreamReader(_is)); + headers = line(); + if ((_headerTransformer != null) && (headers != null)) { + for (int i=0; i> entryset = metadata.entrySet(); + for (Map.Entry entry : entryset) { + entity.put(entry.getKey(), entry.getValue()); + } + } + for (int i = 0; i < headers.length; i++) { + entity.put(headers[i], CollectionUtils.get(line, i)); + } + T t; + try { + t = serializer.fromDaoEntity(entity); + } + catch (RuntimeException e) { + line = line(); + throw e; + } + line = line(); + return t; + } + + @Override + public void remove() { + } + + private String[] line() { + try { + String line = reader.readLine(); + if (line == null) + return null; + return line.split("\\s*,\\s*"); + } catch (IOException _e) { + LOG.error("Failed to parse CSV", _e); + return null; + } + } + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/DaoSerializerGenerator.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/DaoSerializerGenerator.java new file mode 100644 index 0000000..780145b --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/DaoSerializerGenerator.java @@ -0,0 +1,379 @@ +package com.lanternsoftware.util.dao.generator; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.AnnotationFinder; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.annotations.CaseFormat; +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; +import com.lanternsoftware.util.dao.annotations.StringDates; +import com.lanternsoftware.util.dao.annotations.TimestampDates; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.DateUtils; +import com.lanternsoftware.util.ITransformer; +import com.lanternsoftware.util.NullUtils; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DaoSerializerGenerator { + private static final Logger LOG = LoggerFactory.getLogger(DaoSerializerGenerator.class); + + public static void generateSerializers(String _codePath) { + generateSerializers(_codePath, false, null); + } + + public static void generateSerializers(String _codePath, boolean _serializeNestedObjects, List _proxyTypes) { + generateSerializers(_codePath, _serializeNestedObjects, _proxyTypes, true); + } + + public static void generateSerializers(String _codePath, boolean _serializeNestedObjects, List _proxyTypes, boolean _generateSpiFile) { + if (CollectionUtils.isEmpty(_proxyTypes)) + _proxyTypes = Collections.singletonList(DaoProxyType.MONGO); + Map> serializers = new HashMap<>(); + for (Map.Entry e : AnnotationFinder.findAnnotatedClasses(_codePath, DBSerializable.class).entrySet()) { + SerializerGenerationResult result = generateSerializer(e.getKey(), e.getValue() + File.separator + "dao" + File.separator, _serializeNestedObjects, null, _proxyTypes, null); + if (result == null) + continue; + int idx = e.getValue().indexOf(File.separator + "src" + File.separator); + if (idx > -1) + CollectionUtils.addToMultiMap(e.getValue().substring(0, idx + 9) + File.separator + "resources" + File.separator + "META-INF" + File.separator + "services" + File.separator, result.getClassName(), serializers); + } + if (_generateSpiFile) { + for (Entry> entry : serializers.entrySet()) { + new File(entry.getKey()).mkdirs(); + FileOutputStream f = null; + try { + f = new FileOutputStream(entry.getKey() + "com.lanternsoftware.util.dao.IDaoSerializer"); + Collections.sort(entry.getValue()); + for (String className : entry.getValue()) { + f.write(NullUtils.toByteArray(className)); + f.write(NullUtils.toByteArray("\r\n")); + } + } + catch (Exception e) { + LOG.error("Failed to create service loader file", e); + } + finally { + IOUtils.closeQuietly(f); + } + } + } + } + + /** + * @param _className + * The name of the class to be serialized + * @param _outputPath + * The path to write the generated serializer to + * @param _serializeNestedObjects + * if true, the serializer will save a hierarchical structure containing all sub objects. If false, it only serializes primitives + * @param _fieldNameExceptions + * a mapping of standard names to actual names to handle special cases where existing objects aren't following proper naming conventions + * @param _intendedProxyTypes + * a list of proxy types that can use this generated serializer (can pass null if it should be used for all proxies) + * @param _primaryKey + * The database primary key field. For Mongo, this will be changed to "_id" in the serializer. + * @return The class name of the generated serializer + */ + public static SerializerGenerationResult generateSerializer(String _className, String _outputPath, boolean _serializeNestedObjects, Map _fieldNameExceptions, List _intendedProxyTypes, String _primaryKey) { + try { + Set> customSerializerFields = new HashSet<>(); + Class clazz = Class.forName(_className); + String packagePath; + int srcPos = _outputPath.indexOf(File.separator + "java" + File.separator); + if (srcPos > -1) { + packagePath = _outputPath.substring(srcPos + 6).replace(File.separator, "."); + if (packagePath.endsWith(".")) + packagePath = packagePath.substring(0, packagePath.length() - 1); + } + else + packagePath = clazz.getPackage().getName() + ".dao"; + StringBuilder serializer = new StringBuilder(); + serializer.append("package "); + serializer.append(packagePath); + serializer.append(";\n\n"); + + Set> imports = CollectionUtils.asHashSet(AbstractDaoSerializer.class, DaoEntity.class, DaoSerializer.class, clazz); + if (CollectionUtils.isNotEmpty(_intendedProxyTypes)) { + imports.add(DaoProxyType.class); + imports.add(List.class); + if (_intendedProxyTypes.size() > 1) + imports.add(Arrays.class); + else + imports.add(Collections.class); + } + + Map> mapFields = new HashMap<>(); + List fields = DaoSerializer.getSerializableFields(clazz, _serializeNestedObjects); + for (Field f : fields) { + for (Annotation a : f.getAnnotations()) { + CollectionUtils.addToMultiMap(a.annotationType().getCanonicalName(), f, mapFields); + } + Class type = AbstractDaoSerializer.getType(f); + if (Collection.class.isAssignableFrom(type)) { + Class elementClass = ((Class) (((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0])); + imports.add(elementClass); + } + else if (type.isEnum() || AbstractDaoSerializer.requiresCustomSerializer(f)) + imports.add(type); + } + CaseFormat caseFormat = CaseFormat.SNAKE; + DBSerializable dbSerializable = DaoSerializer.getAnnotation(clazz, DBSerializable.class); + if ((dbSerializable != null) && (dbSerializable.caseFormat() != null)) + caseFormat = dbSerializable.caseFormat(); + StringDates dateFormat = DaoSerializer.getAnnotation(clazz, StringDates.class); + if (dateFormat != null) + imports.add(DateUtils.class); + + Field primaryKey = CollectionUtils.getFirst(mapFields.get(PrimaryKey.class.getCanonicalName())); + if (primaryKey != null) { + if (primaryKey.getType() != String.class) + imports.add(NullUtils.class); + if (NullUtils.isEmpty(_primaryKey)) + _primaryKey = AbstractDaoSerializer.fieldToDatabaseName(primaryKey, caseFormat); + } + + List imp = CollectionUtils.transform(imports, new ITransformer, String>() { + @Override + public String transform(Class _class) { + return "import " + _class.getCanonicalName() + ";\n"; + } + }); + Collections.sort(imp); + for (String i : imp) { + serializer.append(i); + } + serializer.append("\npublic class "); + serializer.append(clazz.getSimpleName()); + serializer.append("Serializer extends AbstractDaoSerializer<"); + serializer.append(clazz.getSimpleName()); + serializer.append(">\n{\n"); + if (dateFormat != null) { + serializer.append("\tprivate static final String FORMAT = \""); + serializer.append(dateFormat.format()); + serializer.append("\";\n\n"); + } + serializer.append("\t@Override\n\tpublic Class<"); + serializer.append(clazz.getSimpleName()); + serializer.append("> getSupportedClass()\n\t{\n\t\treturn "); + serializer.append(clazz.getSimpleName()); + serializer.append(".class;\n\t}\n\n"); + String intendedType = "DaoProxyType." + CollectionUtils.getFirst(_intendedProxyTypes).name(); + if (CollectionUtils.isNotEmpty(_intendedProxyTypes)) { + serializer.append("\t@Override\n\tpublic List getSupportedProxies() {\n\t\t"); + if (_intendedProxyTypes.size() > 1) { + serializer.append("return Arrays.asList("); + serializer.append(CollectionUtils.transformToCommaSeparated(_intendedProxyTypes, new ITransformer() { + @Override + public String transform(DaoProxyType _daoProxyType) { + return "DaoProxyType." + _daoProxyType.name(); + } + })); + serializer.append(");\n\t}\n"); + } + else { + serializer.append("return Collections.singletonList("); + serializer.append(intendedType); + serializer.append(");\n\t}\n\n"); + } + } + serializer.append("\t@Override\n\tpublic DaoEntity toDaoEntity("); + serializer.append(clazz.getSimpleName()); + serializer.append(" _o)\n\t{\n\t\tDaoEntity d = new DaoEntity("); + serializer.append(");\n"); + + StringBuilder from = new StringBuilder(); + from.append("\t@Override\n\tpublic "); + from.append(clazz.getSimpleName()); + from.append(" fromDaoEntity(DaoEntity _d)\n\t{\n\t\t"); + from.append(clazz.getSimpleName()); + from.append(" o = new "); + from.append(clazz.getSimpleName()); + from.append("();\n"); + for (Field f : fields) { + String databaseField = AbstractDaoSerializer.fieldToDatabaseName(f, caseFormat); + boolean customSerializer = AbstractDaoSerializer.requiresCustomSerializer(f); + Class type = AbstractDaoSerializer.getType(f); + String classField = AbstractDaoSerializer.fieldToGetterName(f); + if (_fieldNameExceptions != null) { + String override = _fieldNameExceptions.get(classField); + if (NullUtils.isNotEmpty(override)) + classField = override; + } + Class collType = null; + if (Collection.class.isAssignableFrom(type)) + collType = ((Class) (((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0])); + + boolean mongoPrimaryKey = NullUtils.isEqual(databaseField, _primaryKey) && _intendedProxyTypes.contains(DaoProxyType.MONGO); + if (mongoPrimaryKey) { + databaseField = "_id"; + serializer.append("\t\tif (_o.get"); + serializer.append(classField); + serializer.append("() != null)"); + serializer.append("\n\t\t\td.put(\""); + serializer.append(databaseField); + serializer.append("\", _o.get"); + serializer.append(classField); + from.append("\t\to.set"); + from.append(classField); + serializer.append("());\n"); + from.append("(DaoSerializer.getString(_d, \""); + from.append(databaseField); + from.append("\"));\n"); + } + else { + serializer.append("\t\td.put(\""); + serializer.append(databaseField); + if (customSerializer) { + if (Collection.class.isAssignableFrom(type)) { + serializer.append("\", DaoSerializer.toDaoEntities(_o.get"); + serializer.append(classField); + customSerializerFields.add(collType); + } + else { + serializer.append("\", DaoSerializer.toDaoEntity(_o.get"); + customSerializerFields.add(type); + serializer.append(classField); + } + if (NullUtils.isEmpty(intendedType)) + serializer.append("()));\n"); + else { + serializer.append("(), "); + serializer.append(intendedType); + serializer.append("));\n"); + } + } + else if (type.equals(Date.class)) { + if (dateFormat != null) { + serializer.append("\", DateUtils.format(FORMAT, _o.get"); + } + else if (DaoSerializer.isAnnotationPresent(clazz, TimestampDates.class)) + serializer.append("\", DaoSerializer.toTimestamp(_o.get"); + else + serializer.append("\", DaoSerializer.toLong(_o.get"); + serializer.append(classField); + serializer.append("()));\n"); + } + else if (type.isEnum()) { + serializer.append("\", DaoSerializer.toEnumName(_o.get"); + serializer.append(classField); + serializer.append("()));\n"); + } + else { + if (type.getName().equals("boolean")) { + serializer.append("\", _o.is"); + } + else { + serializer.append("\", _o.get"); + } + serializer.append(classField); + serializer.append("());\n"); + } + from.append("\t\to.set"); + from.append(classField); + + from.append("(DaoSerializer.get"); + if (type.getName().equals("int")) { + from.append("Integer"); + } + else if (type.equals(Date.class)) { + from.append("Date"); + } + else if (type.equals(String.class)) { + from.append("String"); + } + else if (type.equals(BigDecimal.class)) { + from.append("BigDecimal"); + } + else if (type.equals(byte[].class)) { + from.append("ByteArray"); + } + else if (type.isEnum()) { + from.append("Enum"); + } + else if (customSerializer) { + if (Collection.class.isAssignableFrom(type)) + from.append("List"); + else + from.append("Object"); + } + else { + from.append(type.getSimpleName().substring(0, 1).toUpperCase()); + from.append(type.getSimpleName().substring(1)); + } + from.append("(_d, \""); + from.append(databaseField); + from.append("\""); + if (type.equals(Date.class) && (dateFormat != null)) + from.append(", FORMAT"); + else if (Collection.class.isAssignableFrom(type)) { + from.append(", "); + from.append(collType.getSimpleName()); + from.append(".class"); + } + else if (type.isEnum() || customSerializer) { + from.append(", "); + from.append(type.getSimpleName()); + from.append(".class"); + } + from.append("));\n"); + } + } + serializer.append("\t\treturn d;\n\t}\n\n"); + serializer.append(from.toString()); + serializer.append("\t\treturn o;\n\t}\n}"); + + FileOutputStream f = null; + try { + if ((dbSerializable == null) || dbSerializable.autogen()) { + new File(_outputPath).mkdirs(); + f = new FileOutputStream(_outputPath + clazz.getSimpleName() + "Serializer.java"); + f.write(NullUtils.toByteArray(serializer.toString())); + } + return new SerializerGenerationResult(packagePath + "." + clazz.getSimpleName() + "Serializer", customSerializerFields); + } + catch (Exception e) { + LOG.error("Failed to write serializer", e); + return null; + } + finally { + IOUtils.closeQuietly(f); + } + } + catch (ClassNotFoundException e) { + return null; + } + } + + private static boolean doesImplement(Class _class, String _interfaceName) { + if ((_class == null) || _class.equals(Object.class)) + return false; + for (Class intf : _class.getInterfaces()) { + if (intf.getSimpleName().equals(_interfaceName)) + return true; + if (doesImplement(intf, _interfaceName)) + return true; + } + return doesImplement(_class.getSuperclass(), _interfaceName); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SchemaGenerator.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SchemaGenerator.java new file mode 100644 index 0000000..2047eff --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SchemaGenerator.java @@ -0,0 +1,91 @@ +package com.lanternsoftware.util.dao.generator; + +import java.lang.reflect.Field; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.AnnotationFinder; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.annotations.DBClob; +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; +import com.lanternsoftware.util.dao.annotations.TimestampDates; + +public class SchemaGenerator { + public static void generateSchema(String _sourceCodeFolder) { + Map classes = AnnotationFinder.findAnnotatedClasses(_sourceCodeFolder, DBSerializable.class); + for (String className : classes.keySet()) { + try { + Class clazz = Class.forName(className); + if ((clazz == null) || !DaoSerializer.isAnnotationPresent(clazz, DBSerializable.class)) + continue; + System.out.println(generateTableCreateStatement(clazz)); + } + catch (ClassNotFoundException _e) { + } + } + } + + public static String generateTableCreateStatement(Class _entity) { + StringBuilder sql = new StringBuilder("CREATE TABLE "); + boolean timestampDates = _entity.getAnnotation(TimestampDates.class) != null; + String tableName = DaoSerializer.getTableName(_entity); + sql.append(tableName); + sql.append(" ("); + boolean bFirst = true; + List keys = DaoSerializer.getFieldsByAnnotation(_entity, PrimaryKey.class); + for (Field f : DaoSerializer.getSerializableFields(_entity)) { + String name = AbstractDaoSerializer.fieldToDatabaseName(f); + if (name == null) + continue; + StringBuilder col = new StringBuilder(name); + col.append(" "); + if (NullUtils.isOneOf(f.getType(), Byte.TYPE, byte.class)) + col.append("NUMBER(3,0)"); + else if (NullUtils.isOneOf(f.getType(), Short.TYPE, Short.class)) + col.append("NUMBER(5,0)"); + else if (NullUtils.isOneOf(f.getType(), Integer.TYPE, Integer.class)) + col.append("NUMBER(10,0)"); + else if (NullUtils.isOneOf(f.getType(), Long.TYPE, Long.class)) + col.append("NUMBER(19,0)"); + else if (NullUtils.isOneOf(f.getType(), Double.TYPE, Double.class, Float.TYPE, Float.class)) + col.append("NUMBER(19,4)"); + else if (NullUtils.isOneOf(f.getType(), Boolean.TYPE, Boolean.class)) + col.append("NUMBER(1,0)"); + else if (f.getType().equals(String.class) || f.getType().isEnum()) { + if (f.getAnnotation(DBClob.class) != null) + col.append("CLOB"); + else + col.append("VARCHAR(255)"); + } + else if (f.getType().equals(Date.class)) { + if (timestampDates) + col.append("TIMESTAMP"); + else + col.append("NUMBER(19,0)"); + } + else + continue; + if ((f.getAnnotation(PrimaryKey.class) != null) && (keys.size() == 1)) + col.append(" PRIMARY KEY"); + if (!bFirst) + sql.append(","); + else + bFirst = false; + sql.append(col); + } + if (keys.size() > 1) { + sql.append(", CONSTRAINT "); + sql.append(tableName); + sql.append("_pk PRIMARY KEY ("); + sql.append(CollectionUtils.commaSeparated(keys)); + sql.append(")"); + } + sql.append(");"); + return sql.toString(); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SerializerGenerationResult.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SerializerGenerationResult.java new file mode 100644 index 0000000..9d8e1cd --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SerializerGenerationResult.java @@ -0,0 +1,21 @@ +package com.lanternsoftware.util.dao.generator; + +import java.util.Set; + +public class SerializerGenerationResult { + private final String className; + private final Set> fieldsNeedingCustomSerializers; + + public SerializerGenerationResult(String _className, Set> _fieldsNeedingCustomSerializers) { + className = _className; + fieldsNeedingCustomSerializers = _fieldsNeedingCustomSerializers; + } + + public String getClassName() { + return className; + } + + public Set> getFieldsNeedingCustomSerializers() { + return fieldsNeedingCustomSerializers; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SwiftModelGenerator.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SwiftModelGenerator.java new file mode 100644 index 0000000..83d668b --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/generator/SwiftModelGenerator.java @@ -0,0 +1,200 @@ +package com.lanternsoftware.util.dao.generator; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.AnnotationFinder; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.annotations.CaseFormat; +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +public class SwiftModelGenerator { + private static final Logger LOG = LoggerFactory.getLogger(SwiftModelGenerator.class); + + public static void generateModel(String _codePath, String _outputPath) { + for (Entry e : AnnotationFinder.findAnnotatedClasses(_codePath, DBSerializable.class).entrySet()) { + generateSerializer(e.getKey(), e.getValue().replace(_codePath, _outputPath) + File.separator + "bson" + File.separator); + } + } + + private static SerializerGenerationResult generateSerializer(String _className, String _outputPath) { + try { + Set> customSerializerFields = new HashSet<>(); + Class clazz = Class.forName(_className); + DBSerializable dbSerializable = DaoSerializer.getAnnotation(clazz, DBSerializable.class); + if (dbSerializable == null) + return null; + CaseFormat caseFormat = dbSerializable.caseFormat(); + + StringBuilder bson = new StringBuilder(); + bson.append("import Foundation\nimport BSON\n\nclass "); + bson.append(clazz.getSimpleName()); + bson.append(":LanternObject {\n"); + + List fields = DaoSerializer.getSerializableFields(clazz, true); + + Map> mapFields = new HashMap<>(); + for (Field f : fields) { + if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) + continue; + for (Annotation a : f.getAnnotations()) { + CollectionUtils.addToMultiMap(a.annotationType().getCanonicalName(), f, mapFields); + } + } + + String primaryKeyName = null; + Field primaryKey = CollectionUtils.getFirst(mapFields.get(PrimaryKey.class.getCanonicalName())); + if (primaryKey != null) { + primaryKeyName = AbstractDaoSerializer.fieldToDatabaseName(primaryKey, caseFormat); + } + + for (Field f : fields) { + bson.append("\tvar "); + bson.append(f.getName()); + bson.append(":"); + bson.append(typeToSwift(f)); + bson.append("?\n"); + } + bson.append("\n\tinit() {}\n\n\trequired init(bson: Document) {\n"); + for (Field f : fields) { + bson.append("\t\tself."); + bson.append(f.getName()); + String databaseName = AbstractDaoSerializer.fieldToDatabaseName(f, caseFormat); + if (NullUtils.isEqual(databaseName, primaryKeyName)) + databaseName = "_id"; + if (AbstractDaoSerializer.getCollectionType(f) != null) { + bson.append(" = BsonUtils.getList(bson:bson, field:\""); + bson.append(databaseName); + bson.append("\")\n"); + } + else if (AbstractDaoSerializer.requiresCustomSerializer(f)) { + bson.append(" = BsonUtils.getObject(bson:bson, field:\""); + bson.append(databaseName); + bson.append("\")\n"); + } + else { + bson.append(" = bson[\""); + bson.append(databaseName); + bson.append("\"] as? "); + bson.append(typeToSwift(f)); + bson.append("\n"); + } + } + bson.append("\t}\n\n\tfunc toBSON()->Document {\n\t\tlet bson: Document = ["); + boolean first = true; + for (Field f : fields) { + if (!first) + bson.append(","); + else + first = false; + String databaseName = AbstractDaoSerializer.fieldToDatabaseName(f, caseFormat); + if (NullUtils.isEqual(databaseName, primaryKeyName)) + databaseName = "_id"; + bson.append("\n\t\t\t\""); + bson.append(databaseName); + bson.append("\": "); + if (AbstractDaoSerializer.getCollectionType(f) != null) { + bson.append("BsonUtils.toDocument(coll:self."); + bson.append(f.getName()); + bson.append(")"); + } + else if (AbstractDaoSerializer.requiresCustomSerializer(f)) { + bson.append("BsonUtils.toDocument(obj:self."); + bson.append(f.getName()); + bson.append(")"); + } + else { + bson.append("self."); + bson.append(f.getName()); + } + } + bson.append("\n\t\t]\n\t\treturn bson\n\t}\n}"); + + FileOutputStream f = null; + try { + // if (dbSerializable.autogen()) { + new File(_outputPath).mkdirs(); + f = new FileOutputStream(_outputPath + clazz.getSimpleName() + ".swift"); + f.write(NullUtils.toByteArray(bson.toString())); + // } + return new SerializerGenerationResult(clazz.getSimpleName() + ".swift", customSerializerFields); + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + finally { + IOUtils.closeQuietly(f); + } + } + catch (ClassNotFoundException e) { + return null; + } + } + + private static String typeToSwift(Field _f) { + if (Collection.class.isAssignableFrom(_f.getType())) + return "[" + typeToSwift(AbstractDaoSerializer.getCollectionType(_f)) + "]"; + return typeToSwift(_f.getType()); + } + + private static String typeToSwift(Class _class) { + if (NullUtils.isOneOf(_class, Integer.TYPE, Integer.class)) + return "Int32"; + if (NullUtils.isOneOf(_class, Long.TYPE, Long.class)) + return "Int64"; + if (NullUtils.isOneOf(_class, Double.TYPE, Double.class)) + return "Double"; + if (NullUtils.isOneOf(_class, Float.TYPE, Float.class)) + return "Float"; + if (NullUtils.isOneOf(_class, Boolean.TYPE, Boolean.class)) + return "Bool"; + if (_class.equals(Date.class)) + return "Date"; + if (_class.equals(String.class)) + return "String"; + if (_class.equals(byte[].class)) + return "[Uint8]"; + if (_class.isEnum()) + return "String"; + if (NullUtils.isOneOf(_class, Short.TYPE, Short.class)) + return "Int16"; + if (NullUtils.isOneOf(_class, Byte.TYPE, Byte.class)) + return "Int8"; + return _class.getSimpleName(); + } + + private static boolean doesImplement(Class _class, String _interfaceName) { + if ((_class == null) || _class.equals(Object.class)) + return false; + for (Class intf : _class.getInterfaces()) { + if (intf.getSimpleName().equals(_interfaceName)) + return true; + if (doesImplement(intf, _interfaceName)) + return true; + } + return doesImplement(_class.getSuperclass(), _interfaceName); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/AbstractJdbcProxy.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/AbstractJdbcProxy.java new file mode 100644 index 0000000..11fd7c4 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/AbstractJdbcProxy.java @@ -0,0 +1,1166 @@ +package com.lanternsoftware.util.dao.jdbc; + +import java.io.InputStream; +import java.io.Reader; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.ITransformer; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.AbstractDaoProxy; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoQuery; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.DaoSort; +import com.lanternsoftware.util.dao.DaoSortField; +import com.lanternsoftware.util.dao.EntityPreparer; +import com.lanternsoftware.util.dao.annotations.DBClob; +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.NeverUpdate; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.PreparedInStatement; + +public abstract class AbstractJdbcProxy extends AbstractDaoProxy { + private static final Logger LOG = LoggerFactory.getLogger(AbstractJdbcProxy.class); + protected final Map insertStatements = new HashMap(); + protected final Map updateStatements = new HashMap(); + protected DatabaseType databaseType = DatabaseType.ORACLE_11G; + protected EntityPreparer entityPreparer; + private int fetchSize = 500; + + @Override + public DaoProxyType getType() { + return DaoProxyType.JDBC; + } + + public abstract Connection getConnection(); + + public abstract boolean isConnected(); + + public abstract boolean alwaysClose(); + + public void setFetchSize(int _fetchSize) { + fetchSize = _fetchSize; + } + + public EntityPreparer getEntityPreparer() { + return entityPreparer; + } + + public void setEntityPreparer(EntityPreparer _entityPreparer) { + entityPreparer = _entityPreparer; + } + + public List query(final Class _class, String _sql, DaoQuery _query) { + return toObjects(queryForEntities(_query, _sql), _class); + } + + @Override + public int count(String _tableName, DaoQuery _query) { + ResultSet rs = null; + PreparedStatement statement = null; + try { + int count = 0; + String sql = buildSQL("SELECT COUNT(*)", _tableName, _query, null, 0, -1); + if (sql.contains("{in}")) { + PreparedInStatement inStatement = getPreparedInStatement(sql); + populateStatement(inStatement, 1, _query); + Connection conn = null; + while (inStatement.hasNext()) { + rs = inStatement.next(); + if (rs.next()) { + count += rs.getInt(1); + } + Statement st = rs.getStatement(); + conn = st.getConnection(); + close(rs); + close(st); + } + close(conn); + } + else { + statement = getPreparedStatement(sql); + populateStatement(statement, 1, _query); + rs = statement.executeQuery(); + if (rs.next()) { + count = rs.getInt(1); + } + } + return count; + } + catch (SQLException e) { + LOG.error("Failed to read count", e); + return 0; + } + finally { + close(rs); + closeStatementAndConnection(statement); + } + } + + @Override + public List queryForEntities(String _tableName, DaoQuery _query, Collection _fields, DaoSort _sort, int _offset, int _count) { + StringBuilder operation = null; + if (CollectionUtils.isEmpty(_fields)) + operation = new StringBuilder("SELECT *"); + else { + for (String sField : _fields) { + if (operation == null) { + operation = new StringBuilder("SELECT "); + } + else { + operation.append(", "); + } + operation.append(sField); + } + } + return queryForEntities(_query, buildSQL(operation.toString(), _tableName, _query, _sort, _offset, _count)); + } + + protected List queryForEntities(DaoQuery _query, String _sql) { + LOG.debug(_sql); + if (_sql.contains("{in}")) { + PreparedInStatement statement = getPreparedInStatement(_sql); + populateStatement(statement, 1, _query); + return toDaoEntities(statement); + } + PreparedStatement statement = getPreparedStatement(_sql); + populateStatement(statement, 1, _query); + return toDaoEntities(statement); + } + + protected String buildSQL(String _sOperation, String _tableName, DaoQuery _object, DaoSort _sort, int _offset, int _count) { + StringBuilder builder = new StringBuilder(_sOperation); + builder.append(" FROM "); + builder.append(_tableName); + if ((_object != null) && !_object.isEmpty()) { + builder.append(" WHERE "); + queryToSQL(_object, builder); + } + if (_sort != null) { + StringBuilder sort = null; + for (DaoSortField sortField : _sort.getFields()) { + if (sort == null) { + sort = new StringBuilder(" ORDER BY "); + } + else { + sort.append(", "); + } + if (sortField.isIgnoreCase()) + sort.append("UPPER("); + sort.append(sortField.getField()); + if (sortField.isIgnoreCase()) + sort.append(")"); + if (!sortField.isAscending()) { + sort.append(" DESC"); + } + } + if (sort != null) { + builder.append(sort); + } + } + if ((_offset == 0) && (_count <= 0)) + return builder.toString(); + if (databaseType == DatabaseType.ORACLE_11G) { + StringBuilder paged = new StringBuilder(); + if (_offset > 0) + paged.append("select * from ("); + paged.append("SELECT t.*, ROWNUM rn FROM ("); + paged.append(builder.toString()); + paged.append(") t"); + if (_count > 0) { + paged.append(" WHERE ROWNUM <= "); + paged.append(_offset + _count); + } + if (_offset > 0) { + paged.append(") WHERE rn > "); + paged.append(_offset); + } + return paged.toString(); + } + else if (databaseType == DatabaseType.MYSQL) { + builder.append(" limit "); + builder.append(_offset); + if (_count > 0) { + builder.append(","); + builder.append(_count); + } + } + return builder.toString(); + } + + protected void queryToSQL(DaoQuery _object, StringBuilder _builder) { + if (_object == null) + return; + Iterator> iterEntries = _object.entrySet().iterator(); + Map.Entry entry = iterEntries.next(); + objectToSQL(entry.getKey(), entry.getValue(), _builder); + while (iterEntries.hasNext()) { + entry = iterEntries.next(); + if(!entry.getKey().equals("$or")) { + _builder.append(" and "); + } + else { + _builder.append(" or ("); + } + objectToSQL(entry.getKey(), entry.getValue(), _builder); + } + } + + protected void objectToSQL(String _sName, Object _value, StringBuilder _builder) { + if (_sName.equals("$or") && (_value instanceof DaoQuery)) { + DaoQuery query = (DaoQuery) _value; + queryToSQL(query, _builder); + _builder.append(")"); + return; + } + if (_sName.equals("$and") && (_value instanceof DaoQuery[])) { + DaoQuery[] values = (DaoQuery[]) _value; + if (values.length > 1) { + queryToSQL(values[0], _builder); + for (int i = 1; i < values.length; i++) { + _builder.append(" and "); + queryToSQL(values[i], _builder); + } + } + } + else if (_value instanceof DaoQuery) { + DaoQuery child = (DaoQuery) _value; + boolean first = true; + if (child.containsKey("$equalIgnoreCase")) { + if (!first) + _builder.append(" and "); + _builder.append("lower("); + _builder.append(_sName); + _builder.append(")"); + _builder.append(" = ?"); + first = false; + } + if (child.containsKey("$ne")) { + if (!first) + _builder.append(" and "); + _builder.append(_sName); + _builder.append(" != ?"); + first = false; + } + if (child.containsKey("$gt")) { + if (!first) + _builder.append(" and "); + _builder.append(_sName); + _builder.append(" > ?"); + first = false; + } + if (child.containsKey("$lt")) { + if (!first) + _builder.append(" and "); + _builder.append(_sName); + _builder.append(" < ?"); + first = false; + } + if (child.containsKey("$gte")) { + if (!first) + _builder.append(" and "); + _builder.append(_sName); + _builder.append(" >= ?"); + first = false; + } + if (child.containsKey("$lte")) { + if (!first) + _builder.append(" and "); + _builder.append(_sName); + _builder.append(" <= ?"); + first = false; + } + if (child.containsKey("$contains") || child.containsKey("$startsWith")) { + if (!first) + _builder.append(" and "); + _builder.append(_sName); + _builder.append(" like ?"); + first = false; + } + if (child.containsKey("$containsIgnoreCase") || child.containsKey("$startsWithIgnoreCase")) { + if (!first) + _builder.append(" and "); + _builder.append("lower("); + _builder.append(_sName); + _builder.append(")"); + _builder.append(" like ?"); + first = false; + } + if (child.containsKey("$in")) { + if (!first) + _builder.append(" and "); + _builder.append("{in}"); + first = false; + } + if (child.containsKey("$nin")) { + if (!first) + _builder.append(" and "); + _builder.append("NOT {in}"); + first = false; + } + if (child.containsKey("$or")) { + _builder.append("?"); + } + if (child.containsKey("$orEqualsIgnoreCase")) { + _builder.append("lower("); + _builder.append(_sName); + _builder.append(")"); + _builder.append(" = ?"); + } + + } + else if ((_value instanceof String) && NullUtils.isEqual(_value, "$null")) { + _builder.append(_sName); + _builder.append(" IS NULL"); + } + else if ((_value instanceof String) && NullUtils.isEqual(_value, "$notnull")) { + _builder.append(_sName); + _builder.append(" IS NOT NULL"); + } + else { + _builder.append(_sName); + _builder.append(" = ?"); + } + } + + protected int populateStatement(PreparedStatement _statement, int _iParam, DaoQuery _query) { + try { + for (Map.Entry entry : CollectionUtils.makeNotNull(_query).entrySet()) { + _iParam = setSQLValue(_statement, _iParam, entry.getKey(), entry.getValue()); + } + } + catch (SQLException e) { + LOG.error("Failed to populate statement", e); + } + return _iParam; + } + + protected int setSQLValue(PreparedStatement _statement, int _iParam, String _sName, Object _value) throws SQLException { + if (_sName.equals("$and") && (_value instanceof DaoQuery[])) { + for (DaoQuery query : (DaoQuery[]) _value) { + _iParam = populateStatement(_statement, _iParam, query); + } + } + if (_sName.equals("$or") && (_value instanceof DaoQuery)) { + DaoQuery query = (DaoQuery) _value; + _iParam = populateStatement(_statement, _iParam, query); + } + else if (_value instanceof DaoQuery) { + DaoQuery child = (DaoQuery) _value; + _iParam = setParam(_statement, _iParam, child.get("$ne")); + _iParam = setParam(_statement, _iParam, child.get("$gt")); + _iParam = setParam(_statement, _iParam, child.get("$lt")); + _iParam = setParam(_statement, _iParam, child.get("$gte")); + _iParam = setParam(_statement, _iParam, child.get("$lte")); + Object contains = child.get("$contains"); + if (contains instanceof String) { + StringBuilder param = new StringBuilder("%"); + param.append((String)contains); + param.append("%"); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object containsIgnoreCase = child.get("$containsIgnoreCase"); + if (containsIgnoreCase instanceof String) { + StringBuilder param = new StringBuilder("%"); + param.append(((String)containsIgnoreCase).toLowerCase()); + param.append("%"); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object equalIgnoreCase = child.get("$equalIgnoreCase"); + if (equalIgnoreCase instanceof String) { + StringBuilder param = new StringBuilder(((String)equalIgnoreCase).toLowerCase()); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object startsWith = child.get("$startsWith"); + if (startsWith instanceof String) { + StringBuilder param = new StringBuilder((String)startsWith); + param.append("%"); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object startsWithIgnoreCase = child.get("$startsWithIgnoreCase"); + if (startsWithIgnoreCase instanceof String) { + StringBuilder param = new StringBuilder(((String)startsWithIgnoreCase).toLowerCase()); + param.append("%"); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object or = child.get("$or"); + if (or instanceof String) { + StringBuilder param = new StringBuilder(((String)or)); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object orEqualIgnoreCase = child.get("$orEqualIgnoreCase"); + if (orEqualIgnoreCase instanceof String) { + StringBuilder param = new StringBuilder(((String)orEqualIgnoreCase).toLowerCase()); + _iParam = setParam(_statement, _iParam, param.toString()); + } + } + else { + _iParam = setParam(_statement, _iParam, _value); + } + return _iParam; + } + + protected int setParam(PreparedStatement _statement, int _iParam, Object _value) throws SQLException { + if ((_value == null) || ((_value instanceof String) && NullUtils.isOneOf(_value, "$null", "$notnull"))) { + return _iParam; + } + _statement.setObject(_iParam, _value); + return ++_iParam; + } + + protected int populateStatement(PreparedInStatement _statement, int _iParam, DaoQuery _object) { + try { + for (Map.Entry entry : CollectionUtils.makeNotNull(_object).entrySet()) { + _iParam = setSQLValue(_statement, _iParam, entry.getKey(), entry.getValue()); + } + } + catch (SQLException e) { + LOG.error("Failed to populate in statement", e); + } + return _iParam; + } + + protected int setSQLValue(PreparedInStatement _statement, int _iParam, String _sName, Object _value) throws SQLException { + if (_sName.equals("$and") && (_value instanceof DaoQuery[])) { + for (DaoQuery object : (DaoQuery[]) _value) { + _iParam = populateStatement(_statement, _iParam, object); + } + } + else if (_value instanceof DaoQuery) { + DaoQuery child = (DaoQuery) _value; + Object inClause = child.get("$in"); + if (inClause == null) + inClause = child.get("$nin"); + if (inClause instanceof Object[]) { + Collection collObjects = new ArrayList(); + Collections.addAll(collObjects, (Object[]) inClause); + _statement.setInClause(_iParam++, _sName, collObjects); + } + else if (inClause instanceof Collection) { + _statement.setInClause(_iParam++, _sName, (Collection) inClause); + } + _iParam = setParam(_statement, _iParam, child.get("$ne")); + _iParam = setParam(_statement, _iParam, child.get("$gt")); + _iParam = setParam(_statement, _iParam, child.get("$lt")); + _iParam = setParam(_statement, _iParam, child.get("$gte")); + _iParam = setParam(_statement, _iParam, child.get("$lte")); + Object contains = child.get("$contains"); + if (contains instanceof String) { + StringBuilder param = new StringBuilder("%"); + param.append((String)contains); + param.append("%"); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object containsIgnoreCase = child.get("$containsIgnoreCase"); + if (containsIgnoreCase instanceof String) { + StringBuilder param = new StringBuilder("%"); + param.append(((String)containsIgnoreCase).toLowerCase()); + param.append("%"); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object equalIgnoreCase = child.get("$equalIgnoreCase"); + if (equalIgnoreCase instanceof String) { + StringBuilder param = new StringBuilder(((String)equalIgnoreCase).toLowerCase()); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object startsWith = child.get("$startsWith"); + if (startsWith instanceof String) { + StringBuilder param = new StringBuilder((String)startsWith); + param.append("%"); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object startsWithIgnoreCase = child.get("$startsWithIgnoreCase"); + if (startsWithIgnoreCase instanceof String) { + StringBuilder param = new StringBuilder(((String)startsWithIgnoreCase).toLowerCase()); + param.append("%"); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object or = child.get("$or"); + if (or instanceof String) { + StringBuilder param = new StringBuilder(((String)or)); + _iParam = setParam(_statement, _iParam, param.toString()); + } + Object orEqualIgnoreCase = child.get("$orEqualIgnoreCase"); + if (orEqualIgnoreCase instanceof String) { + StringBuilder param = new StringBuilder(((String)orEqualIgnoreCase).toLowerCase()); + _iParam = setParam(_statement, _iParam, param.toString()); + } + } + else { + _iParam = setParam(_statement, _iParam, _value); + } + return _iParam; + } + + protected int setParam(PreparedInStatement _statement, int _iParam, Object _value) throws SQLException { + if ((_value == null) || ((_value instanceof String) && NullUtils.isOneOf(_value, "$null", "$notnull"))) { + return _iParam; + } + _statement.setObject(_iParam, _value); + return ++_iParam; + } + + protected List toDaoEntities(PreparedInStatement _statement) { + List listObjects = new ArrayList(); + ResultSet rs = null; + Connection conn = null; + try { + while (_statement.hasNext()) { + rs = _statement.next(); + toDaoEntities(rs, listObjects); + Statement statement = rs.getStatement(); + conn = statement.getConnection(); + close(rs); + close(statement); + } + } + catch (SQLException e) { + LOG.error("Failed to read result set", e); + } + finally { + close(rs); + close(conn); + } + return listObjects; + } + + protected List toDaoEntities(PreparedStatement _statement) { + List listObjects = new ArrayList(); + ResultSet rs = null; + try { + rs = _statement.executeQuery(); + toDaoEntities(rs, listObjects); + } + catch (SQLException e) { + LOG.error("Failed to read result set", e); + } + finally { + close(rs); + closeStatementAndConnection(_statement); + } + return listObjects; + } + + protected void toDaoEntities(ResultSet _rs, Collection _collEntities) { + try { + while (_rs.next()) { + DaoEntity object = new DaoEntity(); + ResultSetMetaData rsmd = _rs.getMetaData(); + for (int i = 1; i <= rsmd.getColumnCount(); i++) { + int type = rsmd.getColumnType(i); + String name = rsmd.getColumnName(i).toLowerCase(); + if (type == Types.TIMESTAMP) // This prevents Oracle from using a non-JDBC compliant type. + { + object.put(name, _rs.getTimestamp(i)); + } + else if (type == Types.BLOB) { + InputStream is = null; + java.sql.Blob blob = null; + try { + blob = _rs.getBlob(i); + is = blob.getBinaryStream(); + object.put(name, IOUtils.toByteArray(is)); + } + catch (Exception e) { + close(blob); + IOUtils.closeQuietly(is); + } + } + else if (type == Types.CLOB) { + Reader r = null; + Clob clob = null; + try { + clob = _rs.getClob(i); + r = clob.getCharacterStream(); + object.put(name, IOUtils.toString(r)); + } + catch (Exception e) { + close(clob); + IOUtils.closeQuietly(r); + } + } + else { + object.put(name, _rs.getObject(i)); + } + } + _collEntities.add(object); + } + } + catch (SQLException e) { + LOG.error("Failed to read result set", e); + } + } + + @Override + public boolean delete(String _tableName, DaoQuery _query) { + PreparedStatement statement = null; + try { + String query = buildSQL("DELETE", _tableName, _query, null, 0, -1); + if (query.contains("{in}")) { + PreparedInStatement inStatement = getPreparedInStatement(query); + populateStatement(inStatement, 1, _query); + inStatement.executeUpdate(); + } + else { + statement = getPreparedStatement(query); + populateStatement(statement, 1, _query); + statement.executeUpdate(); + Connection conn = statement.getConnection(); + close(statement); + close(conn); + } + return true; + } + catch (SQLException e) { + LOG.error("Failed to delete record", e); + return false; + } + finally { + closeStatementAndConnection(statement); + } + } + + @Override + public void update(Class _class, DaoQuery _query, DaoEntity _changes) { + if (_changes == null) { + return; + } + List clobs = new ArrayList(); + PreparedStatement statement = null; + try { + String sQuery = buildUpdateStatement(DaoSerializer.getTableName(_class, getType()), _query, _changes); + if (sQuery.contains("{in}")) { + PreparedInStatement inStatement = getPreparedInStatement(sQuery); + int iIdx = 0; + for (Map.Entry field : _changes.entrySet()) { + if (field.getValue() == null) + inStatement.setNull(++iIdx, DaoSerializer.getSqlType(_class, field.getKey())); + else + inStatement.setObject(++iIdx, field.getValue()); + } + populateStatement(inStatement, ++iIdx, _query); + inStatement.executeUpdate(); + } + else { + Set clobFields = new HashSet(DaoSerializer.getFieldsByAnnotation(_class, DBClob.class, getType())); + Connection connection = getConnection(); + statement = getPreparedStatement(connection, sQuery); + int iIdx = 0; + for (Map.Entry field : _changes.entrySet()) { + setStatementField(connection, statement, ++iIdx, field.getKey(), field.getValue(), _class, clobFields, clobs); + } + populateStatement(statement, ++iIdx, _query); + statement.executeUpdate(); + } + } + catch (SQLException e) { + LOG.error("Failed to execute update", e); + } + finally { + for (Clob clob : clobs) { + try { + clob.free(); + } + catch (SQLException _e) { + LOG.error("Failed to free clob", _e); + } + } + closeStatementAndConnection(statement); + } + } + + @Override + public T updateOne(Class _class, DaoQuery _query, DaoEntity _changes) { + update(_class, _query, _changes); + return queryOne(_class, _query); + } + + @Override + public Map save(Collection _objects) { + return save(_objects, false, true); + } + + public Map save(Collection _objects, boolean _checkExists, boolean _savepoint) { + Map ids = new HashMap(); + T object = CollectionUtils.getFirst(_objects); + if (object == null) + return ids; + Class entityClass = object.getClass(); + String tableName = DaoSerializer.getTableName(entityClass, getType()); + List primaryKeyFields = DaoSerializer.getFieldsByAnnotation(entityClass, PrimaryKey.class, getType()); + if (primaryKeyFields.size() > 1) + throw new RuntimeException("Objects with multiple primary keys are not supported for batch inserts"); + if (primaryKeyFields.size() == 0) + throw new RuntimeException("No primary key annotated on entity class " + entityClass.getCanonicalName()); + final String primaryKeyField = primaryKeyFields.get(0); + List insertEntities = new ArrayList(); + List updateEntities = new ArrayList(); + Object primaryKey = null; + for (T o : _objects) { + DaoEntity entity = DaoSerializer.toDaoEntity(o, getType()); + prepareEntity(entity); + primaryKey = entity.get(primaryKeyField); + if ((primaryKey == null) || (primaryKey instanceof String)) { + if (NullUtils.isEmpty((String) primaryKey)) { + primaryKey = UUID.randomUUID().toString(); + entity.put(primaryKeyField, primaryKey); + insertEntities.add(entity); + } + else { + updateEntities.add(entity); + } + ids.put((String)primaryKey, o); + } + else { + throw new RuntimeException("Only String primary keys are supported for batch inserts"); + } + } + if (_checkExists && !updateEntities.isEmpty()) { + Set entities = CollectionUtils.transformToSet(updateEntities, new ITransformer() { + @Override + public String transform(DaoEntity _daoEntity) { + return (String)_daoEntity.get(primaryKeyField); + } + }); + for (String id : queryForField(entityClass, DaoQuery.in(primaryKeyField, entities), primaryKeyField)) { + entities.remove(id); + } + Iterator iterUpdates = updateEntities.iterator(); + while (iterUpdates.hasNext()) { + DaoEntity entity = iterUpdates.next(); + if (entities.contains((String)entity.get(primaryKeyField))) { + iterUpdates.remove(); + insertEntities.add(entity); + } + } + } + Set clobFields = new HashSet(DaoSerializer.getFieldsByAnnotation(entityClass, DBClob.class, getType())); + List clobs = new ArrayList(); + Connection connection = null; + Savepoint sp = null; + PreparedStatement insert = null; + PreparedStatement update = null; + try { + connection = getConnection(); + if (connection != null) { + boolean autoCommit = connection.getAutoCommit(); + connection.setAutoCommit(false); + if (_savepoint) + sp = connection.setSavepoint(); + DaoEntity insertEntity = CollectionUtils.getFirst(insertEntities); + if (insertEntity != null) { + primaryKey = insertEntity.remove(primaryKeyField); + insert = getPreparedStatement(connection, getInsertStatement(tableName, insertEntity, primaryKeyFields)); + insertEntity.put(primaryKeyField, primaryKey); + if (insert == null) + return ids; + for (DaoEntity entity : insertEntities) { + int iIdx = 0; + primaryKey = entity.remove(primaryKeyField); + insert.setObject(++iIdx, primaryKey); + for (Map.Entry field : entity.entrySet()) { + setStatementField(connection, insert, ++iIdx, field.getKey(), field.getValue(), entityClass, clobFields, clobs); + } + insert.addBatch(); + } + insert.executeBatch(); + } + DaoEntity updateEntity = CollectionUtils.getFirst(updateEntities); + if (updateEntity != null) { + DaoQuery updateQuery = new DaoQuery(); + for (String key : primaryKeyFields) { + updateQuery.put(key, ""); + } + primaryKey = updateEntity.remove(primaryKeyField); + Map ignored = new HashMap(); + for (String ignore : DaoSerializer.getFieldsByAnnotation(entityClass, NeverUpdate.class, getType())) { + ignored.put(ignore, updateEntity.remove(ignore)); + } + update = getPreparedStatement(connection, getUpdateStatement(tableName, updateQuery, updateEntity)); + updateEntity.put(primaryKeyField, primaryKey); + for (Entry ignoredEntry : ignored.entrySet()) { + updateEntity.put(ignoredEntry.getKey(), ignoredEntry.getValue()); + } + if (update == null) { + if (sp != null) + connection.rollback(sp); + return ids; + } + for (DaoEntity entity : updateEntities) { + for (String ignore : DaoSerializer.getFieldsByAnnotation(entityClass, NeverUpdate.class, getType())) { + entity.remove(ignore); + } + primaryKey = entity.remove(primaryKeyField); + int iIdx = 0; + for (Map.Entry field : entity.entrySet()) { + setStatementField(connection, update, ++iIdx, field.getKey(), field.getValue(), entityClass, clobFields, clobs); + } + update.setObject(++iIdx, primaryKey); + update.addBatch(); + } + update.executeBatch(); + } + connection.commit(); + connection.setAutoCommit(autoCommit); + } + return ids; + } + catch (SQLException _e) { + LOG.error("Exception occurred while batch inserting", _e); + try { + if (sp != null) + connection.rollback(sp); + } + catch (SQLException _e1) { + LOG.error("Failed to rollback after exception", _e1); + } + return ids; + } + finally { + for (Clob clob : clobs) { + close(clob); + } + close(insert); + close(update); + close(connection); + } + } + + private void setStatementField(Connection _connection, PreparedStatement _statement, int _idx, String _name, Object _value, Class _entityClass, Set _clobFields, List _clobs) throws SQLException { + if (_clobFields.contains(_name)) { + Clob clob = _connection.createClob(); + clob.setString(1, (String) _value); + _clobs.add(clob); + _statement.setClob(_idx, clob); + } + else { + if (_value == null) + _statement.setNull(_idx, DaoSerializer.getSqlType(_entityClass, _name)); + else + _statement.setObject(_idx, _value); + } + } + + @Override + public String saveEntity(String _collection, DaoEntity _entity) { + return saveEntity(_collection, _entity, "id"); + } + + public String saveEntity(String _collection, DaoEntity _entity, String _primaryKeyField) { + return saveEntity(null, _collection, null, CollectionUtils.asArrayList(_primaryKeyField), null, null, _entity); + } + + @Override + public String saveEntity(Class _class, DaoEntity _entity) { + DBSerializable table = _class.getAnnotation(DBSerializable.class); + String seq = (table == null)?null:table.seq(); + return saveEntity(_class, DaoSerializer.getTableName(_class, getType()), seq, DaoSerializer.getFieldsByAnnotation(_class, PrimaryKey.class, getType()), DaoSerializer.getFieldsByAnnotation(_class, DBClob.class, getType()), DaoSerializer.getFieldsByAnnotation(_class, NeverUpdate.class, getType()), _entity); + } + + private String saveEntity(Class _class, String _table, String _sequenceName, List _primaryKeys, List _clobs, List _neverUpdate, DaoEntity _entity) { + if (_entity == null) { + return null; + } + prepareEntity(_entity); + Set clobFields = new HashSet<>(CollectionUtils.makeNotNull(_clobs)); + DaoQuery keyQuery = new DaoQuery(); + boolean insert = false; + List keys = new ArrayList(); + for (String key : CollectionUtils.makeNotNull(_primaryKeys)) { + Object value = _entity.remove(key); + if ((value == null) || (value instanceof String)) { + if (NullUtils.isEmpty((String) value)) { + value = UUID.randomUUID().toString(); + insert = true; + } + } + else if (value instanceof Long) { + if (((Long) value) == 0) { + value = getNextSequence(_sequenceName, _table); + if (((Long) value) == 0){ + value = null; //null value will use mySQL auto-increment by default + } + insert = true; + } + } + keyQuery.put(key, value); + keys.add(value); + } + if (!insert) + insert = !exists(_table, keyQuery); + String sQuery; + if (insert) { + sQuery = getInsertStatement(_table, _entity, _primaryKeys); + } + else { + DaoQuery updateQuery = new DaoQuery(); + for (String key : _primaryKeys) { + updateQuery.put(key, ""); + } + for (String ignore : CollectionUtils.makeNotNull(_neverUpdate)) { + _entity.remove(ignore); + } + sQuery = getUpdateStatement(_table, updateQuery, _entity); + } + List clobs = new ArrayList(); + Connection connection = getConnection(); + PreparedStatement statement = null; + try { + statement = getPreparedStatement(connection, sQuery); + int iIdx = 0; + if (insert) { + for (Object id : keys) { + statement.setObject(++iIdx, id); + } + } + for (Map.Entry field : _entity.entrySet()) { + setStatementField(connection, statement, ++iIdx, field.getKey(), field.getValue(), _class, clobFields, clobs); + } + if (!insert) { + for (Object id : keys) { + statement.setObject(++iIdx, id); + } + } + final int id = statement.executeUpdate(); + return CollectionUtils.transformToCommaSeparated(keys, new ITransformer() { + @Override + public String transform(Object _o) { + if (_o instanceof String) + return (String) _o; + if (_o instanceof Long) + return String.valueOf((Long) _o); + if(_o instanceof Integer) + return String.valueOf((Integer) id); + return null; + } + }); + } + catch (SQLException e) { + LOG.error("Failed to save entity", e); + return ""; + } + finally { + for (Clob clob : clobs) { + close(clob); + } + close(statement); + close(connection); + } + } + + protected String getInsertStatement(String _tableName, DaoEntity _entity, List _primaryKeyFields) { + String sQuery = insertStatements.get(_tableName); + if (sQuery != null) { + return sQuery; + } + StringBuilder builder = new StringBuilder("INSERT INTO "); + builder.append(_tableName); + builder.append("("); + builder.append(CollectionUtils.commaSeparated(_primaryKeyFields)); + if(CollectionUtils.isNotEmpty(_entity)) { + builder.append(","); + builder.append(CollectionUtils.commaSeparated(_entity.keySet())); + } + + StringBuilder values = null; + for (int i = 0; i < _primaryKeyFields.size() + CollectionUtils.size(_entity); i++) { + if (values == null) { + values = new StringBuilder(); + } + else { + values.append(","); + } + values.append("?"); + } + builder.append(") values("); + builder.append(values); + builder.append(")"); + sQuery = builder.toString(); + insertStatements.put(_tableName, sQuery); + return sQuery; + } + + protected String getUpdateStatement(String _tableName, DaoQuery _query, DaoEntity _object) { + String sQuery = updateStatements.get(_tableName); + if (sQuery != null) { + return sQuery; + } + sQuery = buildUpdateStatement(_tableName, _query, _object); + updateStatements.put(_tableName, sQuery); + return sQuery; + } + + protected String buildUpdateStatement(String _tableName, DaoQuery _query, DaoEntity _object) { + StringBuilder builder = new StringBuilder("UPDATE "); + builder.append(_tableName); + builder.append(" SET "); + Iterator iterColumns = _object.keySet().iterator(); + if (!iterColumns.hasNext()) { + return ""; + } + builder.append(iterColumns.next()); + builder.append(" = ?"); + while (iterColumns.hasNext()) { + builder.append(","); + builder.append(iterColumns.next()); + builder.append(" = ?"); + } + builder.append(" where "); + queryToSQL(_query, builder); + return builder.toString(); + } + + protected void close(ResultSet _rs) { + try { + if (_rs != null) { + _rs.close(); + } + } + catch (SQLException _e) { + } + } + + protected void closeStatementAndConnection(Statement _statement) { + if (_statement == null) + return; + try { + Connection conn = _statement.getConnection(); + close(_statement); + if (alwaysClose()) + conn.close(); + } + catch (SQLException _e) { + } + } + + protected void close(Connection _connection) { + try { + if ((_connection != null) && alwaysClose()) { + _connection.close(); + } + } + catch (SQLException _e) { + } + } + + protected void close(Statement _statement) { + try { + if (_statement != null) { + _statement.close(); + } + } + catch (SQLException _e) { + } + } + + protected void close(Clob _clob) { + try { + if (_clob != null) { + _clob.free(); + } + } + catch (SQLException _e) { + } + } + + protected void close(Blob _blob) { + try { + if (_blob != null) { + _blob.free(); + } + } + catch (SQLException _e) { + } + } + + public PreparedStatement getPreparedStatement(String _statement) { + if (_statement == null) { + return null; + } + LOG.trace(_statement); + return getPreparedStatement(getConnection(), _statement); + } + + public PreparedStatement getPreparedStatement(Connection _connection, String _statement) { + try { + PreparedStatement statement = _connection.prepareStatement(_statement); + statement.setFetchSize(fetchSize); + return statement; + } + catch (SQLException e) { + LOG.error("Failed to create PreparedStatement", e); + return null; + } + } + + protected PreparedInStatement getPreparedInStatement(String _statement) { + if (_statement == null) { + return null; + } + LOG.trace(_statement); + try { + PreparedInStatement statement = new PreparedInStatement(_statement, this); + return statement; + } + catch (SQLException e) { + LOG.error("Failed to create PreparedInStatement", e); + return null; + } + } + + private long getNextSequence(String _sequence, String _tableName) { + if (NullUtils.isEmpty(_sequence)) { + LOG.error("Failed to get sequence name for " + _tableName); + return 0; + } + PreparedStatement statement = getPreparedStatement("SELECT " + _sequence + ".NEXTVAL FROM DUAL"); + ResultSet rs = null; + try { + rs = statement.executeQuery(); + if (rs.next()) { + return rs.getLong(1); + } + LOG.error("Failed to get sequence name for " + _tableName); + return 0; + } + catch (SQLException e) { + LOG.error("Failed to get sequence name for " + _tableName); + return 0; + } + finally { + close(rs); + closeStatementAndConnection(statement); + } + } + + private void prepareEntity(DaoEntity _entity) { + if (entityPreparer != null) + entityPreparer.prepareEntity(_entity); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/DataSourceProxy.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/DataSourceProxy.java new file mode 100644 index 0000000..95a89f7 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/DataSourceProxy.java @@ -0,0 +1,79 @@ +package com.lanternsoftware.util.dao.jdbc; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +import javax.naming.InitialContext; +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.NullUtils; + +public class DataSourceProxy extends AbstractJdbcProxy { + private static final Logger LOG = LoggerFactory.getLogger(DataSourceProxy.class); + private DataSource dataSource; + private String schemaName; + + public DataSourceProxy(String _jndiDataSourceName, String _schemaName) { + this(_jndiDataSourceName); + schemaName = _schemaName; + } + public DataSourceProxy(String _jndiDataSourceName) { + try { + dataSource = (DataSource)new InitialContext().lookup(_jndiDataSourceName); + } + catch (Exception e) { + try { + dataSource = (DataSource)new InitialContext().lookup("java:/comp/env/"+_jndiDataSourceName); + } + catch (Exception _e) { + LOG.error("Error looking up " + _jndiDataSourceName, e); + } + } + } + + public DataSourceProxy(DataSource _dataSource) { + dataSource = _dataSource; + try { + DatabaseMetaData metaData = getConnection().getMetaData(); + if (metaData.getDatabaseProductName().equals("Oracle") && (metaData.getDatabaseMajorVersion() >= 12)) + databaseType = DatabaseType.ORACLE_12C; + } + catch (SQLException _e) { + LOG.error("Could not get database type", _e); + } + } + + @Override + public Connection getConnection() { + try { + Connection conn = dataSource.getConnection(); + if (NullUtils.isNotEmpty(schemaName) && (conn != null)) + conn.setSchema(schemaName); + return conn; + } + catch (SQLException _e) { + LOG.error("Failed to get a jdbc connection", _e); + return null; + } + } + + @Override + public boolean isConnected() { + try { + return getConnection().isValid(10); + } + catch (Exception _e) { + LOG.error("Failed to get a jdbc connection", _e); + return false; + } + } + + @Override + public boolean alwaysClose() { + return true; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/DatabaseType.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/DatabaseType.java new file mode 100644 index 0000000..14793c5 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/DatabaseType.java @@ -0,0 +1,8 @@ +package com.lanternsoftware.util.dao.jdbc; + +public enum DatabaseType { + ORACLE_11G, + ORACLE_12C, + MYSQL, + CACHE +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/JdbcConfig.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/JdbcConfig.java new file mode 100644 index 0000000..987b2e0 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/JdbcConfig.java @@ -0,0 +1,91 @@ +package com.lanternsoftware.util.dao.jdbc; + +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +@DBSerializable +public class JdbcConfig { + private DatabaseType type; + private String username; + private String password; + private String hostname; + private String database; + private String port; + + public JdbcConfig() { + } + + public JdbcConfig(DatabaseType _type, String _username, String _password, String _hostname, String _database, String _port) { + type = _type; + username = _username; + password = _password; + hostname = _hostname; + database = _database; + port = _port; + } + + public DatabaseType getType() { + return type; + } + + public void setType(DatabaseType _type) { + type = _type; + } + + public String getUsername() { + return username; + } + + public void setUsername(String _username) { + username = _username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String _password) { + password = _password; + } + + public String getHostname() { + return hostname; + } + + public void setHostname(String _hostname) { + hostname = _hostname; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String _database) { + database = _database; + } + + public String getPort() { + return port; + } + + public void setPort(String _port) { + port = _port; + } + + public String getConnectionString() { + StringBuilder conn = new StringBuilder("jdbc:"); + if (type == DatabaseType.MYSQL) + conn.append("mysql"); + else + conn.append("oracle:thin"); + conn.append("://"); + conn.append(hostname); + conn.append(":"); + conn.append(port); + if (NullUtils.isNotEmpty(database)) { + conn.append("/"); + conn.append(database); + } + return conn.toString(); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/JdbcProxy.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/JdbcProxy.java new file mode 100644 index 0000000..5f345da --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/JdbcProxy.java @@ -0,0 +1,62 @@ +package com.lanternsoftware.util.dao.jdbc; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JdbcProxy extends AbstractJdbcProxy { + private static final Logger LOG = LoggerFactory.getLogger(JdbcProxy.class); + private Connection connection; + + public JdbcProxy(Connection _connection) { + connection = _connection; + } + + @Override + public Connection getConnection() { + return connection; + } + + @Override + public boolean isConnected() { + try { + return connection.isValid(10); + } + catch (Exception _e) { + LOG.error("Failed to get a jdbc connection", _e); + return false; + } + } + + @Override + public boolean alwaysClose() { + return false; + } + + public static JdbcProxy getProxy(JdbcConfig _config) { + return getProxy(_config.getType(), _config.getConnectionString(), _config.getUsername(), _config.getPassword()); + } + + public static JdbcProxy getProxy(DatabaseType _type, String _connectionString, String _username, String _password) { + String driver; + if (_type == DatabaseType.MYSQL) + driver = "com.mysql.cj.jdbc.Driver"; + else if (_type == DatabaseType.CACHE) + driver = "com.intersys.jdbc.CacheDriver"; + else + driver = "oracle.jdbc.driver.OracleDriver"; + try { + DriverManager.registerDriver(Class.forName(driver).asSubclass(Driver.class).newInstance()); + JdbcProxy proxy = new JdbcProxy(DriverManager.getConnection(_connectionString, _username, _password)); + proxy.databaseType = _type; + return proxy; + } + catch (Exception _e) { + LOG.error("Failed to load JDBC driver for database type: " + _type, _e); + return null; + } + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/OracleTestProxy.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/OracleTestProxy.java new file mode 100644 index 0000000..dd399d1 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/OracleTestProxy.java @@ -0,0 +1,7 @@ +package com.lanternsoftware.util.dao.jdbc; + +public abstract class OracleTestProxy { + public static JdbcProxy getProxy(String _connectionString, String _username, String _password) { + return JdbcProxy.getProxy(DatabaseType.ORACLE_11G, _connectionString, _username, _password); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/dao/JdbcConfigSerializer.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/dao/JdbcConfigSerializer.java new file mode 100644 index 0000000..9df69ad --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/dao/JdbcConfigSerializer.java @@ -0,0 +1,51 @@ +package com.lanternsoftware.util.dao.jdbc.dao; + +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.jdbc.DatabaseType; +import com.lanternsoftware.util.dao.jdbc.JdbcConfig; + +import java.util.Collections; +import java.util.List; + +public class JdbcConfigSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return JdbcConfig.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(JdbcConfig _o) + { + DaoEntity d = new DaoEntity(); + d.put("type", DaoSerializer.toEnumName(_o.getType())); + d.put("username", _o.getUsername()); + d.put("password", _o.getPassword()); + d.put("hostname", _o.getHostname()); + d.put("database", _o.getDatabase()); + d.put("port", _o.getPort()); + return d; + } + + @Override + public JdbcConfig fromDaoEntity(DaoEntity _d) + { + JdbcConfig o = new JdbcConfig(); + o.setType(DaoSerializer.getEnum(_d, "type", DatabaseType.class)); + o.setUsername(DaoSerializer.getString(_d, "username")); + o.setPassword(DaoSerializer.getString(_d, "password")); + o.setHostname(DaoSerializer.getString(_d, "hostname")); + o.setDatabase(DaoSerializer.getString(_d, "database")); + o.setPort(DaoSerializer.getString(_d, "port")); + return o; + } +} \ No newline at end of file diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/BatchBucket.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/BatchBucket.java new file mode 100644 index 0000000..a34fd07 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/BatchBucket.java @@ -0,0 +1,89 @@ +package com.lanternsoftware.util.dao.jdbc.preparedinstatement; + +import java.util.Collection; +import java.util.Iterator; +import java.util.TreeSet; + +/** + * Utility class that houses the logic to determine bucket sizes for batched in-clauses. + */ +class BatchBucket { + private static final int MAX_BATCH_SIZE = 1000; + private static final Collection predefinedBucketSizes = new TreeSet(); + static { + predefinedBucketSizes.add(1); + predefinedBucketSizes.add(10); + predefinedBucketSizes.add(25); + predefinedBucketSizes.add(50); + predefinedBucketSizes.add(100); + predefinedBucketSizes.add(500); + predefinedBucketSizes.add(MAX_BATCH_SIZE); + } + + private final Collection bucketSizes = new TreeSet(); + private int maxBatchSize; + + /** + * Default Constructor. The default maximum batch size is currently 1000. + */ + public BatchBucket() { + bucketSizes.addAll(predefinedBucketSizes); + maxBatchSize = MAX_BATCH_SIZE; + } + + /** + * Batch-size Constructor + * + * @param _nMaxBatchSize + * - Integer specifying the maximum batch size. If this value is greater than the maximum batch size (1000), + * it will be truncated. + */ + public BatchBucket(int _nMaxBatchSize) { + if (_nMaxBatchSize <= 0 || _nMaxBatchSize > MAX_BATCH_SIZE) { + bucketSizes.addAll(predefinedBucketSizes); + maxBatchSize = MAX_BATCH_SIZE; + return; + } + + Iterator iter = predefinedBucketSizes.iterator(); + while (iter.hasNext()) { + int nNextBatchSize = iter.next(); + if (nNextBatchSize < _nMaxBatchSize) { + maxBatchSize = nNextBatchSize; + bucketSizes.add(nNextBatchSize); + } + else + maxBatchSize = _nMaxBatchSize; + } + + bucketSizes.add(_nMaxBatchSize); + } + + /** + * @return the calculated maximum batch size + */ + public int getMaxBatchSize() { + return maxBatchSize; + } + + /** + * Method to calculate the appropriate batch size for a specific current size + * + * @param _nCurSize + * - Integer representing the current size of the statement + * @return an Integer representing the batch size + */ + public int getBatchSize(int _nCurSize) { + if (_nCurSize <= 0) + return 0; + + Iterator iter = bucketSizes.iterator(); + while (iter.hasNext()) { + int nNextBatchSize = iter.next(); + if (_nCurSize <= nNextBatchSize) + return nNextBatchSize; + } + + return 0; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClause.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClause.java new file mode 100644 index 0000000..9ff7a22 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClause.java @@ -0,0 +1,148 @@ +package com.lanternsoftware.util.dao.jdbc.preparedinstatement; + +import java.sql.PreparedStatement; +import java.util.Iterator; +import java.util.LinkedList; + +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedParameter; + +/** + * Class that represents a single in-clause statement (all columns - {@link InClauseColumn}) Example: Batch Size: 2 + * select * from table where (record_id, str_val) in (('1','2'),('3','4')) InClause is an abstract representation for + * all of the pieces of information: InClauseColumn Column Display: record_id Parameters: '1', '3' InClauseColumn Column + * Display: str_val Parameters: '2', '4' + */ +class InClause { + private final InClauseColumn[] columns; + private BatchBucket batchBucket; + private final int startIdx; + private final boolean returnAllIfEmpty; + + /** + * Default Constructor + * + * @param _startIdx + * - Integer representing the index of the in-clause inside the SQL statement + * @param _returnAllIfEmpty + * - a boolean flag that determines the resulting behavior if the {@link InClauseColumn}s are empty. If true, + * the in-clause will be replaced with a '1=1', meaning all rows would be returned; false will replace with + * '1 = 0'. + * @param _columns + * - a variable list of {@link InClauseColumn}s that make up the in-clause + */ + public InClause(int _startIdx, boolean _returnAllIfEmpty, InClauseColumn... _columns) { + columns = _columns; + startIdx = _startIdx; + returnAllIfEmpty = _returnAllIfEmpty; + batchBucket = new BatchBucket(0); + } + + /** + * Method which defines a maximum batch size used to calculate in-clause batch sizes. + * + * @param _batchSize + */ + public void setMaxBatchSize(int _batchSize) { + batchBucket = new BatchBucket(_batchSize); + } + + /** + * Method to take all of the {@link InClauseColumn}s that make up the in-clause and turned those into a list of + * {@link InClauseBatchedParameter}s. When executing an in-clause, some databases restrict the number of items that may + * be in a specified in-clause (currently the limit is 1000). This item-limit includes items for all columns (i.e. if + * you have two columns, each column may only contribute half of the total - i.e. 500 items). Therefore, we need to + * build a list of batched parameters that do not exceed the maximum item threshold, but at the same time is batched + * appropriately (to minimize the number of {@link PreparedStatement}s that will be generated. When optimizing batch + * sizes, there are a few special cases that are noted below. Special Case 1: The batch-size is less + * than the number of {@link InClauseColumn}s In this situation, the batch size will be adjusted to the number of + * columns. For example, if you set the batch size to 1 and you pass in 2 columns, your optimized batch size will be 2. + * Special Case 2: No {@link PreparedParameter}s are set on any {@link InClauseColumn}s In this + * situation, the behavior will depend on the ReturnAllIfEmpty flag passed in during the constructor. If true, the + * in-clause will be replaced with a '1=1', meaning all rows would be returned; false will replace with '1 = 0'. + * Special Case 3: A single {@link InClauseColumn} with a single {@link PreparedParameter}s is set. In + * this situation, the in-clause will be optimized to be an equals (i.e. = ? and not in (?)). + * + * @return a {@link LinkedList} of optimum-sized batched {@link PreparedParameter}s + */ + public LinkedList getBatchedParameters() { + LinkedList listReturnParameters = new LinkedList(); + if (columns == null || columns.length == 0) + return listReturnParameters; + + int nColumnCnt = columns.length; + int nCurrentSize = 0; + + InClauseBatchedParameter batchedParameters = new InClauseBatchedParameter(columns); + listReturnParameters.add(batchedParameters); + + // special case 2: if there are no parameters, we will honor the behavior of returnAllIfEmpty + if (nColumnCnt >= 1 && columns[0].getParameterCnt() == 0) { + batchedParameters.setReturnAllIfEmpty(returnAllIfEmpty); + return listReturnParameters; + } + + // special case 3: if there is only 1 column and 1 element in the column, we can use = , not in() + if (nColumnCnt == 1 && columns[0].getParameterCnt() == 1) { + batchedParameters.addParameter(columns[0].getNextParameter()); + return listReturnParameters; + } + + InClauseBatchedParameter lastBatchedParameters = null; + boolean bMoreParameters = true; + while (bMoreParameters) { + if (/* special case 1 */nCurrentSize > 0 && (nCurrentSize + nColumnCnt) > batchBucket.getMaxBatchSize()) { + batchedParameters = new InClauseBatchedParameter(columns); + listReturnParameters.add(batchedParameters); + nCurrentSize = 0; + } + + lastBatchedParameters = new InClauseBatchedParameter(columns); + for (InClauseColumn column : columns) { + if (column == null) + continue; + + PreparedParameter parameter = column.getNextParameter(); + batchedParameters.addParameter(parameter); + lastBatchedParameters.addParameter(parameter); + nCurrentSize++; + + if (!column.hasNextParameter()) + bMoreParameters = false; + } + } + + // optimize the size of the remaining bucket + if (lastBatchedParameters != null) { + int nBatchSize = batchBucket.getBatchSize(nCurrentSize); + while (nCurrentSize < nBatchSize) { + // if we are going to add more parameters than the bucket size, we need to break now + if (nCurrentSize + nColumnCnt > nBatchSize) + break; + + Iterator iter = lastBatchedParameters.getParameters().iterator(); + while (iter.hasNext()) { + batchedParameters.addParameter(iter.next()); + ++nCurrentSize; + } + } + } + return listReturnParameters; + } + + /** + * @return Integer representing the starting index in the SQL statement + */ + public int getStartIndex() { + return startIdx; + } + + /** + * Method to reset the clause after an evaluation + */ + public void reset() { + for (InClauseColumn column : columns) { + if (column != null) + column.reset(); + } + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseBatchedParameter.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseBatchedParameter.java new file mode 100644 index 0000000..a8936c6 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseBatchedParameter.java @@ -0,0 +1,90 @@ +package com.lanternsoftware.util.dao.jdbc.preparedinstatement; + +import java.sql.PreparedStatement; +import java.util.LinkedList; + +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedParameter; + +/** + * Class that represents a collection of data to be set into a {@link PreparedStatement} ({@link PreparedParameter}). + * This will represent all of the data associated with an {@link InClauseColumn} Example: Batch Size: 2 select * from + * table where (record_id, str_val) in (('1','2'),('3','4')) InClauseBatchedParameter represents the Collection of + * batched parameters need to execute the InClause: Collection: '1', '2', '3', '4' If we change the + * batch size to one, the InClause would now have 2 InClauseBatchedParameters: Collection: '1', '3' + * Collection: '2', '4' + */ +public class InClauseBatchedParameter { + private final LinkedList parameters = new LinkedList(); + private final InClauseColumn[] columns; + + private int size = 0; + private boolean returnAllIfEmpty = false; + + /** + * Default Constructor + * + * @param _arrColumns + * - a variable list of {@link InClauseColumn}s that make up the in-clause + */ + public InClauseBatchedParameter(InClauseColumn[] _arrColumns) { + columns = _arrColumns; + } + + /** + * Method to add a new primitive parameter to the batched Collection + * + * @param _parameter + * - the {@link PreparedParameter} to be added + */ + public void addParameter(PreparedParameter _parameter) { + parameters.add(_parameter); + ++size; + } + + /** + * @param _bReturnAllIfEmpty + * - a boolean flag that determines the resulting behavior if the {@link InClauseColumn}s are empty. If true, + * the in-clause will be replaced with a '1=1', meaning all rows would be returned; false will replace with + * '1 = 0' + */ + public void setReturnAllIfEmpty(boolean _bReturnAllIfEmpty) { + returnAllIfEmpty = _bReturnAllIfEmpty; + } + + /** + * @return boolean; if true, the in-clause will be replaced with a '1=1', meaning all rows would be returned; false will + * replace with '1 = 0' + */ + public boolean isReturnAllIfEmpty() { + return returnAllIfEmpty; + } + + /** + * @return Integer representing the number of batched {@link PreparedParameter}s + */ + public int getBatchParameterCnt() { + return size; + } + + /** + * @return Integer representing the number of {@link InClauseColumn}s that make up the batched + * {@link PreparedParameter}s + */ + public int getColumnCnt() { + return columns.length; + } + + /** + * @return the array of {@link InClauseColumn}s that make up the batched {@link PreparedParameter}s + */ + public InClauseColumn[] getColumns() { + return columns; + } + + /** + * @return the {@link LinkedList} of batched {@link PreparedParameter}s + */ + public LinkedList getParameters() { + return parameters; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseBuilder.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseBuilder.java new file mode 100644 index 0000000..da6cc0c --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseBuilder.java @@ -0,0 +1,148 @@ +package com.lanternsoftware.util.dao.jdbc.preparedinstatement; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; + +class InClauseBuilder { + private final Map inClauses = new HashMap(); + private int maxBatchSize = 0; + + /** + * @param _clause + * - {@link InClause} to be processed. + */ + public void addClause(InClause _clause) { + if (_clause != null) { + inClauses.put(_clause.getStartIndex(), _clause); + } + } + + /** + * Method which defines a maximum batch size used to calculate in-clause batch sizes. + * + * @param _batchSize + */ + public void setMaxBatchSize(int _batchSize) { + maxBatchSize = _batchSize; + } + + /** + * Method to generate a Collection of {@link InClauseStatement}s to be executed. These statements represent all possible combinations of {@link InClauseBatchedParameter}s from all {@link InClause}s that will need to be executed in order to fulfill the set in-clauses. + * + * @return Collection of {@link InClauseStatement}s to be executed. Will not return null, but return an empty Collection + */ + public Collection buildStatements() { + Collection collStatements = new ArrayList(); + if (inClauses.isEmpty()) { + return collStatements; + } + + Map> mapBatchedParameters = new HashMap>(); + Iterator> iter = inClauses.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry = iter.next(); + if (entry == null) { + continue; + } + + int nStartIdx = entry.getKey(); + InClause inClause = entry.getValue(); + if (inClause == null) { + continue; + } + + inClause.setMaxBatchSize(maxBatchSize); + LinkedList batchedParameters = inClause.getBatchedParameters(); + mapBatchedParameters.put(nStartIdx, batchedParameters); + } + + buildInClauseStatements(mapBatchedParameters, collStatements); + return collStatements; + } + + private void buildInClauseStatements(Map> _batchedParameters, Collection _inStatements) { + if (_batchedParameters.isEmpty()) { + return; + } + + Entry> entry = _batchedParameters.entrySet().iterator().next(); + + int nStartIdx = entry.getKey(); + _batchedParameters.remove(nStartIdx); + + LinkedList parameters = entry.getValue(); + if (parameters == null) { + return; + } + + if (_inStatements.isEmpty()) { + for (InClauseBatchedParameter parameter : parameters) { + if (parameter == null) { + continue; + } + + InClauseStatement statement = new InClauseStatement(); + statement.setNextParameter(nStartIdx, parameter); + _inStatements.add(statement); + } + } + else { + Collection collNewStatements = new ArrayList(); + Iterator iter = _inStatements.iterator(); + while (iter.hasNext()) { + InClauseStatement existingStatement = iter.next(); + if (existingStatement == null) { + continue; + } + + // we need to clone the statement because we'll add a parameter to the first statement, + // but we need the original to add other combinations of the parameters + InClauseStatement clonedStatement = existingStatement.clone(); + + boolean bFirst = true; + for (InClauseBatchedParameter parameter : parameters) { + if (parameter == null) { + continue; + } + + /* + * if there's only 1 parameter, we can just add that parameter to the existing statements for additional parameters, we + * need to build all of the remaining combinations for the new parameter we the existing statements + */ + if (bFirst) { + existingStatement.setNextParameter(nStartIdx, parameter); + bFirst = false; + continue; + } + else { + InClauseStatement newStatement = clonedStatement.clone(); + newStatement.setNextParameter(nStartIdx, parameter); + collNewStatements.add(newStatement); + } + } + } + + _inStatements.addAll(collNewStatements); + } + + buildInClauseStatements(_batchedParameters, _inStatements); + } + + /** + * Method to reset the builder + */ + public void reset() { + for (InClause clause : inClauses.values()) { + if (clause != null) { + clause.reset(); + } + } + inClauses.clear(); + maxBatchSize = 0; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseColumn.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseColumn.java new file mode 100644 index 0000000..2364881 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseColumn.java @@ -0,0 +1,87 @@ +package com.lanternsoftware.util.dao.jdbc.preparedinstatement; + +import java.util.Iterator; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedParameter; + +/** + * Class that represents the data (column display and values) associated with an in-clause column Example: Batch Size: 2 + * select * from table where (record_id, str_val) in (('1','2'),('3','4')) InClauseColumn is an abstract representation + * for the pieces of information about a specific column within the in-clause: InClauseColumn Column Display: record_id + * Parameters: '1', '3' InClauseColumn Column Display: str_val Parameters: '2', '4' + */ +public class InClauseColumn { + private final String sqlColumnDisplay; + private final Queue parameters; + private final int size; + private Iterator currentParam; + + /** + * Default Constructor + * + * @param _sqlColumnDisplay + * - String representing the column's display. Note that this may not just be the column name, but may also + * represent the column's alias: 'record_id' or 't.record_id'. + * @param _parameters + * - a {@link Queue} of {@link PreparedParameter}. + */ + public InClauseColumn(String _sqlColumnDisplay, final Queue _parameters) { + sqlColumnDisplay = _sqlColumnDisplay; + if (_parameters != null) { + parameters = _parameters; + size = parameters.size(); + currentParam = parameters.iterator(); + } + else { + parameters = null; + size = 0; + currentParam = null; + } + } + + /** + * @return String representing the column's display + */ + public String getColumnDisplay() { + return sqlColumnDisplay; + } + + /** + * @return boolean; true if there are remaining {@link PreparedParameter}s in the queue + */ + public boolean hasNextParameter() { + if (parameters == null) + return false; + + return currentParam.hasNext(); + } + + /** + * @return the next {@link PreparedParameter}s in the queue; may return null if queue is empty + */ + public PreparedParameter getNextParameter() { + if (currentParam == null) + return null; + + return currentParam.next(); + } + + /** + * @return Integer representing the initial size of the queue (not current size) + */ + public int getParameterCnt() { + return size; + } + + /** + * Method that allows you re-use the column. This method will reset the {@link #hasNextParameter()} and + * {@link #getNextParameter()} methods. + */ + public void reset() { + if (parameters != null) + currentParam = parameters.iterator(); + else + currentParam = null; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseStatement.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseStatement.java new file mode 100644 index 0000000..89c45ef --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/InClauseStatement.java @@ -0,0 +1,211 @@ +package com.lanternsoftware.util.dao.jdbc.preparedinstatement; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedParameter; + +/** + * Class that represents an instance of batched parameters for all {@link InClause}s that are part of an SQL statement. + * Example: Batch Size: 2 select * from table where (record_id) in ('A','B','C') and str_val in('Y','Z') and dbl_val + * in(1, 2, 3) In this case, we will end up with 4 distinct statements to represent the data within the 3 in-clauses: + * Statement 1: Query: select * from table where (record_id) in (?,?) and str_val = in(?,?) and dbl_val in(?,?) + * Parameters: 1. Collection: Parameters: 'A', 'B' 2. Collection: Parameters: 'Y', + * 'Z' 3. Collection: Parameters: 1, 2 Statement 2: Query: select * from table where record_id = ? + * and str_val = in(?,?) and dbl_val in(?,?) Parameters: 1. Collection: Parameters: 'C' 2. + * Collection: Parameters: 'Y', 'Z' 3. Collection: Parameters: 1, 2 Statement 3: + * Query: select * from table where (record_id) in (?,?) and str_val = in(?,?) and dbl_val = ? Parameters: 1. + * Collection: Parameters: 'A', 'B' 2. Collection: Parameters: 'Y', 'Z' 3. + * Collection: Parameters: 3 Statement 4: Query: select * from table where (record_id) = ? and + * str_val = in(?,?) and dbl_val = ? Parameters: 1. Collection: Parameters: 'C' 2. + * Collection: Parameters: 'Y', 'Z' 3. Collection: Parameters: 3 + */ +public class InClauseStatement implements Cloneable { + public static final String IN_CLAUSE_EXPRESSION = "{in}"; + + private static final String IN_CLAUSE_REGEX = "\\{in\\}"; + private static final String QUESTION_MARK = "?"; + private static final String COMMA = ","; + private static final String LEFT_PARANTHESIS = "("; + private static final String RIGHT_PARANTHESIS = ")"; + private static final String IN_STATEMENT = " in "; + private static final String EQUALS = " = "; + private static final String SQL_NOT_EQUAL = "0 = 1"; + private static final String SQL_EQUAL = "1 = 1"; + + private Map parameters = new HashMap(); + + /** + * Method that sets a {@link InClauseBatchedParameter} (representing the data from an {@link InClause} for a specific + * index + * + * @param _startIdx + * - Integer representing the {@link InClause}'s index + * @param _parameter + * - {@link InClauseBatchedParameter} (representing the data from an {@link InClause} + */ + void setNextParameter(int _startIdx, InClauseBatchedParameter _parameter) { + if (_parameter == null) + return; + + parameters.put(_startIdx, _parameter); + } + + /** + * {@inheritDoc} + */ + public InClauseStatement clone() { + InClauseStatement statement = new InClauseStatement(); + + Iterator> iter = parameters.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry = iter.next(); + if (entry == null) + continue; + + statement.setNextParameter(entry.getKey(), entry.getValue()); + } + + return statement; + } + + /** + * @return a Map of index to {@link InClauseBatchedParameter} (representing the data from an {@link InClause} + */ + Map getParameters() { + return parameters; + } + + /** + * Method to generate the column display for an in-clause. Depending on the number of columns, the format may vary: 1 + * column: (column) 2 or more columns: (column_one, column_two) + * + * @param _arrColumns + * - An Array of {@link InClauseColumn}s needed to build the display + * @return String representing the in-clause output + */ + String buildColumnDisplay(InClauseColumn[] _arrColumns) { + StringBuilder sbColumns = new StringBuilder(); + for (InClauseColumn column : _arrColumns) { + if (column == null) + continue; + + if (sbColumns.length() > 0) + sbColumns.append(COMMA); + sbColumns.append(column.getColumnDisplay()); + } + + StringBuilder sbDisplay = new StringBuilder(); + sbDisplay.append(LEFT_PARANTHESIS); + sbDisplay.append(sbColumns.toString()); + sbDisplay.append(RIGHT_PARANTHESIS); + sbDisplay.append(IN_STATEMENT); + return sbDisplay.toString(); + } + + /** + * Generates a column display for special cases where there is only 1 column. Special Case: A single + * {@link InClauseColumn} with a single {@link PreparedParameter}s is set. In this situation, the in-clause will be + * optimized to be an equals (i.e. = ? and not in (?)). The string will format to: column = + * + * @param _columns + * @return + */ + String buildEqualDisplay(InClauseColumn[] _columns) { + StringBuilder sbDisplay = new StringBuilder(); + int nLength = _columns.length; + + // special case: if there is only 1 column with 1 value in the parameter, we will format the + // select as column = ?, rather than column in (?) + if (nLength == 1) { + sbDisplay.append(_columns[0].getColumnDisplay()); + sbDisplay.append(EQUALS); + } + return sbDisplay.toString(); + } + + /** + * Method to generate a dynamic SQL-query from an SQL query using in-clause delimiters (i.e {in}). Example: Batch Size: + * 2 select * from table where {in} and {in} and {in} In-Clause 1 Data: record_id - 'A','B','C' In-Clause 2 Data: + * str_val - 'Y','Z' In-Clause 3 Data: dbl_val - 1, 2, 3 Will return the following formatted queries: Statement 1: + * Query: select * from table where (record_id) in (?,?) and str_val = in(?,?) and dbl_val in(?,?) Parameters: 1. + * Collection: Parameters: 'A', 'B' 2. Collection: Parameters: 'Y', 'Z' 3. + * Collection: Parameters: 1, 2 Statement 2: Query: select * from table where record_id = ? and + * str_val = in(?,?) and dbl_val in(?,?) Parameters: 1. Collection: Parameters: 'C' 2. + * Collection: Parameters: 'Y', 'Z' 3. Collection: Parameters: 1, 2 Statement 3: + * Query: select * from table where (record_id) in (?,?) and str_val = in(?,?) and dbl_val = ? Parameters: 1. + * Collection: Parameters: 'A', 'B' 2. Collection: Parameters: 'Y', 'Z' 3. + * Collection: Parameters: 3 Statement 4: Query: select * from table where (record_id) = ? and + * str_val = in(?,?) and dbl_val = ? Parameters: 1. Collection: Parameters: 'C' 2. + * Collection: Parameters: 'Y', 'Z' 3. Collection: Parameters: 3 + * + * @param _query + * - String representing the in-clause delimited SQL statement + * @return a String representing the dynamically modified SQL statement + */ + String formatQuery(String _query) { + if (parameters.size() == 0) + return _query; + + String sModifiedQuery = _query; + Iterator iter = parameters.values().iterator(); + while (iter.hasNext()) { + InClauseBatchedParameter parameter = iter.next(); + if (parameter == null) + return ""; + + int nColumnCnt = parameter.getColumnCnt(); + int nCurColumnCnt = 0; + + StringBuilder sbParameters = new StringBuilder(); + + // special case: if there are no parameters, check to see if we should return a true or false for the in statement + // i.e. 0 = 1 or 1 = 1 + if (parameter.getBatchParameterCnt() == 0) { + if (parameter.isReturnAllIfEmpty()) + sbParameters.append(SQL_EQUAL); + else + sbParameters.append(SQL_NOT_EQUAL); + } + + // special case: if there is only 1 column with 1 value in the parameter, we will format the + // select as column = ?, rather than column in (?) + else if (nColumnCnt == 1 && parameter.getBatchParameterCnt() == 1) { + sbParameters.append(buildEqualDisplay(parameter.getColumns())); + sbParameters.append(QUESTION_MARK); + } + + // normal replacement case + else { + + sbParameters.append(buildColumnDisplay(parameter.getColumns())); + sbParameters.append(LEFT_PARANTHESIS); + + if (nColumnCnt > 1) + sbParameters.append(LEFT_PARANTHESIS); + + for (int nCurIdx = 0; nCurIdx < parameter.getBatchParameterCnt(); ++nCurIdx) { + if (nColumnCnt > 1 && nCurColumnCnt == nColumnCnt) { + sbParameters.append(RIGHT_PARANTHESIS); + sbParameters.append(COMMA); + sbParameters.append(LEFT_PARANTHESIS); + nCurColumnCnt = 0; + } + else if (nCurIdx > 0) + sbParameters.append(COMMA); + sbParameters.append(QUESTION_MARK); + ++nCurColumnCnt; + } + + if (nColumnCnt > 1) + sbParameters.append(RIGHT_PARANTHESIS); + + sbParameters.append(RIGHT_PARANTHESIS); + } + + sModifiedQuery = sModifiedQuery.replaceFirst(IN_CLAUSE_REGEX, sbParameters.toString()); + } + return sModifiedQuery; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/PreparedInStatement.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/PreparedInStatement.java new file mode 100644 index 0000000..d685723 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedinstatement/PreparedInStatement.java @@ -0,0 +1,507 @@ +package com.lanternsoftware.util.dao.jdbc.preparedinstatement; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.AbstractJdbcProxy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedBoolean; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedByte; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedBytes; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedDouble; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedEnum; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedFloat; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedInBatchedParameter; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedInt; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedLong; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedNull; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedObject; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedParameter; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedShort; +import com.lanternsoftware.util.dao.jdbc.preparedparameter.PreparedString; + +/** + * Class that will execute batched {@link PreparedStatement}s supporting {@link InClause}(s). + *

+ * An in-clause can be defined using an in-clause delimiter: {in}. This delimiter will be replaced by the + * appropriate column displays and values. Because the number of parameters within each in-clause is dynamic, the query + * will be restructured every time it's executed to reflect the correct number of ?-delimiters for parameters. This + * syntax supports both single and multi-column in-clauses as well as multiple in-clauses per select. The batch size for + * the overall SQL statement may be customized. Depending on the number of columns and parameters that are passed in for + * each in-clause, several special cases may occur: + *

+ * Special Case 1: The batch-size is less than the number of {@link InClauseColumn}s In this situation, + * the batch size will be adjusted to the number of columns. For example, if you set the batch size to 1 and you pass in + * 2 columns, your optimized batch size will be 2.
+ * Special Case 2: No {@link PreparedParameter}s are set on any {@link InClauseColumn}s In this + * situation, the behavior will depend on the ReturnAllIfEmpty flag passed in during the constructor. If true, the + * in-clause will be replaced with a '1=1', meaning all rows would be returned; false will replace with + * '1=0'.
+ * Special Case 3: A single {@link InClauseColumn} with a single {@link PreparedParameter}s is set. In + * this situation, the in-clause will be optimized to be an equals (i.e. =? and not in(?)). + *

+ * Some thought must be given when constructing queries and using this utility. If you are using a single in-clause, the + * parameters will be batched into optimized sizes (i.e. if you pass in a list of 76, it may be batched to a list of + * 100). As a result, the resulting {@link ResultSet}s may not produce unique rows across multiple {@link #next()} + * calls. + *

+ * If you are using multiple in-clauses, you may end up executing several queries to accommodate all combinations of + * parameters. This can be minimized by using appropriate batch-sizes (typically the default). Using or-statements + * between in-clauses may produce multiple queries for the same data (the queries will now be batched).
+ * Example:
+ * Batch Size: 2
+ * Original Query: + * select * from table where (record_id) in ('A','B','C') and str_val in('Y','Z') and dbl_val in(1, 2, 3) + *
+ * In-Clause Delimited Query: select * from table where {in} and {in} and {in}
+ *
+ * In this case, we will end up with 4 distinct statements to represent the data within the 3 in-clauses:
+ * Statement 1:
+ * Query: select * from table where (record_id) in (?,?) and str_val = in(?,?) and dbl_val in(?,?)
+ * Parameters:
+ * 1. Collection: Parameters: 'A', 'B'
+ * 2. Collection: Parameters: 'Y', 'Z'
+ * 3. Collection: Parameters: 1, 2
+ *
+ * Statement 2:
+ * Query: select * from table where record_id = ? and str_val = in(?,?) and dbl_val in(?,?)
+ * Parameters:
+ * 1. Collection: Parameters: 'C'
+ * 2. Collection: Parameters: 'Y', 'Z'
+ * 3. Collection: Parameters: 1, 2
+ *
+ * Statement 3:
+ * Query: select * from table where (record_id) in (?,?) and str_val = in(?,?) and dbl_val = ?
+ * Parameters:
+ * 1. Collection: Parameters: 'A', 'B'
+ * 2. Collection: Parameters: 'Y', 'Z'
+ * 3. Collection: Parameters: 3
+ *
+ * Statement 4:
+ * Query: select * from table where (record_id) = ? and str_val = in(?,?) and dbl_val = ?
+ * Parameters:
+ * 1. Collection: Parameters: 'C'
+ * 2. Collection: Parameters: 'Y', 'Z'
+ * 3. Collection: Parameters: 3 + *

+ * A Note on {@link PreparedParameter}s: The current functionality of PreparedInStatement (like the + * current SQL standards) does not support null-parameters in a "in()" query.
+ * i.e. select * from table where (record_id) in ('1', '2', NULL) would not get you rows where the + * record_id is null.
+ * To do this you would need to manually format your query to something like: + * select * from table where record_id IS NULL or {in}, which would then get translated by + * PreparedInStatement to: select * from table where record_id IS NULL or (record_id) in ('1', '2'). + */ +public class PreparedInStatement { + private static final Logger LOG = LoggerFactory.getLogger(PreparedInStatement.class); + private final Map m_mapParameters = new HashMap(); + private final InClauseBuilder clauseBuilder = new InClauseBuilder(); + private final AbstractJdbcProxy proxy; + private final Connection connection; + private final String query; + private ResultSet resultSet; + + private Collection statements; + + /** + * Default Constructor + * + * @param _query + * - SQL query to execute + * @param _proxy + * - {@link AbstractJdbcProxy} that should be used to retrieve the {@link PreparedStatement}s. + * @throws SQLException + */ + public PreparedInStatement(String _query, AbstractJdbcProxy _proxy) throws SQLException { + query = _query; + proxy = _proxy; + connection = proxy.getConnection(); + statements = null; + + if (proxy == null) + throw new SQLException("invalid_connection"); + } + + /** + * Sets the designated parameter to the given {@link Boolean} value. The driver converts this to an SQL BIT value when + * it sends it to the database. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _val + * - {@link Boolean} value + */ + public void setBoolean(int _parameterIndex, boolean _val) { + setParameter(_parameterIndex, new PreparedBoolean(_val)); + } + + /** + * Sets the designated parameter to the given {@link byte}. The driver converts this to an SQL TINYINT value when it + * sends it to the database. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _byte + * - {@link byte} value + */ + public void setByte(int _parameterIndex, byte _byte) { + setParameter(_parameterIndex, new PreparedByte(_byte)); + } + + /** + * Sets the designated parameter to the given bytes. The driver converts this to an SQL VARBINARY or + * LONGVARBINARY (depending on the argument's size relative to the driver's limits on VARBINARY values) when it sends it + * to the database. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _bytes + * - values + */ + public void setBytes(int _parameterIndex, byte[] _bytes) { + setParameter(_parameterIndex, new PreparedBytes(_bytes)); + } + + /** + * Sets the designated parameter to the given Java double value. The driver converts this to an SQL DOUBLE value when it + * sends it to the database. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _val + * - {@link double} value + */ + public void setDouble(int _parameterIndex, double _val) { + setParameter(_parameterIndex, new PreparedDouble(_val)); + } + + /** + * Sets the designated parameter to the given Java float value. The driver converts this to an SQL FLOAT value when it + * sends it to the database. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _val + * - {@link float} value + */ + public void setFloat(int _parameterIndex, float _val) { + setParameter(_parameterIndex, new PreparedFloat(_val)); + } + + /** + * Sets the designated parameter to the given Java int value. The driver converts this to an SQL INTEGER value when it + * sends it to the database. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _val + * - {@link int} value + */ + public void setInt(int _parameterIndex, int _val) { + setParameter(_parameterIndex, new PreparedInt(_val)); + } + + /** + * Sets the designated parameter to the given Java long value. The driver converts this to an SQL BIGINT value when it + * sends it to the database. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _val + * - {@link long} value + */ + public void setLong(int _parameterIndex, long _val) { + setParameter(_parameterIndex, new PreparedLong(_val)); + } + + /** + * Sets the designated parameter to SQL NULL. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _sqlType + * - Integer representing the SQL type code defined in {@link java.sql.Types} + */ + public void setNull(int _parameterIndex, int _sqlType) { + setParameter(_parameterIndex, new PreparedNull(_sqlType)); + } + + /** + * Sets the designated parameter to SQL NULL. This version of the method setNull should be used for user-defined types + * and REF type parameters. Examples of user-defined types include: STRUCT, DISTINCT, JAVA_OBJECT, and named array + * types. Note: To be portable, applications must give the SQL type code and the fully-qualified SQL + * type name when specifying a NULL user-defined or REF parameter. In the case of a user-defined type the name is the + * type name of the parameter itself. For a REF parameter, the name is the type name of the referenced type. If a JDBC + * driver does not need the type code or type name information, it may ignore it. Although it is intended for + * user-defined and Ref parameters, this method may be used to set a null parameter of any JDBC type. If the parameter + * does not have a user-defined or REF type, the given typeName is ignored. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _sqlType + * - Integer representing the SQL type code defined in {@link java.sql.Types} + * @param _typeName + * - String representing the fully-qualified name of an SQL user-defined type; ignored if the parameter is + * not a user-defined type or REF + */ + public void setNull(int _parameterIndex, int _sqlType, String _typeName) { + setParameter(_parameterIndex, new PreparedNull(_sqlType, _typeName)); + } + + /** + * Sets the designated parameter to the given Java short value. The driver converts this to an SQL SMALLINT value when + * it sends it to the database. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _val + * - {@link short} value + */ + public void setShort(int _parameterIndex, short _val) { + setParameter(_parameterIndex, new PreparedShort(_val)); + } + + /** + * Sets the designated parameter to the given {@link String} value. The driver converts this to an SQL VARCHAR or + * LONGVARCHAR value (depending on the argument's size relative to the driver's limits on VARCHAR values) when it sends + * it to the database. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _val + * - {@link String} value + */ + public void setString(int _parameterIndex, String _val) { + setParameter(_parameterIndex, new PreparedString(_val)); + } + + public void setObject(int _parameterIndex, Object _object) { + setParameter(_parameterIndex, new PreparedObject(_object)); + } + + public void setParameter(int _parameterIndex, PreparedParameter _parameter) { + m_mapParameters.put(_parameterIndex, _parameter); + } + + /** + * Sets a Collection of {@link InClauseColumn}s, making up an in-clause. The parameters contained in the + * {@link InClauseColumn}s will be batched appropriately before execution. + *

+ * Note that if the list of parameters (contained within the {@link InClauseColumn}s are empty), the in-clause will be + * replaced with a '1 = 0'. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _columns + * - a variable list of {@link InClauseColumn}s that make up the in-clause + * @throws SQLException + * If at least one InClauseColumn is not given or if the size of each InClauseColumn parameter count is not + * the same. + */ + public void setInClause(int _parameterIndex, InClauseColumn... _columns) throws SQLException { + setInClause(_parameterIndex, false, _columns); + } + + @SuppressWarnings("rawtypes") + public void setInClause(int _parameterIndex, String _columnDisplay, Collection _values) throws SQLException { + Queue queueParameters = new LinkedList(); + for (Object value : _values) { + if (value instanceof String) + queueParameters.add(new PreparedString((String) value, false)); + else if (value instanceof Boolean) + queueParameters.add(new PreparedBoolean((Boolean) value)); + else if (value instanceof Byte) + queueParameters.add(new PreparedByte((Byte) value)); + else if (value instanceof Double) + queueParameters.add(new PreparedDouble((Double) value)); + else if (value instanceof Enum) + queueParameters.add(new PreparedEnum((Enum) value)); + else if (value instanceof Float) + queueParameters.add(new PreparedFloat((Float) value)); + else if (value instanceof Integer) + queueParameters.add(new PreparedInt((Integer) value)); + else if (value instanceof Long) + queueParameters.add(new PreparedLong((Long) value)); + else if (value instanceof Short) + queueParameters.add(new PreparedShort((Short) value)); + } + setInClause(_parameterIndex, false, new InClauseColumn(_columnDisplay, queueParameters)); + } + + /** + * Sets a Collection of {@link InClauseColumn}s, making up an in-clause. The parameters contained in the + * {@link InClauseColumn}s will be batched appropriately before execution. + * + * @param _parameterIndex + * - Integer representing the index of the parameter in the SQL {@link PreparedStatement} + * @param _returnAllIfEmpty + * - a boolean flag that determines the resulting behavior if the {@link InClauseColumn}s are empty. If true, + * the in-clause will be replaced with a '1=1', meaning all rows would be returned; false will replace with + * '1=0' + * @param _columns + * - a variable list of {@link InClauseColumn}s that make up the in-clause + * @throws SQLException + * If at least one InClauseColumn is not given or if the size of each InClauseColumn parameter count is not + * the same. + */ + public void setInClause(int _parameterIndex, boolean _returnAllIfEmpty, InClauseColumn... _columns) throws SQLException { + if (_columns == null || (_columns.length > 0 && _columns[0] == null)) { + throw new SQLException("invalid_in_clause_columns"); + } + int size = 0; + if (_columns.length > 0) { + size = _columns[0].getParameterCnt(); + for (InClauseColumn column : _columns) { + if (column == null) + continue; + + if (size != column.getParameterCnt()) + throw new SQLException("invalid_parameter_cnt"); + } + } + clauseBuilder.addClause(new InClause(_parameterIndex, _returnAllIfEmpty, _columns)); + } + + /** + * Method which defines a maximum batch size used to calculate in-clause batch sizes. + * + * @param _batchSize + */ + public void setMaxBatchSize(int _batchSize) { + clauseBuilder.setMaxBatchSize(_batchSize); + } + + /** + * Method which tells if there are remaining batched {@link PreparedStatement}s left to be executed. + * + * @return boolean; true if there are more {@link PreparedStatement}s left to be executed. + */ + public boolean hasNext() { + if (statements == null) + statements = clauseBuilder.buildStatements(); + return !statements.isEmpty(); + } + + private PreparedStatement getNextPopulatedStatement() throws SQLException { + if (statements == null) + throw new SQLException("invalid_statements"); + + String sQuery = ""; + if (!statements.isEmpty()) { + InClauseStatement inStatement = statements.iterator().next(); + if (inStatement != null) { + sQuery = inStatement.formatQuery(query); + Map mapParameters = inStatement.getParameters(); + if (mapParameters != null && !mapParameters.isEmpty()) { + Iterator> iter = mapParameters.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry = iter.next(); + if (entry == null) + continue; + + m_mapParameters.put(entry.getKey(), new PreparedInBatchedParameter(entry.getValue())); + } + } + + statements.remove(inStatement); + } + } + else + sQuery = query; + + PreparedStatement statement = proxy.getPreparedStatement(connection, sQuery); + int idx = 1; + Iterator iter = m_mapParameters.values().iterator(); + while (iter.hasNext()) { + PreparedParameter parameter = iter.next(); + if (parameter == null) + continue; + + idx = parameter.addToStatement(idx, statement); + } + return statement; + } + + /** + * Method that will call executeQuery() the next batched {@link PreparedStatement}. This method will also close the + * previous {@link ResultSet} that was returned. Note that the consumer is responsible for closing the last + * {@link ResultSet} (when next() is not called again). It's important to remember that this will execute the current + * batched {@link PreparedStatement}; others will be executed. Therefore, you are not guaranteed that the data being + * returned is unique across multiple calls to next(). It is required to call {@link #hasNext()} before executing this + * method, otherwise a {@link SQLException} will be thrown. + * + * @return the next {@link ResultSet} to be evaluated + * @throws SQLException + * If a database access error occurs or the SQL statement is invalid. + */ + public ResultSet next() throws SQLException { + if (resultSet != null) + resultSet.close(); + PreparedStatement statement = null; + try { + statement = getNextPopulatedStatement(); + resultSet = statement.executeQuery(); + statement.clearWarnings(); + statement.clearBatch(); + statement.clearParameters(); + return resultSet; + } + catch (SQLException e) { + if (connection != null) + connection.close(); + if (statement != null) + statement.close(); + if (resultSet != null) + resultSet.close(); + throw e; + } + } + + /** + * Method that will call executeUpdate() on all batched {@link PreparedStatement}s. + * + * @return an Integer representing the affected row count (across all batched {@link PreparedStatement}s). + * @throws SQLException + * If a database access error occurs or the SQL statement is invalid. + */ + public int executeUpdate() throws SQLException { + int rowCnt = 0; + while (hasNext()) { + PreparedStatement statement = getNextPopulatedStatement(); + rowCnt += statement.executeUpdate(); + statement.close(); + } + connection.close(); + return rowCnt; + } + + /** + * Method that will clear out any remaining {@link PreparedStatement}s as well as close the last-used {@link ResultSet}. + * Clear should be called if the statement is being reused, in between executions. + * + * @throws SQLException + */ + public void clear() throws SQLException { + try { + statements = null; + clauseBuilder.reset(); + m_mapParameters.clear(); + if (resultSet != null) + resultSet.close(); + resultSet = null; + } + catch (Throwable t) { + } + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedBoolean.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedBoolean.java new file mode 100644 index 0000000..7cf3841 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedBoolean.java @@ -0,0 +1,55 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link Boolean}s + */ +public class PreparedBoolean implements PreparedParameter { + private final Boolean val; + + public PreparedBoolean(Boolean _val) { + val = _val; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) + _statement.setBoolean(_startIdx, val); + else + _statement.setNull(_startIdx, Types.TINYINT); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link Boolean}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link Boolean}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Boolean value : _values) + queueParameters.add(new PreparedBoolean(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedByte.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedByte.java new file mode 100644 index 0000000..e65acc5 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedByte.java @@ -0,0 +1,61 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link Byte} + */ +public class PreparedByte implements PreparedParameter { + private final Byte val; + + /** + * Default Constructor + * + * @param _val + * - {@link Byte} + */ + public PreparedByte(Byte _val) { + val = _val; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) + _statement.setByte(_startIdx, val); + else + _statement.setNull(_startIdx, Types.TINYINT); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link Byte}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link Byte}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Byte value : _values) + queueParameters.add(new PreparedByte(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedBytes.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedBytes.java new file mode 100644 index 0000000..f6c318a --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedBytes.java @@ -0,0 +1,61 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link byte}s + */ +public class PreparedBytes implements PreparedParameter { + private final byte[] val; + + /** + * Default Constructor + * + * @param _val + * - {@link byte}s + */ + public PreparedBytes(byte[] _val) { + val = _val; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) + _statement.setBytes(_startIdx, val); + else + _statement.setNull(_startIdx, Types.VARBINARY); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link byte}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link byte}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (byte[] value : _values) + queueParameters.add(new PreparedBytes(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedDouble.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedDouble.java new file mode 100644 index 0000000..cca2cdf --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedDouble.java @@ -0,0 +1,61 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link Double}s + */ +public class PreparedDouble implements PreparedParameter { + private final Double val; + + /** + * Default Constructor + * + * @param _val + * - {@link Double} + */ + public PreparedDouble(Double _val) { + val = _val; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) + _statement.setDouble(_startIdx, val); + else + _statement.setNull(_startIdx, Types.DOUBLE); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link Double}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link Double}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Double value : _values) + queueParameters.add(new PreparedDouble(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedEnum.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedEnum.java new file mode 100644 index 0000000..1320daa --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedEnum.java @@ -0,0 +1,75 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.EnumSet; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link String}s + */ +public class PreparedEnum implements PreparedParameter { + private final Enum val; + + public PreparedEnum(Enum _val) { + val = _val; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) + _statement.setString(_startIdx, val.name()); + else + _statement.setNull(_startIdx, Types.VARCHAR); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link String}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * - Collection of {@link String}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection> _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Enum value : _values) + queueParameters.add(new PreparedEnum(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link String}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * - Collection of {@link String}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, EnumSet _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Enum value : _values) + queueParameters.add(new PreparedEnum(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedFloat.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedFloat.java new file mode 100644 index 0000000..4d465ff --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedFloat.java @@ -0,0 +1,61 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link Float}s + */ +public class PreparedFloat implements PreparedParameter { + private final Float val; + + /** + * Default Constructor + * + * @param _val + * - {@link Float} + */ + public PreparedFloat(Float _val) { + val = _val; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) + _statement.setFloat(_startIdx, val); + else + _statement.setNull(_startIdx, Types.FLOAT); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link Float}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link Float}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Float value : _values) + queueParameters.add(new PreparedFloat(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedInBatchedParameter.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedInBatchedParameter.java new file mode 100644 index 0000000..4dfc3dc --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedInBatchedParameter.java @@ -0,0 +1,46 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedList; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseBatchedParameter; + +/** + * Implementation of {@link PreparedParameter} for {@link InClauseBatchedParameter}s + */ +public class PreparedInBatchedParameter implements PreparedParameter { + private final InClauseBatchedParameter batchedParameter; + + /** + * Default Constructor + * + * @param _parameter + * - {@link InClauseBatchedParameter} + */ + public PreparedInBatchedParameter(InClauseBatchedParameter _parameter) { + batchedParameter = _parameter; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null || batchedParameter == null) + return _startIdx; + + LinkedList listParameters = batchedParameter.getParameters(); + if (listParameters == null || listParameters.isEmpty()) + return _startIdx; + + int nIdx = _startIdx; + for (PreparedParameter parameter : listParameters) { + if (parameter == null) + continue; + parameter.addToStatement(nIdx, _statement); + ++nIdx; + } + + return nIdx; + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedInt.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedInt.java new file mode 100644 index 0000000..9027df9 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedInt.java @@ -0,0 +1,85 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link Integer}s + */ +public class PreparedInt implements PreparedParameter { + private final Integer val; + + /** + * Default Constructor + * + * @param _val + * - {@link Integer} + */ + public PreparedInt(Integer _val) { + val = _val; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) + _statement.setInt(_startIdx, val); + else + _statement.setNull(_startIdx, Types.INTEGER); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link Integer}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link Integer}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Integer value : _values) + queueParameters.add(new PreparedInt(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link Enum}s. The Integer value will be + * generated for each enum by using the enum's {@link Enum#ordinal()} value. + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link Enum}s + * @return {@link InClauseColumn} containing integers + */ + public static InClauseColumn getInClauseEnums(String _columnDisplay, Collection> _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Enum value : _values) { + if (value != null) + queueParameters.add(new PreparedInt(value.ordinal())); + else + queueParameters.add(new PreparedInt(null)); + } + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedLong.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedLong.java new file mode 100644 index 0000000..de902a4 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedLong.java @@ -0,0 +1,96 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.GregorianCalendar; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link Long}s + */ +public class PreparedLong implements PreparedParameter { + private final Long val; + + /** + * Constructor taking a long value + * + * @param _val + * - {@link Long} + */ + public PreparedLong(Long _val) { + val = _val; + } + + /** + * Constructor taking a GregorianCalendar that will be converted to a long using the + * {@link GregorianCalendar#getTimeInMillis()} value. + * + * @param _cal + * - {@link GregorianCalendar} + */ + public PreparedLong(GregorianCalendar _cal) { + if (_cal == null) + val = null; + else + val = _cal.getTimeInMillis(); + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) + _statement.setLong(_startIdx, val); + else + _statement.setNull(_startIdx, Types.BIGINT); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link Long}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link Long}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Long value : _values) + queueParameters.add(new PreparedLong(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link GregorianCalendar}s. The + * GregorianCalendar will be converted to a {@link Long} using the {@link GregorianCalendar#getTimeInMillis()} value. + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link GregorianCalendar}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClauseCalendars(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (GregorianCalendar value : _values) + queueParameters.add(new PreparedLong(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedNull.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedNull.java new file mode 100644 index 0000000..98e1ee3 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedNull.java @@ -0,0 +1,53 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Implementation of {@link PreparedParameter} for SQL-Nulls + */ +public class PreparedNull implements PreparedParameter { + private final int sqlType; + private final String typeName; + + /** + * Default Constructor + * + * @param _sqlType + * - Integer representing the SQL type code defined in {@link java.sql.Types} + */ + public PreparedNull(int _sqlType) { + sqlType = _sqlType; + typeName = null; + } + + /** + * Type Name Constructor + * + * @param _sqlType + * - Integer representing the SQL type code defined in {@link java.sql.Types} + * @param _typeName + * - String representing the fully-qualified name of an SQL user-defined type; ignored if the parameter is + * not a user-defined + */ + public PreparedNull(int _sqlType, String _typeName) { + sqlType = _sqlType; + typeName = _typeName; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (typeName == null) + _statement.setNull(_startIdx, sqlType); + else + _statement.setNull(_startIdx, sqlType, typeName); + + return ++_startIdx; + } + +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedObject.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedObject.java new file mode 100644 index 0000000..b001c85 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedObject.java @@ -0,0 +1,43 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link Short}s + */ +public class PreparedObject implements PreparedParameter { + private final Object m_val; + + public PreparedObject(Object _val) { + m_val = _val; + } + + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (m_val != null) + _statement.setObject(_startIdx, m_val); + else + _statement.setNull(_startIdx, Types.OTHER); + + return ++_startIdx; + } + + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Object value : _values) + queueParameters.add(new PreparedObject(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedParameter.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedParameter.java new file mode 100644 index 0000000..cc9f3c5 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedParameter.java @@ -0,0 +1,22 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Interface representing a {@link PreparedStatement}'s parameters + */ +public interface PreparedParameter { + /** + * Method that will build add the parameter to a {@link PreparedStatement} + * + * @param _startIdx + * - integer representing the starting index of the parameter + * @param _statement + * - {@link PreparedStatement} + * @return an int, representing the ending index of the parameter + * @throws {@link + * SQLException} + */ + int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException; +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedShort.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedShort.java new file mode 100644 index 0000000..6aedc16 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedShort.java @@ -0,0 +1,61 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link Short}s + */ +public class PreparedShort implements PreparedParameter { + private final Short val; + + /** + * Default Constructor + * + * @param _val + * - {@link Short} + */ + public PreparedShort(Short _val) { + val = _val; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) + _statement.setShort(_startIdx, val); + else + _statement.setNull(_startIdx, Types.SMALLINT); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link Short}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * Collection of {@link Short}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (Short value : _values) + queueParameters.add(new PreparedShort(value)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedString.java b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedString.java new file mode 100644 index 0000000..e553de5 --- /dev/null +++ b/util/lantern-util-dao/src/main/java/com/lanternsoftware/util/dao/jdbc/preparedparameter/PreparedString.java @@ -0,0 +1,101 @@ +package com.lanternsoftware.util.dao.jdbc.preparedparameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Locale; +import java.util.Queue; +import java.util.regex.Pattern; + +import com.lanternsoftware.util.dao.jdbc.preparedinstatement.InClauseColumn; + +/** + * Implementation of {@link PreparedParameter} for {@link String}s + */ +public class PreparedString implements PreparedParameter { + private final String val; + private final boolean isKey; + private static final Pattern KEY_PATTERN = Pattern.compile("[^A-Za-z0-9]+"); + + /** + * Default Constructor + * + * @param _val + * - {@link String} + */ + public PreparedString(String _val) { + this(_val, false); + } + + /** + * Default Constructor + * + * @param _val + * - {@link String} + * @param _isKey + * - boolean; if true the {@link String} should be converted to upper case alpha numeric before being saved + * the {@link PreparedStatement} + */ + public PreparedString(String _val, boolean _isKey) { + val = _val; + isKey = _isKey; + } + + /** + * {@inheritDoc} + */ + public int addToStatement(int _startIdx, PreparedStatement _statement) throws SQLException { + if (_statement == null) + return _startIdx; + + if (val != null) { + if (isKey) { + String upper = val.toUpperCase(Locale.ENGLISH); + _statement.setString(_startIdx, KEY_PATTERN.matcher(upper).replaceAll("")); + } + else + _statement.setString(_startIdx, val); + } + else + _statement.setNull(_startIdx, Types.VARCHAR); + + return ++_startIdx; + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link String}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * - Collection of {@link String}s + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values) { + return getInClause(_columnDisplay, _values, false); + } + + /** + * Static method to produce an {@link InClauseColumn} for a Collection of {@link String}s + * + * @param _columnDisplay + * - {@link InClauseColumn} display + * @param _values + * - Collection of {@link String}s + * @param _isKeyColumn + * - boolean; if true the {@link String} should be converted to upper case alpha numeric before being saved + * Collection + * @return {@link InClauseColumn} + */ + public static InClauseColumn getInClause(String _columnDisplay, Collection _values, boolean _isKeyColumn) { + if (_values == null) + return null; + + Queue queueParameters = new LinkedList(); + for (String value : _values) + queueParameters.add(new PreparedString(value, _isKeyColumn)); + return new InClauseColumn(_columnDisplay, queueParameters); + } +} diff --git a/util/lantern-util-dao/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer b/util/lantern-util-dao/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer new file mode 100644 index 0000000..6043f50 --- /dev/null +++ b/util/lantern-util-dao/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer @@ -0,0 +1 @@ +com.lanternsoftware.util.dao.jdbc.dao.JdbcConfigSerializer diff --git a/util/lantern-util-http/pom.xml b/util/lantern-util-http/pom.xml new file mode 100644 index 0000000..bb77d09 --- /dev/null +++ b/util/lantern-util-http/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + com.lanternsoftware.util + lantern-util-http + lantern-util-http + 1.0.0 + jar + + + + ${project.groupId} + lantern-util-common + ${project.version} + + + org.apache.httpcomponents + httpclient + 4.5.3 + + + org.apache.httpcomponents + httpcore + 4.4.8 + + + org.apache.httpcomponents + httpmime + 4.5.3 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar + + + + + + + diff --git a/util/lantern-util-http/src/main/java/com/lanternsoftware/util/http/HttpPool.java b/util/lantern-util-http/src/main/java/com/lanternsoftware/util/http/HttpPool.java new file mode 100644 index 0000000..313e521 --- /dev/null +++ b/util/lantern-util-http/src/main/java/com/lanternsoftware/util/http/HttpPool.java @@ -0,0 +1,107 @@ +package com.lanternsoftware.util.http; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.CookieStore; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.protocol.HttpContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.NullUtils; + +public class HttpPool { + private static final Logger LOG = LoggerFactory.getLogger(HttpPool.class); + + private final RequestConfig requestConfig; + private final CookieStore cookieStore = new BasicCookieStore(); + private final ConnectionKeepAliveStrategy keepAliveStrategy; + private final PoolingHttpClientConnectionManager connectionManager; + + public HttpPool(int _maxTotalConnections, int _maxPerRoute) { + this(_maxTotalConnections, _maxPerRoute, 10000, 5000, 10000); + } + + public HttpPool(int _maxTotalConnections, int _maxPerRoute, int _socketTimeoutMs, int _connectTimeoutMs, int _connectionRequestTimeoutMs) { + requestConfig = RequestConfig.custom().setSocketTimeout(_socketTimeoutMs).setConnectTimeout(_connectTimeoutMs).setConnectionRequestTimeout(_connectionRequestTimeoutMs).setCookieSpec(CookieSpecs.STANDARD).build(); + keepAliveStrategy = (HttpResponse response, HttpContext context) -> 0; + connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(_maxTotalConnections); + connectionManager.setDefaultMaxPerRoute(_maxPerRoute); + } + + public void shutdown() { + connectionManager.shutdown(); + } + + public CloseableHttpResponse execute(HttpUriRequest _request) { + try { + return getClient().execute(_request); + } + catch (IOException _e) { + LOG.error("Failed to make http request to " + _request.getURI().toString(), _e); + return null; + } + } + + public CloseableHttpResponse execute(HttpUriRequest _request, HttpContext _context) { + try { + return getClient().execute(_request, _context); + } + catch (IOException _e) { + LOG.error("Failed to make http request to " + _request.getURI().toString(), _e); + return null; + } + } + + private CloseableHttpClient getClient() { + return HttpClients.custom().setConnectionManager(connectionManager).setDefaultCookieStore(cookieStore).setDefaultRequestConfig(requestConfig).setKeepAliveStrategy(keepAliveStrategy).build(); + } + + public String executeToString(HttpUriRequest _request) { + return NullUtils.toString(executeToByteArray(_request)); + } + + public byte[] executeToByteArray(HttpUriRequest _request) { + InputStream is = null; + CloseableHttpResponse resp = null; + try { + resp = execute(_request); + if ((resp.getStatusLine().getStatusCode() < 200) || (resp.getStatusLine().getStatusCode() >= 300)) { + LOG.error("Failed to make http request to " + _request.getURI().toString() + ". Status code: " + resp.getStatusLine().getStatusCode()); + return null; + } + HttpEntity entity = resp.getEntity(); + if (entity != null) { + is = entity.getContent(); + return IOUtils.toByteArray(is); + } + return null; + } + catch (IOException _e) { + LOG.error("Failed to make http request to " + _request.getURI().toString(), _e); + return null; + } + finally { + IOUtils.closeQuietly(is); + IOUtils.closeQuietly(resp); + } + } + + public static void addBasicAuthHeader(HttpUriRequest _request, String _username, String _password) { + _request.addHeader("Authorization", "Basic " + Base64.encodeBase64String(NullUtils.toByteArray(_username + ":" + _password))); + } +} diff --git a/util/lantern-util-servlet/pom.xml b/util/lantern-util-servlet/pom.xml new file mode 100644 index 0000000..e513aa0 --- /dev/null +++ b/util/lantern-util-servlet/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + com.lanternsoftware.util + lantern-util-servlet + lantern-util-servlet + 1.0.0 + jar + + + + ${project.groupId} + lantern-util-common + ${project.version} + + + org.freemarker + freemarker + 2.3.23 + + + javax + javaee-api + 7.0 + provided + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar + + + + + + + diff --git a/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/BasicAuth.java b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/BasicAuth.java new file mode 100644 index 0000000..ab0c7da --- /dev/null +++ b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/BasicAuth.java @@ -0,0 +1,48 @@ +package com.lanternsoftware.util.servlet; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.codec.binary.Base64; + +import com.lanternsoftware.util.NullUtils; + +public class BasicAuth { + private final String username; + private final String password; + + public BasicAuth(HttpServletRequest _req) { + String u = null; + String p = null; + String auth = _req.getHeader("Authorization"); + if (auth != null && auth.startsWith("Basic")) { + String credentials = new String(Base64.decodeBase64(auth.substring("Basic".length()).trim()), StandardCharsets.UTF_8); + String[] values = credentials.split(":", 2); + if (values.length == 2) { + u = values[0]; + p = values[1]; + } + } + username = u; + password = p; + } + + public BasicAuth(String _username, String _password) { + username = _username; + password = _password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public static String toHeader(String _username, String _password) { + return "Basic " + Base64.encodeBase64String(NullUtils.toByteArray(_username + ":" + _password)); + } +} \ No newline at end of file diff --git a/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerConfigUtil.java b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerConfigUtil.java new file mode 100644 index 0000000..d653a7a --- /dev/null +++ b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerConfigUtil.java @@ -0,0 +1,36 @@ +package com.lanternsoftware.util.servlet; + +import java.io.File; +import java.io.IOException; + +import freemarker.cache.MruCacheStorage; +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapperBuilder; +import freemarker.template.TemplateExceptionHandler; + +public abstract class FreemarkerConfigUtil { + public static final Configuration createConfig(Class _templateClassLoader, String _templatePath, int _cacheMaxTemplateCount) { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_23); + cfg.setClassForTemplateLoading(_templateClassLoader, _templatePath); + cfg.setObjectWrapper(new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_23).build()); + cfg.setDefaultEncoding("UTF-8"); + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER); + cfg.setCacheStorage(new MruCacheStorage(_cacheMaxTemplateCount, Math.max(1, _cacheMaxTemplateCount/2))); + return cfg; + } + + public static final Configuration createFileSystemConfig(String _templatePath, int _cacheMaxTemplateCount) { + try { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_23); + cfg.setDirectoryForTemplateLoading(new File(_templatePath)); + cfg.setObjectWrapper(new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_23).build()); + cfg.setDefaultEncoding("UTF-8"); + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER); + cfg.setCacheStorage(new MruCacheStorage(_cacheMaxTemplateCount, Math.max(1, _cacheMaxTemplateCount/2))); + return cfg; + } + catch (IOException _e) { + return null; + } + } +} diff --git a/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerServlet.java b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerServlet.java new file mode 100644 index 0000000..cfb2122 --- /dev/null +++ b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerServlet.java @@ -0,0 +1,125 @@ +package com.lanternsoftware.util.servlet; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; + +import freemarker.template.Configuration; + +public abstract class FreemarkerServlet extends HttpServlet { + protected static final Logger LOG = LoggerFactory.getLogger(FreemarkerServlet.class); + + protected abstract Configuration getFreemarkerConfig(); + + public static String[] getPath(HttpServletRequest _request) { + String sPath = _request.getRequestURI().substring(_request.getContextPath().length()); + if (sPath.startsWith("/")) + sPath = sPath.substring(1); + String[] path = sPath.split("/"); + if ((path == null) || (path.length == 0) || (path[0].length() == 0)) + return new String[] { "index" }; + int iExtPos = CollectionUtils.last(path).lastIndexOf("."); + if (iExtPos > -1) { + path[path.length - 1] = CollectionUtils.last(path).substring(0, iExtPos); + } + return path; + } + + public static void redirect(HttpServletResponse _response, String _sURL) throws IOException { + _response.sendRedirect(_response.encodeRedirectURL(_sURL)); + } + + public static void setResponseHtml(HttpServletResponse _response, String _sHtml) { + setResponseEntity(_response, "text/html", _sHtml); + } + + public static void setResponseEntity(HttpServletResponse _response, String _sContentType, String _sEntity) { + setResponseEntity(_response, 200, _sContentType, _sEntity); + } + + public static void setResponseEntity(HttpServletResponse _response, String _sContentType, byte[] _btData) { + setResponseEntity(_response, 200, _sContentType, _btData); + } + + public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, String _sEntity) { + setResponseEntity(_response, _iStatus, _sContentType, NullUtils.toByteArray(_sEntity)); + } + + public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, byte[] _btData) { + OutputStream os = null; + try { + _response.setStatus(_iStatus); + _response.setCharacterEncoding("UTF-8"); + _response.setContentType(_sContentType); + if ((_btData != null) && (_btData.length > 0)) { + _response.setContentLength(_btData.length); + os = _response.getOutputStream(); + os.write(_btData); + } + else + _response.setContentLength(0); + } + catch (Exception e) { + if (!e.getClass().getSimpleName().equals("ClientAbortException")) + LOG.error("Failed to set response entity", e); + } + finally { + IOUtils.closeQuietly(os); + } + } + + public void render(HttpServletResponse _rep, String _sHtmlResourceKey, Map _mapModel) { + String html = FreemarkerUtil.render(getFreemarkerConfig(), _sHtmlResourceKey, _mapModel); + if (html == null) + _rep.setStatus(500); + else + setResponseHtml(_rep, html); + } + + protected Map simpleModel(String _name, Object _value) { + Map mapModel = new HashMap(); + mapModel.put(_name, _value); + return mapModel; + } + + public static T getSessionVar(HttpServletRequest _req, String _name) { + return (T) _req.getSession().getAttribute(_name); + } + + public static void putSessionVar(HttpServletRequest _req, String _name, Object _var) { + _req.getSession().setAttribute(_name, _var); + } + + protected String relativeOffset(HttpServletRequest _req) { + String[] path = getPath(_req); + StringBuilder offset = new StringBuilder(); + for (int i = 1; i < CollectionUtils.size(path); i++) { + offset.append("../"); + } + return offset.toString(); + } + + protected Cookie getCookie(HttpServletRequest _req, String _name) { + if (_req.getCookies() != null) { + for (Cookie c : _req.getCookies()) { + if (NullUtils.isEqual(c.getName(), _name)) + return c; + } + } + return null; + } + +} diff --git a/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerUtil.java b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerUtil.java new file mode 100644 index 0000000..e969975 --- /dev/null +++ b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/FreemarkerUtil.java @@ -0,0 +1,28 @@ +package com.lanternsoftware.util.servlet; + +import java.io.StringWriter; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import freemarker.template.Configuration; +import freemarker.template.Template; + +public abstract class FreemarkerUtil { + protected static final Logger LOG = LoggerFactory.getLogger(FreemarkerUtil.class); + + public static String render(Configuration _config, String _templateName, Map_model) { + try { + Template temp = _config.getTemplate(_templateName); + StringWriter writer = new StringWriter(); + temp.process(_model, writer); + writer.close(); + return writer.toString(); + } + catch (Exception e) { + LOG.error("Failed to render html", e); + return null; + } + } +} diff --git a/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/SchemaUtils.java b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/SchemaUtils.java new file mode 100644 index 0000000..00b8296 --- /dev/null +++ b/util/lantern-util-servlet/src/main/java/com/lanternsoftware/util/servlet/SchemaUtils.java @@ -0,0 +1,193 @@ +package com.lanternsoftware.util.servlet; + +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Field; +import java.util.Date; +import java.util.Map; +import java.util.TreeMap; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.apache.commons.io.IOUtils; + +import com.lanternsoftware.util.NullUtils; + +public class SchemaUtils { + public static void printStatements(String _sourceCodeFolder) { + Map> mapClasses = new TreeMap<>(); + searchFile(new File(_sourceCodeFolder), mapClasses); + for (Class c : mapClasses.values()) { + System.out.println(SchemaUtils.generateTableCreateStatement(c)); + System.out.println(SchemaUtils.generateSequenceCreateStatement(c)); + System.out.println(SchemaUtils.generateIndexCreateStatements(c)); + } + } + + private static void searchFile(File _f, Map> _mapClasses) { + if (_f == null) + return; + if (_f.isDirectory()) { + for (File child : _f.listFiles()) { + searchFile(child, _mapClasses); + } + } + else if (_f.getName().endsWith(".java")) { + try { + String sSource = IOUtils.toString(new FileInputStream(_f)); + if (!sSource.contains("@Table")) + return; + int iPackagePos = sSource.indexOf("package "); + int iPackageEnd = sSource.indexOf(";", iPackagePos); + String sPackageName = sSource.substring(iPackagePos + 8, iPackageEnd); + int iClassPos = sSource.indexOf("public class") + 12; + while (sSource.charAt(iClassPos) == ' ') + iClassPos++; + int iNewLineN = sSource.indexOf("\n", iClassPos); + int iNewLineR = sSource.indexOf("\r", iClassPos); + int iSpace = sSource.indexOf(" ", iClassPos); + int iClassEnd = NullUtils.min((iNewLineN == -1) ? Integer.MAX_VALUE : iNewLineN, (iNewLineR == -1) ? Integer.MAX_VALUE : iNewLineR, (iSpace == -1) ? Integer.MAX_VALUE : iSpace); + String sClassName = sSource.substring(iClassPos, iClassEnd); + String fullName = sPackageName + "." + sClassName; + Class clazz = Class.forName(fullName); + if (!clazz.isAnnotationPresent(Table.class)) + return; + _mapClasses.put(fullName, clazz); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } + + public static String generateTableCreateStatement(Class _entity) { + Table table = _entity.getAnnotation(Table.class); + if (table == null) + return null; + StringBuilder sql = new StringBuilder("CREATE TABLE "); + sql.append(table.name()); + sql.append(" ("); + boolean bFirst = true; + for (Field f : _entity.getDeclaredFields()) { + String name = null; + Column column = f.getAnnotation(Column.class); + JoinColumn join = f.getAnnotation(JoinColumn.class); + if (column != null) + name = column.name(); + else if (join != null) + name = join.name(); + if (name == null) + continue; + StringBuilder col = new StringBuilder(name); + col.append(" "); + if (NullUtils.isOneOf(f.getType(), Byte.TYPE, byte.class)) + col.append("NUMBER(3,0)"); + if (NullUtils.isOneOf(f.getType(), Short.TYPE, Short.class)) + col.append("NUMBER(5,0)"); + else if (NullUtils.isOneOf(f.getType(), Integer.TYPE, Integer.class)) + col.append("NUMBER(10,0)"); + else if (NullUtils.isOneOf(f.getType(), Long.TYPE, Long.class)) + col.append("NUMBER(19,0)"); + else if (NullUtils.isOneOf(f.getType(), Double.TYPE, Double.class, Float.TYPE, Float.class)) + col.append("NUMBER(19,4)"); + else if (NullUtils.isOneOf(f.getType(), Boolean.TYPE, Boolean.class)) + col.append("NUMBER(1,0)"); + else if (f.getType().equals(String.class) || f.getType().isEnum() || (join != null)) { + if (f.getAnnotation(Lob.class) != null) + col.append("CLOB"); + else + col.append("VARCHAR(255)"); + } + else if (f.getType().equals(Date.class)) + col.append("TIMESTAMP"); + else + continue; + if (f.getAnnotation(Id.class) != null) + col.append(" PRIMARY KEY"); + if (!bFirst) + sql.append(","); + else + bFirst = false; + sql.append(col); + } + sql.append(");"); + return sql.toString(); + } + + public static String generateSequenceCreateStatement(Class _entity) { + StringBuilder statements = new StringBuilder(); + for (Field f : _entity.getDeclaredFields()) { + SequenceGenerator seq = f.getAnnotation(SequenceGenerator.class); + if (seq == null) + continue; + statements.append("CREATE SEQUENCE \""); + statements.append(seq.name()); + statements.append("\" MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1000 NOCACHE NOORDER NOCYCLE;\n"); + } + return statements.toString(); + } + + public static String generateIndexCreateStatements(Class _entity) { + Table table = _entity.getAnnotation(Table.class); + if (table == null) + return null; + StringBuilder statements = new StringBuilder(); + Index[] indexes = table.indexes(); + if (indexes != null) { + for (Index index : indexes) { + statements.append("CREATE "); + if (index.unique()) + statements.append("UNIQUE "); + statements.append("INDEX \""); + statements.append(index.name()); + statements.append("\" ON \""); + statements.append(table.name()); + statements.append("\" ("); + statements.append(formatIndexFields(index.columnList())); + statements.append( + ") PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT);\n"); + } + } + return statements.toString(); + } + + private static String formatIndexFields(String _fields) { + String[] fields = NullUtils.makeNotNull(_fields).split(","); + StringBuilder builder = null; + if (fields != null) { + for (String field : fields) { + if (NullUtils.isEmpty(field)) + continue; + if (builder == null) + builder = new StringBuilder(); + else + builder.append(","); + builder.append("\""); + builder.append(field); + builder.append("\""); + } + } + if (builder == null) + return ""; + return builder.toString(); + } + + public static String generateTablespaceCreateStatement(String _name, String _dataFilePath, int _iStartSizeMB, int _iMaxSize) { + StringBuilder sql = new StringBuilder("CREATE TABLESPACE "); + sql.append(_name.toUpperCase()); + sql.append(" DATAFILE '"); + sql.append(_dataFilePath); + sql.append("' SIZE "); + sql.append(_iStartSizeMB * 1024 * 1024); + sql.append(" AUTOEXTEND ON NEXT 1 MAXSIZE "); + sql.append(_iMaxSize * 1024 * 1024); + sql.append(" BLOCKSIZE 8192 DEFAULT NOCOMPRESS ONLINE EXTENT MANAGEMENT LOCAL AUTOALLOCATE;\n"); + return sql.toString(); + } +} diff --git a/util/pom.xml b/util/pom.xml new file mode 100644 index 0000000..da83f35 --- /dev/null +++ b/util/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + com.lanternsoftware.util + util + util + 1.0.0 + pom + + + UTF-8 + 1.8 + 1.8 + + + + lantern-util-common + lantern-util-dao + lantern-util-dao-ephemeral + lantern-util-dao-mongo + lantern-util-http + lantern-util-servlet + + diff --git a/zwave/lantern-datamodel-zwave/pom.xml b/zwave/lantern-datamodel-zwave/pom.xml new file mode 100644 index 0000000..ca1fe14 --- /dev/null +++ b/zwave/lantern-datamodel-zwave/pom.xml @@ -0,0 +1,73 @@ + + 4.0.0 + com.lanternsoftware.services + lantern-datamodel-zwave + jar + 1.0.0 + lantern-datamodel-zwave + + + 1.8 + 1.8 + + + + + com.lanternsoftware.util + lantern-util-dao + 1.0.0 + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + package + + jar + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.5 + + + true + + + + + + diff --git a/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/Switch.java b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/Switch.java new file mode 100644 index 0000000..fbaa810 --- /dev/null +++ b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/Switch.java @@ -0,0 +1,146 @@ +package com.lanternsoftware.datamodel.zwave; + + +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.List; + +@DBSerializable +public class Switch { + private String room; + private String name; + private int nodeId; + private int level; + private boolean primary; + private boolean multilevel; + private boolean hold; + private String thermostatSource; + private ThermostatMode thermostatMode; + private int lowLevel; + private List schedule; + + public Switch() { + } + + public Switch(String _room, String _name, int _nodeId, boolean _primary, boolean _multilevel, String _thermostatSource, int _lowLevel) { + this(_room, _name, _nodeId, 0, _primary, _multilevel, false, _thermostatSource, _lowLevel, null); + } + + public Switch(String _room, String _name, int _nodeId, int _level, boolean _primary, boolean _multilevel, boolean _hold, String _thermostatSource, int _lowLevel, List _schedule) { + room = _room; + name = _name; + nodeId = _nodeId; + level = _level; + primary = _primary; + multilevel = _multilevel; + hold = _hold; + thermostatSource = _thermostatSource; + lowLevel = _lowLevel; + schedule = _schedule; + } + + public String getRoom() { + return room; + } + + public void setRoom(String _room) { + room = _room; + } + + public String getName() { + return name; + } + + public void setName(String _name) { + name = _name; + } + + public int getNodeId() { + return nodeId; + } + + public void setNodeId(int _nodeId) { + nodeId = _nodeId; + } + + public int getLevel() { + return level; + } + + public void setLevel(int _level) { + level = _level; + } + + public boolean isPrimary() { + return primary; + } + + public void setPrimary(boolean _primary) { + primary = _primary; + } + + public boolean isMultilevel() { + return multilevel; + } + + public void setMultilevel(boolean _multilevel) { + multilevel = _multilevel; + } + + public boolean isHold() { + return hold; + } + + public void setHold(boolean _hold) { + hold = _hold; + } + + public String getThermostatSource() { + return thermostatSource; + } + + public void setThermostatSource(String _thermostatSource) { + thermostatSource = _thermostatSource; + } + + public boolean isThermostat() { + return NullUtils.isNotEmpty(thermostatSource) && (nodeId < 100); + } + + public boolean isThermometer() { + return isUrlThermostat() && (nodeId > 99); + } + + public boolean isUrlThermostat() { + return NullUtils.makeNotNull(thermostatSource).startsWith("http"); + } + + public boolean isZWaveThermostat() { + return NullUtils.isEqual(thermostatSource, "ZWAVE"); + } + + public ThermostatMode getThermostatMode() { + return thermostatMode; + } + + public void setThermostatMode(ThermostatMode _thermostatMode) { + thermostatMode = _thermostatMode; + } + + public int getLowLevel() { + return lowLevel; + } + + public void setLowLevel(int _lowLevel) { + lowLevel = _lowLevel; + } + + public List getSchedule() { + return schedule; + } + + public void setSchedule(List _schedule) { + schedule = _schedule; + } +} diff --git a/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/SwitchSchedule.java b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/SwitchSchedule.java new file mode 100644 index 0000000..e5bb646 --- /dev/null +++ b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/SwitchSchedule.java @@ -0,0 +1,118 @@ +package com.lanternsoftware.datamodel.zwave; + + +import com.lanternsoftware.util.DateUtils; +import com.lanternsoftware.util.dao.annotations.DBSerializable; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +@DBSerializable +public class SwitchSchedule { + private int dayOfWeek; + private int timeOfDay; + private int minutesPerHour; + private int level; + + public SwitchSchedule() { + } + + public SwitchSchedule(int _minutesPerHour) { + minutesPerHour = _minutesPerHour; + } + + public SwitchSchedule(int _dayOfWeek, int _timeOfDay, int _level) { + dayOfWeek = _dayOfWeek; + timeOfDay = _timeOfDay; + level = _level; + } + + public SwitchSchedule(int _dayOfWeek, int _hour, int _minute, int _level) { + dayOfWeek = _dayOfWeek; + setTimeOfDay(_hour, _minute); + level = _level; + } + + public SwitchSchedule(int _dayOfWeek, int _hour, int _minute, int _second, int _level) { + dayOfWeek = _dayOfWeek; + setTimeOfDay(_hour, _minute, _second); + level = _level; + } + + public int getDayOfWeek() { + return dayOfWeek; + } + + public void setDayOfWeek(int _dayOfWeek) { + dayOfWeek = _dayOfWeek; + } + + public int getTimeOfDay() { + return timeOfDay; + } + + public void setTimeOfDay(int _timeOfDayInSeconds) { + timeOfDay = _timeOfDayInSeconds; + } + + public void setTimeOfDay(int _hour, int _minute) { + timeOfDay = (_hour * 3600) + (_minute * 60); + } + public void setTimeOfDay(int _hour, int _minute, int _second) { + timeOfDay = (_hour * 3600) + (_minute * 60) + _second; + } + + public int getMinutesPerHour() { + return minutesPerHour; + } + + public void setMinutesPerHour(int _minutesPerHour) { + minutesPerHour = _minutesPerHour; + } + + public int getLevel() { + return level; + } + + public void setLevel(int _level) { + level = _level; + } + + public int hour() { + return timeOfDay/3600; + } + + public int minute() { + return (timeOfDay/60)%60; + } + + public int second() { + return timeOfDay%60; + } + + private boolean isOn() { + return GregorianCalendar.getInstance().get(Calendar.MINUTE) < minutesPerHour; + } + + public SwitchTransition getNextTransition(Switch _switch, TimeZone _tz) { + if (minutesPerHour > 0) { + Date dt = DateUtils.getStartOfHour(_tz); + Date transition = DateUtils.addMinutes(dt, minutesPerHour); + if (new Date().before(transition)) + return new SwitchTransition(_switch, transition, 0); + return new SwitchTransition(_switch, DateUtils.getEndOfHour(_tz), level == 0?255:level); + } + Date now = new Date(); + Calendar cal = DateUtils.toCalendar(now, _tz); + cal.set(Calendar.DAY_OF_WEEK, dayOfWeek); + cal.set(Calendar.HOUR_OF_DAY, hour()); + cal.set(Calendar.MINUTE, minute()); + cal.set(Calendar.SECOND, second()); + cal.set(Calendar.MILLISECOND, 0); + if (cal.getTimeInMillis() <= now.getTime()) + cal.add(Calendar.DAY_OF_MONTH, 7); + return new SwitchTransition(_switch, cal.getTime(), level); + } +} diff --git a/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/SwitchTransition.java b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/SwitchTransition.java new file mode 100644 index 0000000..cad2f9a --- /dev/null +++ b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/SwitchTransition.java @@ -0,0 +1,27 @@ +package com.lanternsoftware.datamodel.zwave; + +import java.util.Date; + +public class SwitchTransition { + private final Switch sw; + private final Date transitionTime; + private final int level; + + SwitchTransition(Switch _sw, Date _transitionTime, int _level) { + sw = _sw; + transitionTime = _transitionTime; + level = _level; + } + + public Switch getSwitch() { + return sw; + } + + public Date getTransitionTime() { + return transitionTime; + } + + public int getLevel() { + return level; + } +} diff --git a/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/ThermostatMode.java b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/ThermostatMode.java new file mode 100644 index 0000000..a9a7bd3 --- /dev/null +++ b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/ThermostatMode.java @@ -0,0 +1,24 @@ +package com.lanternsoftware.datamodel.zwave; + +public enum ThermostatMode { + OFF((byte)0, "Off"), + HEAT((byte)1, "Heat"), + COOL((byte)2, "Cool"), + AUXILIARY((byte)4, "E-Heat"); + + public final byte data; + public final String display; + + ThermostatMode(byte _data, String _display) { + data = _data; + display = _display; + } + + public static ThermostatMode fromByte(byte _bt) { + for (ThermostatMode mode : values()) { + if (mode.data == _bt) + return mode; + } + return null; + } +} diff --git a/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/ZWaveConfig.java b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/ZWaveConfig.java new file mode 100644 index 0000000..bdc2229 --- /dev/null +++ b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/ZWaveConfig.java @@ -0,0 +1,29 @@ +package com.lanternsoftware.datamodel.zwave; + +import com.lanternsoftware.util.dao.annotations.DBSerializable; +import com.lanternsoftware.util.dao.annotations.PrimaryKey; + +import java.util.List; + +@DBSerializable(autogen = false) +public class ZWaveConfig { + @PrimaryKey + private int accountId; + private List switches; + + public int getAccountId() { + return accountId; + } + + public void setAccountId(int _accountId) { + accountId = _accountId; + } + + public List getSwitches() { + return switches; + } + + public void setSwitches(List _switches) { + switches = _switches; + } +} diff --git a/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/SwitchScheduleSerializer.java b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/SwitchScheduleSerializer.java new file mode 100644 index 0000000..c537a5f --- /dev/null +++ b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/SwitchScheduleSerializer.java @@ -0,0 +1,46 @@ +package com.lanternsoftware.datamodel.zwave.dao; + +import com.lanternsoftware.datamodel.zwave.SwitchSchedule; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.util.Collections; +import java.util.List; + +public class SwitchScheduleSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return SwitchSchedule.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(SwitchSchedule _o) + { + DaoEntity d = new DaoEntity(); + d.put("day_of_week", _o.getDayOfWeek()); + d.put("time_of_day", _o.getTimeOfDay()); + d.put("minutes_per_hour", _o.getMinutesPerHour()); + d.put("level", _o.getLevel()); + return d; + } + + @Override + public SwitchSchedule fromDaoEntity(DaoEntity _d) + { + SwitchSchedule o = new SwitchSchedule(); + o.setDayOfWeek(DaoSerializer.getInteger(_d, "day_of_week")); + o.setTimeOfDay(DaoSerializer.getInteger(_d, "time_of_day")); + o.setMinutesPerHour(DaoSerializer.getInteger(_d, "minutes_per_hour")); + o.setLevel(DaoSerializer.getInteger(_d, "level")); + return o; + } +} \ No newline at end of file diff --git a/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/SwitchSerializer.java b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/SwitchSerializer.java new file mode 100644 index 0000000..e137e8f --- /dev/null +++ b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/SwitchSerializer.java @@ -0,0 +1,62 @@ +package com.lanternsoftware.datamodel.zwave.dao; + +import com.lanternsoftware.datamodel.zwave.Switch; +import com.lanternsoftware.datamodel.zwave.SwitchSchedule; +import com.lanternsoftware.datamodel.zwave.ThermostatMode; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.util.Collections; +import java.util.List; + +public class SwitchSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return Switch.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(Switch _o) + { + DaoEntity d = new DaoEntity(); + d.put("room", _o.getRoom()); + d.put("name", _o.getName()); + d.put("node_id", _o.getNodeId()); + d.put("level", _o.getLevel()); + d.put("primary", _o.isPrimary()); + d.put("multilevel", _o.isMultilevel()); + d.put("hold", _o.isHold()); + d.put("thermostat_source", _o.getThermostatSource()); + d.put("thermostat_mode", DaoSerializer.toEnumName(_o.getThermostatMode())); + d.put("low_level", _o.getLowLevel()); + d.put("schedule", DaoSerializer.toDaoEntities(_o.getSchedule(), DaoProxyType.MONGO)); + return d; + } + + @Override + public Switch fromDaoEntity(DaoEntity _d) + { + Switch o = new Switch(); + o.setRoom(DaoSerializer.getString(_d, "room")); + o.setName(DaoSerializer.getString(_d, "name")); + o.setNodeId(DaoSerializer.getInteger(_d, "node_id")); + o.setLevel(DaoSerializer.getInteger(_d, "level")); + o.setPrimary(DaoSerializer.getBoolean(_d, "primary")); + o.setMultilevel(DaoSerializer.getBoolean(_d, "multilevel")); + o.setHold(DaoSerializer.getBoolean(_d, "hold")); + o.setThermostatSource(DaoSerializer.getString(_d, "thermostat_source")); + o.setThermostatMode(DaoSerializer.getEnum(_d, "thermostat_mode", ThermostatMode.class)); + o.setLowLevel(DaoSerializer.getInteger(_d, "low_level")); + o.setSchedule(DaoSerializer.getList(_d, "schedule", SwitchSchedule.class)); + return o; + } +} \ No newline at end of file diff --git a/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/ZWaveConfigSerializer.java b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/ZWaveConfigSerializer.java new file mode 100644 index 0000000..f53d694 --- /dev/null +++ b/zwave/lantern-datamodel-zwave/src/main/java/com/lanternsoftware/datamodel/zwave/dao/ZWaveConfigSerializer.java @@ -0,0 +1,43 @@ +package com.lanternsoftware.datamodel.zwave.dao; + +import com.lanternsoftware.datamodel.zwave.Switch; +import com.lanternsoftware.datamodel.zwave.ZWaveConfig; +import com.lanternsoftware.util.dao.AbstractDaoSerializer; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoProxyType; +import com.lanternsoftware.util.dao.DaoSerializer; + +import java.util.Collections; +import java.util.List; + +public class ZWaveConfigSerializer extends AbstractDaoSerializer +{ + @Override + public Class getSupportedClass() + { + return ZWaveConfig.class; + } + + @Override + public List getSupportedProxies() { + return Collections.singletonList(DaoProxyType.MONGO); + } + + @Override + public DaoEntity toDaoEntity(ZWaveConfig _o) + { + DaoEntity d = new DaoEntity(); + d.put("_id", String.valueOf(_o.getAccountId())); + d.put("switches", DaoSerializer.toDaoEntities(_o.getSwitches(), DaoProxyType.MONGO)); + return d; + } + + @Override + public ZWaveConfig fromDaoEntity(DaoEntity _d) + { + ZWaveConfig o = new ZWaveConfig(); + o.setAccountId(DaoSerializer.getInteger(_d, "_id")); + o.setSwitches(DaoSerializer.getList(_d, "switches", Switch.class)); + return o; + } +} \ No newline at end of file diff --git a/zwave/lantern-datamodel-zwave/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer b/zwave/lantern-datamodel-zwave/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer new file mode 100644 index 0000000..23dda4c --- /dev/null +++ b/zwave/lantern-datamodel-zwave/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer @@ -0,0 +1,3 @@ +com.lanternsoftware.datamodel.zwave.dao.SwitchScheduleSerializer +com.lanternsoftware.datamodel.zwave.dao.SwitchSerializer +com.lanternsoftware.datamodel.zwave.dao.ZWaveConfigSerializer diff --git a/zwave/lantern-service-thermometer/pom.xml b/zwave/lantern-service-thermometer/pom.xml new file mode 100644 index 0000000..bd618d2 --- /dev/null +++ b/zwave/lantern-service-thermometer/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + com.lanternsoftware.thermometer + lantern-service-thermometer + war + 1.0.0 + lantern-service-thermometer + + + 1.8 + 1.8 + + + + + com.lanternsoftware.util + lantern-util-common + 1.0.0 + + + javax + javaee-api + 8.0 + provided + + + ch.qos.logback + logback-classic + 1.2.3 + + + org.hid4java + hid4java + 0.5.0 + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + maven-war-plugin + 2.5 + + + + true + lib/ + + + + + + + diff --git a/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/TestThermo.java b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/TestThermo.java new file mode 100644 index 0000000..1ccfe7b --- /dev/null +++ b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/TestThermo.java @@ -0,0 +1,16 @@ +package com.lanternsoftware.thermometer; + +import com.lanternsoftware.thermometer.context.ThermometerApp; + +public class TestThermo { + public static void main(String[] args) { + ThermometerApp app = new ThermometerApp(); + app.start(); + try { + Thread.sleep(20000); + } catch (InterruptedException _e) { + _e.printStackTrace(); + } + app.stop(); + } +} diff --git a/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/context/Globals.java b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/context/Globals.java new file mode 100644 index 0000000..553f540 --- /dev/null +++ b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/context/Globals.java @@ -0,0 +1,22 @@ +package com.lanternsoftware.thermometer.context; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +public class Globals implements ServletContextListener { + public static ThermometerApp app; + + @Override + public void contextInitialized(ServletContextEvent sce) { + app = new ThermometerApp(); + app.start(); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + if (app != null) { + app.stop(); + app = null; + } + } +} diff --git a/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/context/ThermometerApp.java b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/context/ThermometerApp.java new file mode 100644 index 0000000..a682b96 --- /dev/null +++ b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/context/ThermometerApp.java @@ -0,0 +1,131 @@ +package com.lanternsoftware.thermometer.context; + +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.concurrency.ConcurrencyUtils; +import org.hid4java.HidDevice; +import org.hid4java.HidManager; +import org.hid4java.HidServices; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Timer; +import java.util.TimerTask; + +public class ThermometerApp { + private static final Logger LOG = LoggerFactory.getLogger(ThermometerApp.class); + + private HidDevice device; + private final Timer timer = new Timer(); + private double lastTemp; + + public void start() { + HidServices hs = HidManager.getHidServices(); + for (HidDevice d : hs.getAttachedHidDevices()) { + if (NullUtils.isEqual(d.getVendorId(), (short) 0x413d) && NullUtils.isEqual(d.getProductId(), (short) 0x2107)) { + if (d.getInterfaceNumber() == 1) + device = d; + } + } + if ((device != null) && device.open()) { + synchronized (device) { + read(hexToByte("0182770100000000")); + read(hexToByte("0186ff0100000000")); + read(hexToByte("0182770100000000")); + read(hexToByte("0182770100000000")); + } + } else { + LOG.error("Failed to open HID Device"); + return; + } + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + lastTemp = readTemperature(); + } + }, 0L, 10000L); + } + + public void stop() { + timer.cancel(); + ConcurrencyUtils.sleep(10000); + if (device != null) { + device.close(); + device = null; + } + HidServices hs = HidManager.getHidServices(); + hs.stop(); + hs.shutdown(); + } + + private byte[] read(byte[] _request) { + int RETRIES = 8; + int stat = -1; + int attempts = 0; + while ((stat <= 0) && (attempts < RETRIES)) { + attempts++; + try { + stat = device.write(_request, _request.length*8, (byte) 0); + } + catch (Exception _e) { + LOG.error("Exception while writing", _e); + } + if (stat <= 0) { + if (attempts == RETRIES) { + LOG.error("Failed max number of retires, returning null"); + return null; + } + LOG.error("Write attempt " + attempts + " failed, waiting 250ms to retry"); + ConcurrencyUtils.sleep(250); + } + } + byte[] response = new byte[32]; + stat = -1; + attempts = 0; + while ((stat <= 0) && (attempts < RETRIES)) { + attempts++; + try { + stat = device.read(response, 500); + } + catch (Exception _e) { + LOG.error("Exception while reading", _e); + } + if (stat <= 0) { + if (attempts == RETRIES) { + LOG.error("Failed max number of retires, returning null"); + return null; + } + LOG.error("Read attempt " + attempts + " failed, waiting 250ms to retry"); + ConcurrencyUtils.sleep(250); + } + } + return response; + } + public double getTemperature() { + return lastTemp; + } + + public double readTemperature() { + if (device != null) { + synchronized (device) { + byte[] response = read(hexToByte("0180330100000000")); + if (response == null) + return 5.0; + int rawReading = ((response[3] & 0xFF) + (response[2] << 8)); + if (rawReading == 0) + return 5.0; + return rawReading / 100.0; + } + + } + return 5.0; + } + + private static byte[] hexToByte(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } +} diff --git a/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/servlet/TempServlet.java b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/servlet/TempServlet.java new file mode 100644 index 0000000..1eccd99 --- /dev/null +++ b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/servlet/TempServlet.java @@ -0,0 +1,18 @@ +package com.lanternsoftware.thermometer.servlet; + +import com.lanternsoftware.thermometer.context.Globals; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + + +@WebServlet("/temp") +public class TempServlet extends ThermoServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + setResponseEntity(resp, "application/json", "{\"temp\": "+ Globals.app.getTemperature() + "}"); + } +} diff --git a/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/servlet/ThermoServlet.java b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/servlet/ThermoServlet.java new file mode 100644 index 0000000..efe7e65 --- /dev/null +++ b/zwave/lantern-service-thermometer/src/main/java/com/lanternsoftware/thermometer/servlet/ThermoServlet.java @@ -0,0 +1,45 @@ +package com.lanternsoftware.thermometer.servlet; + +import com.lanternsoftware.util.NullUtils; +import org.apache.commons.io.IOUtils; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletResponse; +import java.io.OutputStream; + +public abstract class ThermoServlet extends HttpServlet { + public static void setResponseHtml(HttpServletResponse _response, String _sHtml) { + setResponseEntity(_response, "text/html", _sHtml); + } + + public static void setResponseEntity(HttpServletResponse _response, String _sContentType, String _sEntity) { + setResponseEntity(_response, 200, _sContentType, _sEntity); + } + + public static void setResponseEntity(HttpServletResponse _response, String _sContentType, byte[] _btData) { + setResponseEntity(_response, 200, _sContentType, _btData); + } + + public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, String _sEntity) { + setResponseEntity(_response, _iStatus, _sContentType, NullUtils.toByteArray(_sEntity)); + } + + public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, byte[] _btData) { + OutputStream os = null; + try { + _response.setStatus(_iStatus); + _response.setCharacterEncoding("UTF-8"); + _response.setContentType(_sContentType); + if ((_btData != null) && (_btData.length > 0)) { + _response.setContentLength(_btData.length); + os = _response.getOutputStream(); + os.write(_btData); + } else + _response.setContentLength(0); + } catch (Exception e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(os); + } + } +} diff --git a/zwave/lantern-service-thermometer/src/main/resources/logback.xml b/zwave/lantern-service-thermometer/src/main/resources/logback.xml new file mode 100644 index 0000000..939eaaa --- /dev/null +++ b/zwave/lantern-service-thermometer/src/main/resources/logback.xml @@ -0,0 +1,28 @@ + + + + + + + ${log.pattern} + + + + + /opt/currentmonitor/log/log.txt + + /opt/currentmonitor/log/log.%d{yyyy-MM-dd}.%i.txt + 20MB + 20 + + + ${log.pattern} + + + + + + + + + \ No newline at end of file diff --git a/zwave/lantern-service-thermometer/src/main/webapp/WEB-INF/web.xml b/zwave/lantern-service-thermometer/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..871def2 --- /dev/null +++ b/zwave/lantern-service-thermometer/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,9 @@ + + + + + com.lanternsoftware.thermometer.context.Globals + + \ No newline at end of file diff --git a/zwave/lantern-service-thermometer/src/test/java/com/lanternsoftware/thermometer/TestStartup.java b/zwave/lantern-service-thermometer/src/test/java/com/lanternsoftware/thermometer/TestStartup.java new file mode 100644 index 0000000..5dc82b4 --- /dev/null +++ b/zwave/lantern-service-thermometer/src/test/java/com/lanternsoftware/thermometer/TestStartup.java @@ -0,0 +1,16 @@ +package com.lanternsoftware.thermometer; + +import com.lanternsoftware.thermometer.context.ThermometerApp; + +public class TestStartup { + public static void main(String[] args) { + ThermometerApp app = new ThermometerApp(); + app.start(); + try { + Thread.sleep(20000); + } catch (InterruptedException _e) { + _e.printStackTrace(); + } + app.stop(); + } +} diff --git a/zwave/lantern-service-zwave/pom.xml b/zwave/lantern-service-zwave/pom.xml new file mode 100644 index 0000000..8d02389 --- /dev/null +++ b/zwave/lantern-service-zwave/pom.xml @@ -0,0 +1,94 @@ + + 4.0.0 + com.lanternsoftware.zwave + lantern-service-zwave + war + 1.0.0 + lantern-service-zwave + + + 1.8 + 1.8 + + + + + com.lanternsoftware.services + lantern-datamodel-zwave + 1.0.0 + + + com.lanternsoftware.currentmonitor + lantern-dataaccess-currentmonitor + 1.0.0 + + + com.lanternsoftware.zwave + lantern-zwave + 1.0.0 + + + com.lanternsoftware.util + lantern-util-dao-mongo + 1.0.0 + + + com.lanternsoftware.util + lantern-util-http + 1.0.0 + + + ch.qos.logback + logback-classic + 1.2.3 + + + javax + javaee-api + 8.0 + provided + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + maven-war-plugin + 2.5 + + + + true + lib/ + + + + + + + diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/Globals.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/Globals.java new file mode 100644 index 0000000..3a2c9a9 --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/Globals.java @@ -0,0 +1,30 @@ +package com.lanternsoftware.zwave.context; + +import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.dao.mongo.MongoConfig; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +public class Globals implements ServletContextListener { + public static ZWaveApp app; + public static MongoCurrentMonitorDao cmDao; + + @Override + public void contextInitialized(ServletContextEvent sce) { + cmDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg")); + app = new ZWaveApp(); + app.start(); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + if (app != null) { + app.stop(); + app = null; + } + if (cmDao != null) + cmDao.shutdown(); + } +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveApp.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveApp.java new file mode 100644 index 0000000..0129530 --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveApp.java @@ -0,0 +1,334 @@ +package com.lanternsoftware.zwave.context; + +import com.lanternsoftware.datamodel.zwave.Switch; +import com.lanternsoftware.datamodel.zwave.SwitchSchedule; +import com.lanternsoftware.datamodel.zwave.SwitchTransition; +import com.lanternsoftware.datamodel.zwave.ThermostatMode; +import com.lanternsoftware.datamodel.zwave.ZWaveConfig; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.DateUtils; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.concurrency.ConcurrencyUtils; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.mongo.MongoConfig; +import com.lanternsoftware.util.http.HttpPool; +import com.lanternsoftware.zwave.controller.Controller; +import com.lanternsoftware.zwave.dao.MongoZWaveDao; +import com.lanternsoftware.zwave.message.IMessageSubscriber; +import com.lanternsoftware.zwave.message.MessageEngine; +import com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest; +import com.lanternsoftware.zwave.message.impl.MultilevelSensorGetRequest; +import com.lanternsoftware.zwave.message.impl.MultilevelSensorReportRequest; +import com.lanternsoftware.zwave.message.impl.MultilevelSwitchReportRequest; +import com.lanternsoftware.zwave.message.impl.MultilevelSwitchSetRequest; +import com.lanternsoftware.zwave.message.impl.ThermostatModeSetRequest; +import com.lanternsoftware.zwave.message.impl.ThermostatSetPointReportRequest; +import com.lanternsoftware.zwave.message.impl.ThermostatSetPointSetRequest; +import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex; +import org.apache.http.client.methods.HttpGet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.Timer; +import java.util.TimerTask; + +public class ZWaveApp { + private static final Logger logger = LoggerFactory.getLogger(ZWaveApp.class); + + private MongoZWaveDao dao; + private ZWaveConfig config; + private Controller controller; + private final Map switches = new HashMap<>(); + private final Map> peers = new HashMap<>(); + private Timer timer; + private HttpPool pool; + private SwitchScheduleTask nextScheduleTask; + private final Map temperatures = new HashMap<>(); + private final Object ZWAVE_MUTEX = new Object(); + + public void start() { + try { + dao = new MongoZWaveDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg")); + controller = new Controller(); + controller.start("COM4"); + timer = new Timer("ZWaveApp Timer"); + pool = new HttpPool(10, 10, 30000, 10000, 10000); + +//// for (int node = 3; node < 7; node++) { +// session.doAction(new ConfigurationSetAction(node, (byte) 7, new byte[]{99})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 8, new byte[]{0, (byte) 1})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 9, new byte[]{99})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 10, new byte[]{0, (byte) 1})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 11, new byte[]{99})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 12, new byte[]{0, (byte) 1})); +// ConcurrencyUtils.sleep(100); +// } + } catch (Throwable t) { + t.printStackTrace(); + } + config = dao.getConfig(1); + Map> groups = new HashMap<>(); + for (Switch sw : CollectionUtils.makeNotNull(config.getSwitches())) { + switches.put(sw.getNodeId(), sw); + CollectionUtils.addToMultiMap(sw.getRoom() + ":" + sw.getName(), sw.getNodeId(), groups); + } + if (CollectionUtils.filterOne(config.getSwitches(), Switch::isUrlThermostat) != null) { + timer.scheduleAtFixedRate(new ThermostatTask(), 0, 30000); + } + for (List group : groups.values()) { + for (Integer node : group) { + peers.put(node, CollectionUtils.filter(group, _i -> !_i.equals(node))); + } + } + scheduleNextTransition(); + + MessageEngine.subscribe(new IMessageSubscriber() { + @Override + public Class getHandledMessageClass() { + return MultilevelSensorReportRequest.class; + } + + @Override + public void onMessage(MultilevelSensorReportRequest _message) { + synchronized (temperatures) { + temperatures.put((int) _message.getNodeId(), _message.getTemperatureCelsius()); + temperatures.notify(); + } + } + }); + + MessageEngine.subscribe(new IMessageSubscriber() { + @Override + public Class getHandledMessageClass() { + return ThermostatSetPointReportRequest.class; + } + + @Override + public void onMessage(ThermostatSetPointReportRequest _message) { + synchronized (switches) { + Switch sw = switches.get((int) _message.getNodeId()); + if (sw != null) { + if (NullUtils.isOneOf(_message.getIndex(), ThermostatSetPointIndex.HEATING, ThermostatSetPointIndex.COOLING)) { + sw.setLevel((int) Math.round(_message.getTemperatureCelsius() * 1.8) + 32); + persistConfig(); + } + } + } + } + }); + + MessageEngine.subscribe(new IMessageSubscriber() { + @Override + public Class getHandledMessageClass() { + return MultilevelSwitchReportRequest.class; + } + + @Override + public void onMessage(MultilevelSwitchReportRequest _message) { + synchronized (switches) { + Switch sw = switches.get((int) _message.getNodeId()); + if (sw != null) { + sw.setLevel(_message.getLevel()); + for (Integer node : CollectionUtils.makeNotNull(peers.get((int) _message.getNodeId()))) { + sw = switches.get(node); + sw.setLevel(_message.getLevel()); + logger.info("Mirror Event from node {} to node {}", _message.getNodeId(), node); + controller.send(new MultilevelSwitchSetRequest(node.byteValue(), _message.getLevel())); + } + persistConfig(); + } + } + } + }); + +// controller.send(new MultilevelSensorGetRequest((byte)11)); +// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.HEATING)); +// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.COOLING)); +// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.HEATING_ECON)); +// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.COOLING_ECON)); +// controller.send(new ThermostatSetPointSupportedGetRequest((byte)11)); +// controller.send(new ThermostatSetPointCapabilitiesGetRequest((byte)11)); +// controller.send(new ThermostatSetPointSetRequest((byte)11, ThermostatSetPointIndex.HEATING_ECON, 72)); +// controller.send(new ThermostatModeSetRequest((byte)11, ThermostatMode.HEAT)); +// controller.send(new ThermostatModeGetRequest((byte)11)); + } + + private void scheduleNextTransition() { + TimeZone tz = TimeZone.getTimeZone("America/Chicago"); + if (nextScheduleTask != null) + nextScheduleTask.cancel(); + List nextTransitions = CollectionUtils.getAllSmallest(CollectionUtils.aggregate(switches.values(), _s->CollectionUtils.transform(_s.getSchedule(), _t->_t.getNextTransition(_s, tz))), Comparator.comparing(SwitchTransition::getTransitionTime)); + if (!CollectionUtils.isEmpty(nextTransitions)) { + for (SwitchTransition tr : nextTransitions) { + logger.info("Next transition scheduled for node {} to level {} at {}", tr.getSwitch().getNodeId(), tr.getLevel(), DateUtils.format("hh:mm:ssa", tz, tr.getTransitionTime())); + } + nextScheduleTask = new SwitchScheduleTask(nextTransitions); + timer.schedule(nextScheduleTask, CollectionUtils.getFirst(nextTransitions).getTransitionTime()); + } else + nextScheduleTask = null; + } + + public void setSwitchLevel(int _nodeId, int _level) { + Switch sw = switches.get(_nodeId); + if ((sw == null) || !sw.isPrimary()) + return; + sw.setLevel(_level); + if (!sw.isThermostat()) { + setGroupSwitchLevel(_nodeId, _level, sw.isMultilevel()); + } else if (sw.isZWaveThermostat()) { + controller.send(new ThermostatSetPointSetRequest((byte) sw.getNodeId(), sw.getThermostatMode() == ThermostatMode.COOL ? ThermostatSetPointIndex.COOLING : ThermostatSetPointIndex.HEATING, _level)); + } else { + if (timer != null) + timer.schedule(new ThermostatTask(), 0); + persistConfig(); + } + } + + public void setThermostatMode(int _nodeId, ThermostatMode _mode) { + Switch sw = switches.get(_nodeId); + if ((sw == null) || !sw.isPrimary() || !sw.isZWaveThermostat()) + return; + controller.send(new ThermostatModeSetRequest((byte) sw.getNodeId(), com.lanternsoftware.zwave.message.thermostat.ThermostatMode.fromByte(_mode.data))); + sw.setThermostatMode(_mode); + persistConfig(); + } + + public void setSwitchSchedule(int _nodeId, List _transitions) { + Switch sw = switches.get(_nodeId); + if ((sw == null) || !sw.isPrimary()) + return; + sw.setSchedule(_transitions); + persistConfig(); + scheduleNextTransition(); + } + + public void setSwitchHold(int _nodeId, boolean _hold) { + Switch sw = switches.get(_nodeId); + if ((sw == null) || !sw.isPrimary()) + return; + sw.setHold(_hold); + persistConfig(); + } + + private void persistConfig() { + synchronized (this) { + dao.putConfig(config); + } + } + + public int getSwitchLevel(int _nodeId) { + Switch sw = switches.get(_nodeId); + return (sw != null) ? sw.getLevel() : 0; + } + + public ZWaveConfig getConfig() { + return config; + } + + public void stop() { + controller.stop(); + if (timer != null) { + timer.cancel(); + timer = null; + } + if (pool != null) { + pool.shutdown(); + pool = null; + } + if (dao != null) { + dao.shutdown(); + dao = null; + } + } + + private void setGroupSwitchLevel(int _primary, int _level, boolean _multilevel) { + List nodes = CollectionUtils.asArrayList(_primary); + nodes.addAll(CollectionUtils.makeNotNull(peers.get(_primary))); + for (int node : nodes) { + controller.send(_multilevel ? new MultilevelSwitchSetRequest((byte) node, _level) : new BinarySwitchSetRequest((byte) node, _level > 0)); + } + } + + private class ThermostatTask extends TimerTask { + @Override + public void run() { + for (Switch sw : switches.values()) { + try { + if (sw.isUrlThermostat() && !sw.isThermometer()) { + double tempF = getTemperatureCelsius(sw) * 1.8 + 32; + if (tempF > sw.getLevel() + 0.4) { + setGroupSwitchLevel(sw.getNodeId(), 0, false); + logger.info("Turning {} {} off, temp is: {} set to: {}", sw.getRoom(), sw.getName(), tempF + " set to: ", sw.getLevel()); + } else if (tempF < sw.getLevel() - 0.4) { + setGroupSwitchLevel(sw.getNodeId(), (byte) 0xf, false); + logger.info("Turning {} {} on, temp is: {} set to: {}", sw.getRoom(), sw.getName(), tempF + " set to: ", sw.getLevel()); + } + } + } + catch (Throwable t) { + logger.error("Failed to check temperature for thermostat {}", sw.getName()); + } + } + } + } + + private class SwitchScheduleTask extends TimerTask { + private final List transitions; + + SwitchScheduleTask(List _transitions) { + transitions = _transitions; + } + + @Override + public void run() { + for (SwitchTransition tr : transitions) { + if (!tr.getSwitch().isHold()) { + logger.info("Executing scheduled transition of node {} to level {}", tr.getSwitch().getNodeId(), tr.getLevel()); + Globals.app.setSwitchLevel(tr.getSwitch().getNodeId(), tr.getLevel()); + } + else + logger.info("Skipping scheduled transition of node {} to level {}, switch is on hold", tr.getSwitch().getNodeId(), tr.getLevel()); + ConcurrencyUtils.sleep(100); + } + nextScheduleTask = null; + Globals.app.scheduleNextTransition(); + } + } + + public double getTemperatureCelsius(int _nodeId) { + return getTemperatureCelsius(switches.get(_nodeId)); + } + + private double getTemperatureCelsius(Switch _sw) { + if ((pool == null) || (_sw == null) || !(_sw.isThermometer() || _sw.isThermostat())) + return 0.0; + if (_sw.isUrlThermostat()) + return DaoSerializer.getDouble(DaoSerializer.parse(pool.executeToString(new HttpGet(_sw.getThermostatSource()))), "temp"); + else if (_sw.isZWaveThermostat()) { + synchronized (ZWAVE_MUTEX) { + synchronized (temperatures) { + controller.send(new MultilevelSensorGetRequest((byte) _sw.getNodeId())); + try { + temperatures.wait(5000); + } catch (InterruptedException _e) { + _e.printStackTrace(); + } + Double temp = temperatures.get(_sw.getNodeId()); + return (temp == null) ? 0.0 : temp; + } + } + } + return 0.0; + } +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveSpring.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveSpring.java new file mode 100644 index 0000000..164c24a --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveSpring.java @@ -0,0 +1,230 @@ +package com.lanternsoftware.zwave.context; + +public class ZWaveSpring { +/* private ZWaveConfig config; + private static ZWaveSession session; + private static Map switches = new HashMap<>(); + private static Map> peers = new HashMap<>(); + private static Timer timer; + private static HttpPool pool; + private static SwitchScheduleTask nextScheduleTask; + + public void start() { + try { +// controller = new Controller(); +// controller.start("COM4"); + timer = new Timer("ZWaveApp Timer"); + pool = new HttpPool(10, 10, 30000, 10000, 10000); + session = new LocalZwaveSession(); + session.connect(); + while (!session.isNetworkReady()) { + System.out.println("Network not ready yet, sleeping"); + ConcurrencyUtils.sleep(1000); + } +// session.subscribe(new ZWaveEventListener()); + +// for (ZWaveNode node : session.getDeviceManager().getNodes()) { +// for (CommandClass cc : node.getCommandClasses()) { +// System.out.println(node.getNodeId() + " " + cc.getClassCode() + " " + cc.getLabel()); +// } +// } + +//// for (int node = 3; node < 7; node++) { +// session.doAction(new ConfigurationSetAction(node, (byte) 7, new byte[]{99})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 8, new byte[]{0, (byte) 1})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 9, new byte[]{99})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 10, new byte[]{0, (byte) 1})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 11, new byte[]{99})); +// ConcurrencyUtils.sleep(100); +// session.doAction(new ConfigurationSetAction(node, (byte) 12, new byte[]{0, (byte) 1})); +// ConcurrencyUtils.sleep(100); +// } + } catch (Throwable t) { + t.printStackTrace(); + } + config = SerializationEngine.deserialize(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "config.dat"), ZWaveConfig.class, SerializationEngine.SerializationType.JSON); + Map> groups = new HashMap<>(); + for (Switch sw : CollectionUtils.makeNotNull(config.getSwitches())) { + switches.put(sw.getNodeId(), sw); + CollectionUtils.addToMultiMap(sw.getRoom() + ":" + sw.getName(), sw.getNodeId(), groups); + } + if (CollectionUtils.filterOne(config.getSwitches(), _sw -> NullUtils.isNotEmpty(_sw.getThermostatSource())) != null) { + timer.scheduleAtFixedRate(new ThermostatTask(), 0, 30000); + } + for (List group : groups.values()) { + for (Integer node : group) { + peers.put(node, CollectionUtils.filter(group, _i -> !_i.equals(node))); + } + } + scheduleNextTransition(); + } + + public void scheduleNextTransition() { + TimeZone tz = TimeZone.getTimeZone("America/Chicago"); + if (nextScheduleTask != null) + nextScheduleTask.cancel(); + Switch next = null; + SwitchTransition transition = null; + Date transitionDate = null; + for (Switch sw : switches.values()) { + for (SwitchTransition t : CollectionUtils.makeNotNull(sw.getSchedule())) { + Date nextTransition = t.getNextTransition(tz); + if ((transitionDate == null) || nextTransition.before(transitionDate)) { + transitionDate = nextTransition; + transition = t; + next = sw; + } + } + } + if (transitionDate != null) { + System.out.println("Next transition scheduled for node " + next.getNodeId() + " to level " + transition.getLevel() + " at " + DateUtils.format(tz, transitionDate, "hh:mm:ssa")); + nextScheduleTask = new SwitchScheduleTask(next, transition); + timer.schedule(nextScheduleTask, transitionDate); + } else + nextScheduleTask = null; + } + + public void setSwitchLevel(int _nodeId, int _level) { + Switch sw = switches.get(_nodeId); + if ((sw == null) || !sw.isPrimary()) + return; + sw.setLevel(_level); + if (NullUtils.isEmpty(sw.getThermostatSource())) { + doGroupSwitchAction(_nodeId, _level, sw.isMultilevel()); + } else { + if (timer != null) + timer.schedule(new ThermostatTask(), 0); + persistConfig(); + } + + } + + public void setSwitchSchedule(int _nodeId, List _transitions) { + Switch sw = switches.get(_nodeId); + if ((sw == null) || !sw.isPrimary()) + return; + sw.setSchedule(_transitions); + persistConfig(); + scheduleNextTransition(); + } + + public void setSwitchHold(int _nodeId, boolean _hold) { + Switch sw = switches.get(_nodeId); + if ((sw == null) || !sw.isPrimary()) + return; + sw.setHold(_hold); + persistConfig(); + } + + private void persistConfig() { + synchronized (this) { + ResourceLoader.writeFile(LanternFiles.OPS_PATH + "config.dat", SerializationEngine.serialize(config, SerializationEngine.SerializationType.JSON)); + } + } + + public int getSwitchLevel(int _nodeId) { + Switch sw = switches.get(_nodeId); + return (sw != null) ? sw.getLevel() : 0; + } + + public ZWaveConfig getConfig() { + return config; + } + + public void stop() { + session.shutdown(); + if (timer != null) { + timer.cancel(); + timer = null; + } + if (pool != null) { + pool.shutdown(); + pool = null; + } + } + + /* + public static class ZWaveEventListener implements EventHandler { + @EventSubscribe + public void receive(ZWaveEvent event) throws Exception { + if (event instanceof ApplicationCommandEvent) { + ApplicationCommandEvent ace = (ApplicationCommandEvent) event; + if (ace.getCommandClass() == CommandClass.SWITCH_MULTILEVEL) { + for (Integer node : CollectionUtils.makeNotNull(peers.get(ace.getNodeId()))) { + Switch sw = switches.get(node); + System.out.println("Mirror Event from node " + ((ApplicationCommandEvent) event).getNodeId() + " to node " + node); + // session.doAction(new SwitchAction(node, ace.getPayload()[1], sw == null || sw.isMultilevel())); + } + } + } + } + + @EventSubscribe + public void handleSensorEvent(DeviceSensorEvent sensorEvent) { + } + } + + private void doGroupSwitchAction(int _primary, int _level, boolean _multilevel) { + List nodes = CollectionUtils.asArrayList(_primary); + nodes.addAll(CollectionUtils.makeNotNull(peers.get(_primary))); + for (int node : nodes) { + try { + session.doAction(new SwitchAction(node, _level, _multilevel)); + } catch (HomeAutomationException _e) { + _e.printStackTrace(); + } + } + } + + private class ThermostatTask extends TimerTask { + @Override + public void run() { + for (Switch sw : switches.values()) { + if (NullUtils.isNotEmpty(sw.getThermostatSource())) { + double tempF = getTemperatureCelsius(sw) * 1.8 + 32; + if (tempF > sw.getLevel() + 0.4) { + doGroupSwitchAction(sw.getNodeId(), 0, false); + System.out.println("Turning " + sw.getRoom() + " " + sw.getName() + " off, temp is: " + tempF + " set to: " + sw.getLevel()); + } else if (tempF < sw.getLevel() - 0.4) { + doGroupSwitchAction(sw.getNodeId(), (byte) 0xf, false); + System.out.println("Turning " + sw.getRoom() + " " + sw.getName() + " on, temp is: " + tempF + " set to: " + sw.getLevel()); + } + } + } + } + } + + private class SwitchScheduleTask extends TimerTask { + private final Switch sw; + private final SwitchTransition transition; + + public SwitchScheduleTask(Switch _sw, SwitchTransition _transition) { + sw = _sw; + transition = _transition; + } + + @Override + public void run() { + System.out.println("Executing scheduled transition of node " + sw.getNodeId() + " to level " + transition.getLevel()); + if (!sw.isHold()) { + Globals.app.setSwitchLevel(sw.getNodeId(), transition.getLevel()); + } + nextScheduleTask = null; + Globals.app.scheduleNextTransition(); + } + } + + public double getTemperatureCelsius(int _nodeId) { + return getTemperatureCelsius(switches.get(_nodeId)); + } + + private static double getTemperatureCelsius(Switch _sw) { + if ((pool == null) || (_sw == null) || NullUtils.isEmpty(_sw.getThermostatSource())) + return 0.0; + return BsonUtils.getDouble(BsonUtils.parse(pool.executeToString(new HttpGet(_sw.getThermostatSource()))), "temp"); + }*/ +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/dao/MongoZWaveDao.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/dao/MongoZWaveDao.java new file mode 100644 index 0000000..fc73f44 --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/dao/MongoZWaveDao.java @@ -0,0 +1,29 @@ +package com.lanternsoftware.zwave.dao; + +import com.lanternsoftware.datamodel.zwave.ZWaveConfig; +import com.lanternsoftware.util.dao.DaoQuery; +import com.lanternsoftware.util.dao.mongo.MongoConfig; +import com.lanternsoftware.util.dao.mongo.MongoProxy; + +public class MongoZWaveDao implements ZWaveDao { + private MongoProxy proxy; + + public MongoZWaveDao(MongoConfig _config) { + proxy = new MongoProxy(_config); + } + + public void shutdown() { + proxy.shutdown(); + } + + + @Override + public void putConfig(ZWaveConfig _config) { + proxy.save(_config); + } + + @Override + public ZWaveConfig getConfig(int _accountId) { + return proxy.queryOne(ZWaveConfig.class, new DaoQuery("_id", String.valueOf(_accountId))); + } +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/dao/ZWaveDao.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/dao/ZWaveDao.java new file mode 100644 index 0000000..b63ef35 --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/dao/ZWaveDao.java @@ -0,0 +1,8 @@ +package com.lanternsoftware.zwave.dao; + +import com.lanternsoftware.datamodel.zwave.ZWaveConfig; + +public interface ZWaveDao { + void putConfig(ZWaveConfig _config); + ZWaveConfig getConfig(int _accountId); +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/ConfigServlet.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/ConfigServlet.java new file mode 100644 index 0000000..af9995b --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/ConfigServlet.java @@ -0,0 +1,18 @@ +package com.lanternsoftware.zwave.servlet; + +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.zwave.context.Globals; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@WebServlet("/config") +public class ConfigServlet extends SecureServlet { + @Override + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + jsonResponse(_rep, Globals.app.getConfig()); + } +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/SecureServlet.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/SecureServlet.java new file mode 100644 index 0000000..fdc2442 --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/SecureServlet.java @@ -0,0 +1,35 @@ +package com.lanternsoftware.zwave.servlet; + +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.zwave.context.Globals; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public abstract class SecureServlet extends ZWaveServlet { + @Override + protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) { + AuthCode authCode = Globals.cmDao.decryptAuthCode(_req.getHeader("auth_code")); + if ((authCode == null) || (authCode.getAccountId() != 1)) { + _rep.setStatus(401); + return; + } + get(authCode, _req, _rep); + } + + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + } + + @Override + protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) { + AuthCode authCode = Globals.cmDao.decryptAuthCode(_req.getHeader("auth_code")); + if ((authCode == null) || (authCode.getAccountId() != 1)) { + _rep.setStatus(401); + return; + } + post(authCode, _req, _rep); + } + + protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + } +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/SwitchServlet.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/SwitchServlet.java new file mode 100644 index 0000000..e50f95c --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/SwitchServlet.java @@ -0,0 +1,53 @@ +package com.lanternsoftware.zwave.servlet; + +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.datamodel.zwave.SwitchSchedule; +import com.lanternsoftware.datamodel.zwave.ThermostatMode; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.zwave.context.Globals; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +@WebServlet("/switch/*") +public class SwitchServlet extends SecureServlet { + @Override + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + String[] path = path(_req); + int nodeId = NullUtils.toInteger(CollectionUtils.get(path, 0)); + if (path.length == 1) { + setResponseEntity(_rep, "application/json", "{level:" + Globals.app.getSwitchLevel(nodeId) + "}"); + } else { + if (nodeId > 0) { + String command = CollectionUtils.get(path, 1); + if ("hold".equals(command)) + Globals.app.setSwitchHold(nodeId, true); + else if ("run".equals(command)) + Globals.app.setSwitchHold(nodeId, false); + else if ("mode".equals(command)) + Globals.app.setThermostatMode(nodeId, ThermostatMode.fromByte(Byte.parseByte(CollectionUtils.get(path, 2)))); + else { + Globals.app.setSwitchLevel(nodeId, NullUtils.toInteger(command)); + } + } + } + } + + @Override + protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + String[] path = path(_req); + int nodeId = NullUtils.toInteger(CollectionUtils.get(path, 0)); + if (path.length > 1) { + String command = CollectionUtils.get(path, 1); + if ("schedule".equals(command)) { + String json = getRequestPayloadAsString(_req); + List transitions = DaoSerializer.parseList(json, SwitchSchedule.class); + Globals.app.setSwitchSchedule(nodeId, transitions); + } + } + } +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/TemperatureServlet.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/TemperatureServlet.java new file mode 100644 index 0000000..e81a563 --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/TemperatureServlet.java @@ -0,0 +1,22 @@ +package com.lanternsoftware.zwave.servlet; + +import com.lanternsoftware.datamodel.currentmonitor.AuthCode; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.DaoEntity; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.zwave.context.Globals; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/temperature/*") +public class TemperatureServlet extends SecureServlet { + @Override + protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) { + String[] path = path(_req); + DaoEntity json = new DaoEntity("temp", Globals.app.getTemperatureCelsius(NullUtils.toInteger(CollectionUtils.get(path, 0)))); + jsonResponse(_rep, DaoSerializer.toJson(json)); + } +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/ZWaveServlet.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/ZWaveServlet.java new file mode 100644 index 0000000..6bbe464 --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/servlet/ZWaveServlet.java @@ -0,0 +1,87 @@ +package com.lanternsoftware.zwave.servlet; + +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.dao.DaoSerializer; +import org.apache.commons.io.IOUtils; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; +import java.io.InputStream; +import java.io.OutputStream; + +public abstract class ZWaveServlet extends HttpServlet { + public static void setResponseHtml(HttpServletResponse _response, String _sHtml) { + setResponseEntity(_response, "text/html", _sHtml); + } + + public static void setResponseEntity(HttpServletResponse _response, String _sContentType, String _sEntity) { + setResponseEntity(_response, 200, _sContentType, _sEntity); + } + + public static void setResponseEntity(HttpServletResponse _response, String _sContentType, byte[] _btData) { + setResponseEntity(_response, 200, _sContentType, _btData); + } + + public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, String _sEntity) { + setResponseEntity(_response, _iStatus, _sContentType, NullUtils.toByteArray(_sEntity)); + } + + public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, byte[] _btData) { + OutputStream os = null; + try { + _response.setStatus(_iStatus); + _response.setCharacterEncoding("UTF-8"); + _response.setContentType(_sContentType); + if ((_btData != null) && (_btData.length > 0)) { + _response.setContentLength(_btData.length); + os = _response.getOutputStream(); + os.write(_btData); + } else + _response.setContentLength(0); + } catch (Exception e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(os); + } + } + + protected void zipBsonResponse(HttpServletResponse _response, Object _object) + { + setResponseEntity(_response, 200, MediaType.APPLICATION_OCTET_STREAM, DaoSerializer.toZipBson(_object)); + } + + protected void jsonResponse(HttpServletResponse _response, Object _object) + { + setResponseEntity(_response, 200, MediaType.APPLICATION_JSON, DaoSerializer.toJson(_object)); + } + + protected void jsonResponse(HttpServletResponse _response, String _json) + { + setResponseEntity(_response, 200, MediaType.APPLICATION_JSON, _json); + } + + protected String getRequestPayloadAsString(HttpServletRequest _req) { + return NullUtils.toString(getRequestPayload(_req)); + } + + protected byte[] getRequestPayload(HttpServletRequest _req) { + InputStream is = null; + try { + is = _req.getInputStream(); + return IOUtils.toByteArray(is); + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + finally { + IOUtils.closeQuietly(is); + } + } + + protected String[] path(HttpServletRequest _req) { + return NullUtils.cleanSplit(NullUtils.makeNotNull(_req.getPathInfo()), "/"); + } +} diff --git a/zwave/lantern-service-zwave/src/main/resources/logback.xml b/zwave/lantern-service-zwave/src/main/resources/logback.xml new file mode 100644 index 0000000..0c83f96 --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + + + ${log.pattern} + + + + + + + + + + \ No newline at end of file diff --git a/zwave/lantern-service-zwave/src/main/resources/mitsubishi_ir_codes.csv b/zwave/lantern-service-zwave/src/main/resources/mitsubishi_ir_codes.csv new file mode 100644 index 0000000..eae62fa --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/resources/mitsubishi_ir_codes.csv @@ -0,0 +1,59 @@ +Power,Mode,Temperature,Code +Off,Heat,72,0000 006C 0000 0083 0010 00D1 0010 0010 0010 0011 0010 00F1 0010 0030 0010 0030 0010 0071 0010 0030 0010 0010 0010 0031 0010 0031 0010 0030 0010 0011 0010 01B2 0080 0041 0010 0031 0010 0030 0010 0011 0010 0010 0010 0011 0010 0031 0010 0010 0010 0010 0010 0031 0010 0031 0010 0011 0010 0031 0010 0011 0010 0011 0010 0031 0010 0031 0010 0010 0010 0031 0010 0031 0010 0010 0010 0011 0010 0031 0010 0010 0010 0010 0010 0031 0010 0011 0010 0010 0010 0010 0010 0071 0010 0091 0010 0011 0010 00B1 0010 0091 0010 0071 0010 0011 0010 0011 0010 0031 0010 0010 0010 0010 0010 0031 0010 0031 0010 0010 0010 0031 0010 0011 0010 0011 0010 0011 0010 0030 0010 0010 0010 0051 0010 0010 0010 0010 0010 0011 0010 0011 0010 0071 0010 0030 0010 0010 0010 0031 0010 0010 0010 0010 0010 0031 0010 0031 0010 0031 0010 0010 0010 0031 0010 0031 0010 0010 0010 0010 0010 0010 0010 0010 0010 0011 0010 0011 0010 0011 0010 0010 0010 0010 0010 0011 0010 0010 0010 0031 0010 0010 0010 0010 0010 0010 0010 0011 0010 0011 0010 0011 0010 0011 0010 0030 0010 0010 0010 0011 0010 0051 0010 0010 0010 0010 0010 0010 0010 0031 0010 0011 0010 0010 0010 0030 0010 0010 0010 0031 0010 0010 0010 0010 0010 0010 0010 0071 0010 0010 0010 0031 0010 0031 0010 0010 0010 0011 0010 0010 0010 0010 0010 0030 0010 0031 0010 0010 0010 0030 0010 0031 0010 0031 0010 0011 0010 02CF +On,Heat,61, +On,Heat,62, +On,Heat,63, +On,Heat,64, +On,Heat,65, +On,Heat,66,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01B1 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8E +On,Heat,67,0000 006D 0000 00BF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01AF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F83 +On,Heat,68,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01B0 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8D +On,Heat,69,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01B0 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8B +On,Heat,70,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01B0 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8A +On,Heat,71,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01AF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F86 +On,Heat,72,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01B0 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F89 +On,Heat,73, +On,Heat,74, +On,Heat,75, +On,Heat,76, +On,Heat,77, +On,Heat,78, +On,Heat,79, +On,Heat,80, +On,Heat,81, +On,Heat,82, +On,Heat,83, +On,Heat,84, +On,Heat,85, +On,Heat,86, +On,Heat,87, +On,Heat,88, +Off,Cool,72, +On,Cool,61, +On,Cool,62, +On,Cool,63, +On,Cool,64, +On,Cool,65, +On,Cool,66, +On,Cool,67, +On,Cool,68, +On,Cool,69, +On,Cool,70, +On,Cool,71, +On,Cool,72, +On,Cool,73, +On,Cool,74, +On,Cool,75, +On,Cool,76, +On,Cool,77, +On,Cool,78, +On,Cool,79, +On,Cool,80, +On,Cool,81, +On,Cool,82, +On,Cool,83, +On,Cool,84, +On,Cool,85, +On,Cool,86, +On,Cool,87, +On,Cool,88, diff --git a/zwave/lantern-service-zwave/src/main/webapp/WEB-INF/web.xml b/zwave/lantern-service-zwave/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..9464117 --- /dev/null +++ b/zwave/lantern-service-zwave/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,9 @@ + + + + + com.lanternsoftware.zwave.context.Globals + + \ No newline at end of file diff --git a/zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/CreateConfig.java b/zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/CreateConfig.java new file mode 100644 index 0000000..aab2ea8 --- /dev/null +++ b/zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/CreateConfig.java @@ -0,0 +1,74 @@ +package com.lanternsoftware.zwave; + +import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao; +import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao; +import com.lanternsoftware.datamodel.zwave.Switch; +import com.lanternsoftware.datamodel.zwave.SwitchSchedule; +import com.lanternsoftware.datamodel.zwave.ZWaveConfig; +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.ResourceLoader; +import com.lanternsoftware.util.dao.DaoSerializer; +import com.lanternsoftware.util.dao.mongo.MongoConfig; +import com.lanternsoftware.zwave.dao.MongoZWaveDao; + +public class CreateConfig { + public static void main(String[] args) { + MongoZWaveDao dao = new MongoZWaveDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg")); + ZWaveConfig config = dao.getConfig(1); +// ZWaveConfig cconfig = DaoSerializer.parse(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "config - christmas lights.dat"), ZWaveConfig.class); +// Switch c = CollectionUtils.filterOne(config.getSwitches(), _s->_s.getName().contains("hristm")); +// CollectionUtils.filterMod(config.getSwitches(), _s->!_s.getRoom().equals("Treehouse")); +// Switch treehouse = new Switch("Treehouse", "Interior", 14, true, true, null, 0); +// Switch to = new Switch("Treehouse", "Floods", 15, true, true, null, 0); +// Switch out = new Switch("Outside", "Repeater Outlet", 10, true, false, null, 0); +// config.getSwitches().add(out); + Switch c = CollectionUtils.filterOne(config.getSwitches(), _s->_s.getName().contains("Agitator")); + c.setName("Septic Aerator"); + dao.putConfig(config); +// if (c != null) { +// c.setNodeId(8); +// dao.putConfig(config); +// } +// ZWaveConfig config = DaoSerializer.parse(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "config.dat"), ZWaveConfig.class); +// config.setAccountId(1); +// config.getSwitches().add(new Switch("Garage", "Septic Agitator", 12, 0, true, false, false, null, 0, Arrays.asList(new SwitchTransition(20)))); +// Switch thermo = CollectionUtils.filterOne(config.getSwitches(), _sw->_sw.getNodeId() == 0); +// if (thermo != null) +// thermo.setNodeId(100); +// config.getSwitches().add(new Switch("Basement", "Temperature", 101, true, false, "https://basement.lanternsoftware.com/thermometer/temp", 0)); + +/* ZWaveConfig config = new ZWaveConfig(); + List switches = new ArrayList<>(); + switches.add(new Switch("Basement", "Main", 3, true, true, null, 0)); + switches.add(new Switch("Basement", "Main", 5, false, true, null, 0)); + switches.add(new Switch("Basement", "Bar", 4, true, true, null, 0)); + switches.add(new Switch("Basement", "Bar", 6, false, true, null, 0)); + switches.add(new Switch("Master Bedroom", "Heater", 7, true, true, "https://thermometer.lanternsoftware.com/thermometer/temp", 0)); + switches.add(new Switch("Bruce's Room", "Heater", 8, true, true, "https://bruce.lanternsoftware.com/thermometer/temp", 0)); + switches.add(new Switch("Master Bedroom", "Heater", 9, false, true, "", 0)); + Switch out = new Switch("Outside", "Christmas Lights", 10, true, false, "", 0); + out.setSchedule(CollectionUtils.asArrayList( + new SwitchTransition(Calendar.FRIDAY, 12, 42, 0, 0), + new SwitchTransition(Calendar.FRIDAY, 12, 42, 10, 0xFF), + new SwitchTransition(Calendar.FRIDAY, 12, 42, 20, 0), + new SwitchTransition(Calendar.FRIDAY, 12, 42, 30, 0xFF), + new SwitchTransition(Calendar.FRIDAY, 12, 42, 40, 0), + new SwitchTransition(Calendar.FRIDAY, 12, 42, 50, 0xFF))); + switches.add(out); + config.setSwitches(switches); + */ +// config.setSwitches(switches); +// Switch sump = CollectionUtils.filterOne(config.getSwitches(), _c->_c.getNodeId() == 12); +// SwitchSchedule transition = CollectionUtils.getFirst(sump.getSchedule()); +// transition.setLevel(0xFF); +// transition.setMinutesPerHour(15); +// dao.putConfig(config); + dao.shutdown(); +// TimeZone tz = TimeZone.getTimeZone("America/Chicago"); +// Date next = transition.getNextTransition(tz); +// System.out.println("Next Transition: " + DateUtils.format(tz, next, "hh:mm:ssa")); + +// ResourceLoader.writeFile(LanternFiles.OPS_PATH + "config.dat", DaoSerializer.toJson(config)); + } +} diff --git a/zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/GenerateSerializers.java b/zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/GenerateSerializers.java new file mode 100644 index 0000000..4f2efee --- /dev/null +++ b/zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/GenerateSerializers.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.zwave; + + +import com.lanternsoftware.util.LanternFiles; +import com.lanternsoftware.util.dao.generator.DaoSerializerGenerator; + +public class GenerateSerializers { + public static void main(String[] args) { + DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_PATH + "zwave", true, null); + } +} diff --git a/zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/TestStartup.java b/zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/TestStartup.java new file mode 100644 index 0000000..ef3b633 --- /dev/null +++ b/zwave/lantern-service-zwave/src/test/java/com/lanternsoftware/zwave/TestStartup.java @@ -0,0 +1,16 @@ +package com.lanternsoftware.zwave; + +import com.lanternsoftware.zwave.context.ZWaveApp; + +public class TestStartup { + public static void main(String[] args) { + ZWaveApp app = new ZWaveApp(); + app.start(); + try { + Thread.sleep(20000); + } catch (InterruptedException _e) { + _e.printStackTrace(); + } + app.stop(); + } +} diff --git a/zwave/lantern-uirt/pom.xml b/zwave/lantern-uirt/pom.xml new file mode 100644 index 0000000..2aa546a --- /dev/null +++ b/zwave/lantern-uirt/pom.xml @@ -0,0 +1,78 @@ + + 4.0.0 + com.lanternsoftware.services + lantern-uirt + jar + 1.0.0 + lantern-uirt + + + 1.8 + 1.8 + + + + + com.lanternsoftware.util + lantern-util-dao + 1.0.0 + + + net.java.dev.jna + jna + 5.5.0 + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + package + + jar + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.5 + + + true + + + + + + diff --git a/zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/UsbUirt.java b/zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/UsbUirt.java new file mode 100644 index 0000000..dde985f --- /dev/null +++ b/zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/UsbUirt.java @@ -0,0 +1,94 @@ +package com.lanternsoftware.uirt; + +import com.lanternsoftware.uirt.model.UIRTConfig; +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.ptr.PointerByReference; +import com.sun.jna.win32.StdCallLibrary; +import com.sun.jna.win32.W32APIOptions; + +public class UsbUirt { + private static String off = "0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01AF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F85"; + private UirtLibrary lib; + private Pointer handle; + + public interface UirtLibrary extends Library { + interface ReceiveProc extends StdCallLibrary.StdCallCallback { + boolean callback(Pointer _event, Pointer _userData); + } + interface LearnProc extends StdCallLibrary.StdCallCallback { + boolean callback(int progress, int _sigQuality, long carrierFreq, Pointer _userData); + } + + Pointer UUIRTOpen(); + boolean UUIRTClose(Pointer _handle); + boolean UUIRTGetDrvVersion(IntByReference _version); + boolean UUIRTGetUUIRTConfig(Pointer _handle, UIRTConfig.ByReference _config); + boolean UUIRTSetReceiveCallback(Pointer _handle, ReceiveProc _proc, Pointer _userData); + boolean UUIRTSetRawReceiveCallback(Pointer _handle, ReceiveProc _proc, Pointer _userData); + boolean UUIRTLearnIR(Pointer _handle, int codeFormat, PointerByReference irCode, LearnProc progressProc, Pointer _userData, IntByReference _abort, int _param1, Pointer reserved0, Pointer reserved1); + boolean UUIRTTransmitIR(Pointer _handle, String _code, int _codeFormat, int repeatCount_, int _inactivityWaitTime, Pointer _event, Pointer reserved0, Pointer reserved1); + } + + public void startup() { + lib = Native.load("uuirtdrv", UirtLibrary.class, W32APIOptions.ASCII_OPTIONS); + handle = lib.UUIRTOpen(); + } + + public void shutdown() { + if (isStarted()) + lib.UUIRTClose(handle); + lib = null; + } + + public int getDriverVersion() { + if (!isStarted()) + return 0; + IntByReference version = new IntByReference(); + lib.UUIRTGetDrvVersion(version); + return version.getValue(); + } + + public UIRTConfig getConfig() { + if (!isStarted()) + return null; + UIRTConfig.ByReference configRef = new UIRTConfig.ByReference(); + lib.UUIRTGetUUIRTConfig(handle, configRef); + return configRef; + } + + public void setReceiveCallback(UirtLibrary.ReceiveProc _proc) { + if (isStarted()) + lib.UUIRTSetReceiveCallback(handle, _proc, null); + } + + public void setRawReceiveCallback(UirtLibrary.ReceiveProc _proc) { + if (isStarted()) + lib.UUIRTSetRawReceiveCallback(handle, _proc, null); + } + + public String learnCode(UirtLibrary.LearnProc _proc) { + PointerByReference codeRef = new PointerByReference(); + lib.UUIRTLearnIR(handle, 0, codeRef, _proc, null, null, 0, null, null); + Pointer code = codeRef.getValue(); + return code == null ? null : code.getString(0); + } + + public boolean transmitIR(boolean _pronto, int _repeatCnt, int _inactivityWaitTime, String _code) { + PointerByReference result = new PointerByReference(); + return lib.UUIRTTransmitIR(handle, _code, _pronto?0x0010:0x0000, _repeatCnt, _inactivityWaitTime, null, null, null); + } + + private boolean isStarted() { + return (lib != null) && (handle != null); + } + + public static void main(String[] args) { + UsbUirt uirt = new UsbUirt(); + uirt.startup(); + uirt.transmitIR(true, 3, 0, off); + uirt.shutdown(); + } +} diff --git a/zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/model/UIRTConfig.java b/zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/model/UIRTConfig.java new file mode 100644 index 0000000..26b95bf --- /dev/null +++ b/zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/model/UIRTConfig.java @@ -0,0 +1,51 @@ +package com.lanternsoftware.uirt.model; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +public class UIRTConfig extends Structure { + public static class ByReference extends UIRTConfig implements Structure.ByReference {} + + public boolean ledRX; + public boolean ledTX; + public boolean legacyRX; + + public UIRTConfig() { + } + + public UIRTConfig(Pointer _value) { + super(_value); + } + + @Override + protected List getFieldOrder() { + return Arrays.asList("ledRX", "ledTX", "legacyRX"); + } + + public boolean isLedRX() { + return ledRX; + } + + public void setLedRX(boolean _ledRX) { + ledRX = _ledRX; + } + + public boolean isLedTX() { + return ledTX; + } + + public void setLedTX(boolean _ledTX) { + ledTX = _ledTX; + } + + public boolean isLegacyRX() { + return legacyRX; + } + + public void setLegacyRX(boolean _legacyRX) { + legacyRX = _legacyRX; + } +} diff --git a/zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/model/UIRTEvent.java b/zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/model/UIRTEvent.java new file mode 100644 index 0000000..05498bd --- /dev/null +++ b/zwave/lantern-uirt/src/main/java/com/lanternsoftware/uirt/model/UIRTEvent.java @@ -0,0 +1,26 @@ +package com.lanternsoftware.uirt.model; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.List; + +public class UIRTEvent extends Structure { + public static class ByReference extends UIRTEvent implements Structure.ByReference {} + + public int code; + public String data; + + public UIRTEvent() { + } + + public UIRTEvent(Pointer p) { + super(p); + } + + @Override + protected List getFieldOrder() { + return Arrays.asList("code", "data"); + } +} diff --git a/zwave/lantern-zwave/pom.xml b/zwave/lantern-zwave/pom.xml new file mode 100644 index 0000000..482c920 --- /dev/null +++ b/zwave/lantern-zwave/pom.xml @@ -0,0 +1,65 @@ + + 4.0.0 + com.lanternsoftware.zwave + lantern-zwave + jar + 1.0.0 + lantern-zwave + + + 1.8 + 1.8 + + + + + com.neuronrobotics + nrjavaserial + 3.15.0 + + + org.slf4j + slf4j-api + 1.7.29 + + + ch.qos.logback + logback-core + 1.2.3 + + + com.lanternsoftware.util + lantern-util-dao + 1.0.0 + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + testCompile + + compile + + + + true + true + UTF-8 + 1.8 + 1.8 + + + + + diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/controller/Controller.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/controller/Controller.java new file mode 100644 index 0000000..ad85e90 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/controller/Controller.java @@ -0,0 +1,237 @@ +package com.lanternsoftware.zwave.controller; + +import com.lanternsoftware.util.NullUtils; +import com.lanternsoftware.util.concurrency.ConcurrencyUtils; +import com.lanternsoftware.zwave.message.IMessageSubscriber; +import com.lanternsoftware.zwave.message.Message; +import com.lanternsoftware.zwave.message.MessageEngine; +import com.lanternsoftware.zwave.message.RequestMessage; +import com.lanternsoftware.zwave.message.ResponseMessage; +import com.lanternsoftware.zwave.message.impl.ByteMessage; +import com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesRequest; +import com.lanternsoftware.zwave.message.impl.ControllerInitialDataRequest; +import com.lanternsoftware.zwave.message.impl.GetControllerIdRequest; +import com.lanternsoftware.zwave.message.impl.SendDataRequest; +import com.lanternsoftware.zwave.node.NodeManager; +import gnu.io.CommPortIdentifier; +import gnu.io.SerialPort; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +public class Controller { + private static final byte SOF = 0x01; + private static final byte ACK = 0x06; + private static final byte NAK = 0x15; + private static final byte CAN = 0x18; + + private static final Logger logger = LoggerFactory.getLogger(Controller.class); + + private SerialPort serialPort; + private OutputStream os; + private boolean running = false; + private AtomicInteger callbackId = new AtomicInteger(0); + private final Object ackMutex = new Object(); + private final Object responseMutex = new Object(); + private final Object callbackMutex = new Object(); + private boolean responseReceived; + private final Map callbacks = new HashMap<>(); + private ExecutorService executor = Executors.newFixedThreadPool(2); + private NodeManager nodeManager; + + public boolean start(String _port) { + try { + CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(_port); + serialPort = portIdentifier.open("zwaveport", 2000); + serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); + serialPort.enableReceiveThreshold(1); + serialPort.enableReceiveTimeout(1000); + os = serialPort.getOutputStream(); + running = true; + executor.submit(new MessageReceiver()); + MessageEngine.subscribe(new SendDataRequestHandler()); + nodeManager = new NodeManager(this); + send(new ControllerCapabilitiesRequest()); + send(new ControllerInitialDataRequest()); + send(new GetControllerIdRequest()); + nodeManager.waitForStartup(); + logger.debug("Finishing Controller Start"); + return true; + } catch (Exception _e) { + if (serialPort != null) { + serialPort.close(); + serialPort = null; + } + logger.error("Exception while starting controller", _e); + return false; + } + } + + public void stop() { + running = false; + ConcurrencyUtils.sleep(2000); + IOUtils.closeQuietly(os); + if (serialPort != null) { + serialPort.close(); + } + executor.shutdown(); + } + + public void send(Message _message) { + executor.submit(new MessageSender(_message)); + } + + private class MessageReceiver implements Runnable { + @Override + public void run() { + InputStream is = null; + try { + is = serialPort.getInputStream(); + int nextByte = 0; + int offset = 0; + while (running) { + nextByte = is.read(); + if (nextByte == -1) + continue; + switch (nextByte) { + case SOF: + int messageLength = is.read(); + byte[] buffer = new byte[messageLength + 2]; + buffer[0] = SOF; + buffer[1] = (byte) messageLength; + offset = 2; + while (offset < messageLength + 2) { + offset += is.read(buffer, offset, messageLength + 2 - offset); + } + processIncomingMessage(buffer); + break; + case ACK: + synchronized (ackMutex) { + logger.debug("Received ACK"); + ackMutex.notify(); + } + MessageEngine.publish(new ByteMessage((byte) nextByte)); + break; + case NAK: + case CAN: + synchronized (ackMutex) { + logger.debug("Received: {}", NullUtils.toHex(new byte[]{(byte) nextByte})); + ackMutex.notify(); + } + MessageEngine.publish(new ByteMessage((byte) nextByte)); + break; + default: + sendRaw(new byte[]{NAK}); + break; + } + } + } catch (IOException _e) { + logger.error("Exception while receiving inbound, stopping controller", _e); + stop(); + } finally { + IOUtils.closeQuietly(is); + } + } + } + + private class MessageSender implements Runnable { + private final Message message; + + MessageSender(Message _message) { + message = _message; + } + + @Override + public void run() { + try { + synchronized (Controller.this) { + byte callback = 0; + String log = "Sending message outbound: " + message.describe(); + if (message.isCallbackExpected()) { + callback = (byte) (callbackId.getAndIncrement() % 126 + 1); + callbacks.put(callback, message.getNodeId()); + log += " callback: " + callback; + } + logger.info(log); + byte[] data = message.toByteArray((byte) 0, callback); + logger.debug("Sending outbound: {}", NullUtils.toHexBytes(data)); + responseReceived = false; + sendRaw(data); + synchronized (ackMutex) { + ackMutex.wait(1000); + } + logger.debug("Finished outbound of: {}", message.describe()); + } + if (message instanceof RequestMessage) { + logger.debug("Waiting for response from: {}", message.describe()); + synchronized (responseMutex) { + responseMutex.wait(1000); + logger.debug("Response received: {}", responseReceived); + responseReceived = false; + } + } + if (message.isCallbackExpected()) { + logger.debug("Waiting for callback from: {}", message.describe()); + synchronized (callbackMutex) { + callbackMutex.wait(1000); + } + } + } catch (InterruptedException _e) { + logger.error("Interrupted while sending outbound", _e); + } + } + } + + private void sendRaw(byte[] _data) { + try { + os.write(_data); + os.flush(); + } catch (IOException _e) { + logger.error("IO exception while sending outbound", _e); + } + } + + private void processIncomingMessage(byte[] _buffer) { + logger.debug("Received inbound: {}", NullUtils.toHexBytes(_buffer)); + logger.debug("Sending ACK"); + sendRaw(new byte[]{ACK}); + Message message = MessageEngine.decode(_buffer); + if (message != null) { + logger.info("Received message inbound: {}", message.describe()); + MessageEngine.publish(message); + if (message instanceof ResponseMessage) { + synchronized (responseMutex) { + responseReceived = true; + responseMutex.notify(); + } + } + } + } + + private class SendDataRequestHandler implements IMessageSubscriber { + @Override + public Class getHandledMessageClass() { + return SendDataRequest.class; + } + + @Override + public void onMessage(SendDataRequest _message) { + Byte nodeId = callbacks.remove(_message.getCallbackId()); + if (nodeId != null) { + logger.debug("Received callback for node: {} callback id: {}", nodeId, _message.getCallbackId()); + synchronized (callbackMutex) { + callbackMutex.notify(); + } + } + } + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/CommandClass.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/CommandClass.java new file mode 100644 index 0000000..2f83015 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/CommandClass.java @@ -0,0 +1,119 @@ +package com.lanternsoftware.zwave.message; + +import java.util.HashMap; +import java.util.Map; + +public enum CommandClass { + NO_OPERATION((byte)0x00, "NO_OPERATION"), + BASIC((byte)0x20, "BASIC"), + CONTROLLER_REPLICATION((byte)0x21, "CONTROLLER_REPLICATION"), + APPLICATION_STATUS((byte)0x22, "APPLICATION_STATUS"), + ZIP_SERVICES((byte)0x23, "ZIP_SERVICES"), + ZIP_SERVER((byte)0x24, "ZIP_SERVER"), + SWITCH_BINARY((byte)0x25, "SWITCH_BINARY", true, 0), + SWITCH_MULTILEVEL((byte)0x26, "SWITCH_MULTILEVEL", true, 0), + SWITCH_ALL((byte)0x27, "SWITCH_ALL"), + SWITCH_TOGGLE_BINARY((byte)0x28, "SWITCH_TOGGLE_BINARY"), + SWITCH_TOGGLE_MULTILEVEL((byte)0x29, "SWITCH_TOGGLE_MULTILEVEL"), + CHIMNEY_FAN((byte)0x2A, "CHIMNEY_FAN"), + SCENE_ACTIVATION((byte)0x2B, "SCENE_ACTIVATION"), + SCENE_ACTUATOR_CONF((byte)0x2C, "SCENE_ACTUATOR_CONF"), + SCENE_CONTROLLER_CONF((byte)0x2D, "SCENE_CONTROLLER_CONF"), + ZIP_CLIENT((byte)0x2E, "ZIP_CLIENT"), + ZIP_ADV_SERVICES((byte)0x2F, "ZIP_ADV_SERVICES"), + SENSOR_BINARY((byte)0x30, "SENSOR_BINARY"), + SENSOR_MULTILEVEL((byte)0x31, "SENSOR_MULTILEVEL"), + METER((byte)0x32, "METER", true, 60), + ZIP_ADV_SERVER((byte)0x33, "ZIP_ADV_SERVER"), + ZIP_ADV_CLIENT((byte)0x34, "ZIP_ADV_CLIENT"), + METER_PULSE((byte)0x35, "METER_PULSE"), + METER_TBL_CONFIG((byte)0x3C, "METER_TBL_CONFIG"), + METER_TBL_MONITOR((byte)0x3D, "METER_TBL_MONITOR"), + METER_TBL_PUSH((byte)0x3E, "METER_TBL_PUSH"), + THERMOSTAT_HEATING((byte)0x38, "THERMOSTAT_HEATING"), + THERMOSTAT_MODE((byte)0x40, "THERMOSTAT_MODE"), + THERMOSTAT_OPERATING_STATE((byte)0x42, "THERMOSTAT_OPERATING_STATE"), + THERMOSTAT_SETPOINT((byte)0x43, "THERMOSTAT_SETPOINT"), + THERMOSTAT_FAN_MODE((byte)0x44, "THERMOSTAT_FAN_MODE"), + THERMOSTAT_FAN_STATE((byte)0x45, "THERMOSTAT_FAN_STATE"), + CLIMATE_CONTROL_SCHEDULE((byte)0x46, "CLIMATE_CONTROL_SCHEDULE"), + THERMOSTAT_SETBACK((byte)0x47, "THERMOSTAT_SETBACK"), + DOOR_LOCK_LOGGING((byte)0x4C, "DOOR_LOCK_LOGGING"), + SCHEDULE_ENTRY_LOCK((byte)0x4E, "SCHEDULE_ENTRY_LOCK"), + BASIC_WINDOW_COVERING((byte)0x50, "BASIC_WINDOW_COVERING"), + MTP_WINDOW_COVERING((byte)0x51, "MTP_WINDOW_COVERING"), + MULTI_INSTANCE((byte)0x60, "MULTI_INSTANCE"), + DOOR_LOCK((byte)0x62, "DOOR_LOCK"), + USER_CODE((byte)0x63, "USER_CODE"), + CONFIGURATION((byte)0x70, "CONFIGURATION"), + ALARM((byte)0x71, "ALARM"), + MANUFACTURER_SPECIFIC((byte)0x72, "MANUFACTURER_SPECIFIC"), + POWERLEVEL((byte)0x73, "POWERLEVEL"), + PROTECTION((byte)0x75, "PROTECTION"), + LOCK((byte)0x76, "LOCK"), + NODE_NAMING((byte)0x77, "NODE_NAMING"), + FIRMWARE_UPDATE_MD((byte)0x7A, "FIRMWARE_UPDATE_MD"), + GROUPING_NAME((byte)0x7B, "GROUPING_NAME"), + REMOTE_ASSOCIATION_ACTIVATE((byte)0x7C, "REMOTE_ASSOCIATION_ACTIVATE"), + REMOTE_ASSOCIATION((byte)0x7D, "REMOTE_ASSOCIATION"), + BATTERY((byte)0x80, "BATTERY", true, 3600), + CLOCK((byte)0x81, "CLOCK"), + HAIL((byte)0x82, "HAIL"), + WAKE_UP((byte)0x84, "WAKE_UP"), + ASSOCIATION((byte)0x85, "ASSOCIATION"), + VERSION((byte)0x86, "VERSION"), + INDICATOR((byte)0x87, "INDICATOR"), + PROPRIETARY((byte)0x88, "PROPRIETARY"), + LANGUAGE((byte)0x89, "LANGUAGE"), + TIME((byte)0x8A, "TIME"), + TIME_PARAMETERS((byte)0x8B, "TIME_PARAMETERS"), + GEOGRAPHIC_LOCATION((byte)0x8C, "GEOGRAPHIC_LOCATION"), + COMPOSITE((byte)0x8D, "COMPOSITE"), + MULTI_INSTANCE_ASSOCIATION((byte)0x8E, "MULTI_INSTANCE_ASSOCIATION"), + MULTI_CMD((byte)0x8F, "MULTI_CMD"), + ENERGY_PRODUCTION((byte)0x90, "ENERGY_PRODUCTION"), + MANUFACTURER_PROPRIETARY((byte)0x91, "MANUFACTURER_PROPRIETARY"), + SCREEN_MD((byte)0x92, "SCREEN_MD"), + SCREEN_ATTRIBUTES((byte)0x93, "SCREEN_ATTRIBUTES"), + SIMPLE_AV_CONTROL((byte)0x94, "SIMPLE_AV_CONTROL"), + AV_CONTENT_DIRECTORY_MD((byte)0x95, "AV_CONTENT_DIRECTORY_MD"), + AV_RENDERER_STATUS((byte)0x96, "AV_RENDERER_STATUS"), + AV_CONTENT_SEARCH_MD((byte)0x97, "AV_CONTENT_SEARCH_MD"), + SECURITY((byte)0x98, "SECURITY"), + AV_TAGGING_MD((byte)0x99, "AV_TAGGING_MD"), + IP_CONFIGURATION((byte)0x9A, "IP_CONFIGURATION"), + ASSOCIATION_COMMAND_CONFIGURATION((byte)0x9B, "ASSOCIATION_COMMAND_CONFIGURATION"), + SENSOR_ALARM((byte)0x9C, "SENSOR_ALARM"), + SILENCE_ALARM((byte)0x9D, "SILENCE_ALARM"), + SENSOR_CONFIGURATION((byte)0x9E, "SENSOR_CONFIGURATION"), + MARK((byte)0xEF, "MARK"), + NON_INTEROPERABLE((byte)0xF0, "NON_INTEROPERABLE"), + ALL((byte)0xFF, null); + + public final byte data; + public final String label; + public final boolean supportsPolling; + public final int secondsRefreshInterval; + private static final Map classes = new HashMap<>(); + static { + for (CommandClass cls : values()) { + classes.put(cls.data, cls); + } + } + + CommandClass(byte _data, String _label) { + this(_data, _label, false, 0); + } + + CommandClass(byte _data, String _label, boolean _supportsPolling, int _secondsRefreshInterval) { + data = _data; + label = _label; + supportsPolling = _supportsPolling; + secondsRefreshInterval = _secondsRefreshInterval; + } + + public static CommandClass fromByte(byte _code) { + CommandClass cls = classes.get(_code); + return cls == null ? CommandClass.NO_OPERATION : cls; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/ControllerMessageType.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/ControllerMessageType.java new file mode 100644 index 0000000..2a66073 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/ControllerMessageType.java @@ -0,0 +1,92 @@ +package com.lanternsoftware.zwave.message; + +import com.lanternsoftware.util.CollectionUtils; + +import java.util.Arrays; +import java.util.Map; + +public enum ControllerMessageType { + None((byte)0x0,"None"), + SerialApiGetInitData((byte)0x02,"SerialApiGetInitData"), // Request initial information about devices in network + SerialApiApplicationNodeInfo((byte)0x03,"SerialApiApplicationNodeInfo"), // Set controller node information + ApplicationCommandHandler((byte)0x04,"ApplicationCommandHandler"), // Handle application command + GetControllerCapabilities((byte)0x05,"GetControllerCapabilities"), // Request controller capabilities (primary role, SUC/SIS availability) + SerialApiSetTimeouts((byte)0x06,"SerialApiSetTimeouts"), // Set Serial API timeouts + GetCapabilities((byte)0x07,"GetCapabilities"), // Request Serial API capabilities from the controller + SerialApiSoftReset((byte)0x08,"SerialApiSoftReset"), // Soft reset. Restarts Z-Wave chip + RfReceiveMode((byte)0x10,"RfReceiveMode"), // Power down the RF section of the stick + SetSleepMode((byte)0x11,"SetSleepMode"), // Set the CPU into sleep mode + SendNodeInfo((byte)0x12,"SendNodeInfo"), // Send Node Information Frame of the stick + SendData((byte)0x13,"SendData"), // Send data. + SendDataMulti((byte)0x14, "SendDataMulti"), + GetVersion((byte)0x15,"GetVersion"), // Request controller hardware version + SendDataAbort((byte)0x16,"SendDataAbort"), // Abort Send data. + RfPowerLevelSet((byte)0x17,"RfPowerLevelSet"), // Set RF Power level + SendDataMeta((byte)0x18, "SendDataMeta"), + GetRandom((byte)0x1c,"GetRandom"), // ??? + MemoryGetId((byte)0x20,"MemoryGetId"), // ??? + MemoryGetByte((byte)0x21,"MemoryGetByte"), // Get a byte of memory. + MemoryPutByte((byte)0x22, "MemoryPutByte"), + ReadMemory((byte)0x23,"ReadMemory"), // Read memory. + WriteMemory((byte)0x24, "WriteMemory"), + SetLearnNodeState((byte)0x40,"SetLearnNodeState"), // ??? + IdentifyNode((byte)0x41,"IdentifyNode"), // Get protocol info (baud rate, listening, etc.) for a given node + SetDefault((byte)0x42,"SetDefault"), // Reset controller and node info to default (original) values + NewController((byte)0x43,"NewController"), // ??? + ReplicationCommandComplete((byte)0x44,"ReplicationCommandComplete"), // Replication send data complete + ReplicationSendData((byte)0x45,"ReplicationSendData"), // Replication send data + AssignReturnRoute((byte)0x46,"AssignReturnRoute"), // Assign a return route from the specified node to the controller + DeleteReturnRoute((byte)0x47,"DeleteReturnRoute"), // Delete all return routes from the specified node + RequestNodeNeighborUpdate((byte)0x48,"RequestNodeNeighborUpdate"), // Ask the specified node to update its neighbors (then read them from the controller) + ApplicationUpdate((byte)0x49,"ApplicationUpdate"), // Get a list of supported (and controller) command classes + AddNodeToNetwork((byte)0x4a,"AddNodeToNetwork"), // Control the addnode (or addcontroller) process...start, stop, etc. + RemoveNodeFromNetwork((byte)0x4b,"RemoveNodeFromNetwork"), // Control the removenode (or removecontroller) process...start, stop, etc. + CreateNewPrimary((byte)0x4c,"CreateNewPrimary"), // Control the createnewprimary process...start, stop, etc. + ControllerChange((byte)0x4d,"ControllerChange"), // Control the transferprimary process...start, stop, etc. + SetLearnMode((byte)0x50,"SetLearnMode"), // Put a controller into learn mode for replication/ receipt of configuration info + AssignSucReturnRoute((byte)0x51,"AssignSucReturnRoute"), // Assign a return route to the SUC + EnableSuc((byte)0x52,"EnableSuc"), // Make a controller a Static Update Controller + RequestNetworkUpdate((byte)0x53,"RequestNetworkUpdate"), // Network update for a SUC(?) + SetSucNodeID((byte)0x54,"SetSucNodeID"), // Identify a Static Update Controller node id + DeleteSUCReturnRoute((byte)0x55,"DeleteSUCReturnRoute"), // Remove return routes to the SUC + GetSucNodeId((byte)0x56,"GetSucNodeId"), // Try to retrieve a Static Update Controller node id (zero if no SUC present) + SendSucId((byte)0x57, "SendSucId"), + RequestNodeNeighborUpdateOptions((byte)0x5a,"RequestNodeNeighborUpdateOptions"), // Allow options for request node neighbor update + RequestNodeInfo((byte)0x60,"RequestNodeInfo"), // Get info (supported command classes) for the specified node + RemoveFailedNodeID((byte)0x61,"RemoveFailedNodeID"), // Mark a specified node id as failed + IsFailedNodeID((byte)0x62,"IsFailedNodeID"), // Check to see if a specified node has failed + ReplaceFailedNode((byte)0x63,"ReplaceFailedNode"), // Remove a failed node from the controller's list (?) + GetRoutingInfo((byte)0x80,"GetRoutingInfo"), // Get a specified node's neighbor information from the controller + LockRoute((byte)0x90, "LockRoute"), + SerialApiSlaveNodeInfo((byte)0xA0,"SerialApiSlaveNodeInfo"), // Set application virtual slave node information + ApplicationSlaveCommandHandler((byte)0xA1,"ApplicationSlaveCommandHandler"), // Slave command handler + SendSlaveNodeInfo((byte)0xA2,"ApplicationSlaveCommandHandler"), // Send a slave node information frame + SendSlaveData((byte)0xA3,"SendSlaveData"), // Send data from slave + SetSlaveLearnMode((byte)0xA4,"SetSlaveLearnMode"), // Enter slave learn mode + GetVirtualNodes((byte)0xA5,"GetVirtualNodes"), // Return all virtual nodes + IsVirtualNode((byte)0xA6,"IsVirtualNode"), // Virtual node test + WatchDogEnable((byte)0xB6, "WatchDogEnable"), + WatchDogDisable((byte)0xB7, "WatchDogDisable"), + WatchDogKick((byte)0xB6, "WatchDogKick"), + RfPowerLevelGet((byte)0xBA,"RfPowerLevelSet"), // Get RF Power level + GetLibraryType((byte)0xBD, "GetLibraryType"), // Gets the type of ZWave library on the stick + SendTestFrame((byte)0xBE, "SendTestFrame"), // Send a test frame to a node + GetProtocolStatus((byte)0xBF, "GetProtocolStatus"), + SetPromiscuousMode((byte)0xD0,"SetPromiscuousMode"), // Set controller into promiscuous mode to listen to all frames + PromiscuousApplicationCommandHandler((byte)0xD1,"PromiscuousApplicationCommandHandler"), + ALL((byte)0xFF, null); + + public final byte data; + public final String label; + + ControllerMessageType(byte _data, String _label) { + data = _data; + label = _label; + } + + private static Map types = CollectionUtils.transformToMap(Arrays.asList(values()), _type->_type.data); + public static ControllerMessageType fromByte(byte _data) { + ControllerMessageType type = types.get(_data); + return type == null ? ControllerMessageType.None : type; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/IMessageSubscriber.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/IMessageSubscriber.java new file mode 100644 index 0000000..b59daf2 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/IMessageSubscriber.java @@ -0,0 +1,6 @@ +package com.lanternsoftware.zwave.message; + +public interface IMessageSubscriber { + Class getHandledMessageClass(); + void onMessage(T _message); +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/Message.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/Message.java new file mode 100644 index 0000000..75bfaac --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/Message.java @@ -0,0 +1,126 @@ +package com.lanternsoftware.zwave.message; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public abstract class Message { + protected byte nodeId; + protected final ControllerMessageType controllerMessageType; + protected final MessageType messageType; + protected final CommandClass commandClass; + protected final byte command; + + public Message(ControllerMessageType _controllerMessageType, MessageType _messageType, CommandClass _commandClass, byte _command) { + this((byte) 0, _controllerMessageType, _messageType, _commandClass, _command); + } + + public Message(byte _nodeId, ControllerMessageType _controllerMessageType, MessageType _messageType, CommandClass _commandClass, byte _command) { + nodeId = _nodeId; + controllerMessageType = _controllerMessageType; + messageType = _messageType; + commandClass = _commandClass; + command = _command; + } + + public byte getNodeId() { + return nodeId; + } + + public void setNodeId(byte _nodeId) { + nodeId = _nodeId; + } + + public byte[] toPayload() { + try { + byte[] payload = getPayload(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + if (nodeId > 0) + os.write(nodeId); + + if (commandClass != CommandClass.NO_OPERATION) { + os.write(payload.length + 2); + os.write(commandClass.data); + os.write(command); + } + if (payload.length > 0) + os.write(payload); + os.close(); + return os.toByteArray(); + } catch (IOException _e) { + _e.printStackTrace(); + return new byte[0]; + } + } + + public void fromPayload(byte[] _payload) { + } + + public byte[] getPayload() { + return new byte[0]; + } + + public byte[] toByteArray(byte _transmitOptions, byte _callbackId) { + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + byte[] payload = toPayload(); + os.write((byte) 0x01); + int messageLength = payload.length + (isCallbackExpected() ? 5 : 3); + os.write((byte) messageLength); + os.write(messageType.data); + os.write(controllerMessageType.data); + if (payload.length > 0) + os.write(payload); + + if (isCallbackExpected()) { + os.write(_transmitOptions); + os.write(_callbackId); + } + + os.write((byte) 1); + byte[] msg = os.toByteArray(); + msg[msg.length - 1] = calculateChecksum(msg); + return msg; + } catch (IOException _e) { + _e.printStackTrace(); + return null; + } + } + + public String getKey() { + return toKey(controllerMessageType.data, messageType.data, commandClass.data, command); + } + + public static String toKey(byte _controllerMessageType, byte _messageType, byte _commandClass, byte _command) { + return String.format("%02X%02X%02X%02X", _controllerMessageType, _messageType, _commandClass, _command); + } + + public static byte calculateChecksum(byte[] buffer) { + byte checkSum = (byte) 0xFF; + for (int i = 1; i < buffer.length - 1; i++) { + checkSum = (byte) (checkSum ^ buffer[i]); + } + return checkSum; + } + + protected byte[] asByteArray(int _byte) { + byte[] ret = new byte[1]; + ret[0] = (byte) _byte; + return ret; + } + + protected byte[] asByteArray(byte... _bytes) { + return _bytes; + } + + public String name() { + return getClass().getSimpleName(); + } + + public boolean isCallbackExpected() { + return (controllerMessageType == ControllerMessageType.SendData) && (messageType == MessageType.REQUEST); + } + + public String describe() { + return name(); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageEngine.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageEngine.java new file mode 100644 index 0000000..83f62cd --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageEngine.java @@ -0,0 +1,63 @@ +package com.lanternsoftware.zwave.message; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.NullUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +public abstract class MessageEngine { + private static final Logger logger = LoggerFactory.getLogger(MessageEngine.class); + + private static final Map messages = new HashMap<>(); + private static final Map, List>> subscribers = new HashMap<>(); + static { + for (Message m : ServiceLoader.load(Message.class)) { + messages.put(m.getKey(), m); + } + for (IMessageSubscriber s : ServiceLoader.load(IMessageSubscriber.class)) { + subscribe(s); + } + } + + public static Message decode(byte[] _data) { + byte messageCheckSum = Message.calculateChecksum(_data); + byte messageCheckSumReceived = _data[_data.length - 1]; + if (messageCheckSum != messageCheckSumReceived) { + logger.debug("Invalid checksum for message: {}", NullUtils.toHex(_data)); + return null; + } + MessageType messageType = _data[2] == 0x00 ? MessageType.REQUEST : MessageType.RESPONSE; + ControllerMessageType controllerMessageType = ControllerMessageType.fromByte((byte)(_data[3] & 0xFF)); + int offset = ((messageType == MessageType.REQUEST) && NullUtils.isOneOf(controllerMessageType, ControllerMessageType.SendData, ControllerMessageType.ApplicationCommandHandler))?7:5; + CommandClass commandClass = _data.length > offset + 1 ? CommandClass.fromByte((byte)(_data[offset] & 0xFF)):CommandClass.NO_OPERATION; + byte command = ((commandClass == CommandClass.NO_OPERATION) || (_data.length <= offset+2))?0:(byte)(_data[offset+1] & 0xFF); + Message message = messages.get(Message.toKey(controllerMessageType.data, messageType.data, commandClass.data, command)); + if (message == null) { + logger.debug("Could not find message class for message: {} {} {} {}", controllerMessageType.label, messageType.name(), commandClass.label, command); + return null; + } + try { + Message ret = message.getClass().newInstance(); + ret.fromPayload(_data); + return ret; + } catch (Exception _e) { + _e.printStackTrace(); + return null; + } + } + + public static void publish(Message _m) { + for (IMessageSubscriber s : CollectionUtils.makeNotNull(subscribers.get(_m.getClass()))) { + s.onMessage(_m); + } + } + + public static void subscribe(IMessageSubscriber _subscriber) { + CollectionUtils.addToMultiMap(_subscriber.getHandledMessageClass(), _subscriber, subscribers); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageType.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageType.java new file mode 100644 index 0000000..e491c69 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageType.java @@ -0,0 +1,12 @@ +package com.lanternsoftware.zwave.message; + +public enum MessageType { + REQUEST((byte)0), + RESPONSE((byte)1); + + public final byte data; + + MessageType(byte _data) { + data = _data; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageUtil.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageUtil.java new file mode 100644 index 0000000..32e5974 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/MessageUtil.java @@ -0,0 +1,39 @@ +package com.lanternsoftware.zwave.message; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public abstract class MessageUtil { + private static final int SIZE_MASK = 0x07; + private static final int SCALE_MASK = 0x18; + private static final int SCALE_SHIFT = 0x03; + private static final int PRECISION_MASK = 0xe0; + private static final int PRECISION_SHIFT = 0x05; + + public static double getTemperatureCelsius(byte[] _payload, int _offset) { + int size = _payload[_offset] & SIZE_MASK; + int scale = (_payload[_offset] & SCALE_MASK) >> SCALE_SHIFT; + int precision = (_payload[_offset] & PRECISION_MASK) >> PRECISION_SHIFT; + + if ((size+_offset) >= _payload.length) + return 0.0; + + int value = 0; + for (int i = 0; i < size; ++i) { + value <<= 8; + value |= _payload[_offset + i + 1] & 0xFF; + } + + BigDecimal result; + if ((_payload[_offset + 1] & 0x80) == 0x80) { + if (size == 1) + value |= 0xffffff00; + else if (size == 2) + value |= 0xffff0000; + } + result = BigDecimal.valueOf(value); + BigDecimal divisor = BigDecimal.valueOf(Math.pow(10, precision)); + double temp = result.divide(divisor, RoundingMode.HALF_EVEN).doubleValue(); + return (scale == 1) ? (temp-32)/1.8 : temp; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/NoCommandRequestMessage.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/NoCommandRequestMessage.java new file mode 100644 index 0000000..9fe8375 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/NoCommandRequestMessage.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.zwave.message; + +public abstract class NoCommandRequestMessage extends RequestMessage { + public NoCommandRequestMessage(ControllerMessageType _controllerMessageType) { + this((byte)0, _controllerMessageType); + } + + public NoCommandRequestMessage(byte _nodeId, ControllerMessageType _controllerMessageType) { + super(_nodeId, _controllerMessageType, CommandClass.NO_OPERATION, (byte) 0); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/NoCommandResponseMessage.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/NoCommandResponseMessage.java new file mode 100644 index 0000000..c5c790d --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/NoCommandResponseMessage.java @@ -0,0 +1,7 @@ +package com.lanternsoftware.zwave.message; + +public abstract class NoCommandResponseMessage extends ResponseMessage { + public NoCommandResponseMessage(ControllerMessageType _controllerMessageType) { + super(_controllerMessageType, CommandClass.NO_OPERATION, (byte) 0); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/RequestMessage.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/RequestMessage.java new file mode 100644 index 0000000..5006fa2 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/RequestMessage.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.zwave.message; + +public abstract class RequestMessage extends Message { + public RequestMessage(ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) { + this((byte)0, _controllerMessageType, _commandClass, _command); + } + + public RequestMessage(byte _nodeId, ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) { + super(_nodeId, _controllerMessageType, MessageType.REQUEST, _commandClass, _command); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/ResponseMessage.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/ResponseMessage.java new file mode 100644 index 0000000..45cf088 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/ResponseMessage.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.zwave.message; + +public abstract class ResponseMessage extends Message { + public ResponseMessage(ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) { + this((byte) 0, _controllerMessageType, _commandClass, _command); + } + + public ResponseMessage(byte _nodeId, ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) { + super(_nodeId, _controllerMessageType, MessageType.RESPONSE, _commandClass, _command); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/SendDataRequestMessage.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/SendDataRequestMessage.java new file mode 100644 index 0000000..ea5f032 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/SendDataRequestMessage.java @@ -0,0 +1,11 @@ +package com.lanternsoftware.zwave.message; + +public abstract class SendDataRequestMessage extends RequestMessage { + public SendDataRequestMessage(CommandClass _commandClass, byte _command) { + this((byte)0, _commandClass, _command); + } + + public SendDataRequestMessage(byte _nodeId, CommandClass _commandClass, byte _command) { + super(_nodeId, ControllerMessageType.SendData, _commandClass, _command); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ApplicationUpdateRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ApplicationUpdateRequest.java new file mode 100644 index 0000000..1c610c9 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ApplicationUpdateRequest.java @@ -0,0 +1,42 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.RequestMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class ApplicationUpdateRequest extends RequestMessage { + private static final Logger logger = LoggerFactory.getLogger(ApplicationUpdateRequest.class); + + private List commandClasses; + + public ApplicationUpdateRequest() { + super(ControllerMessageType.ApplicationUpdate, CommandClass.NO_OPERATION, (byte) 0); + } + + @Override + public void fromPayload(byte[] _payload) { + nodeId = _payload[5]; + if (_payload[4] == (byte) 0x84) + logger.debug("Received node information for node: {}", nodeId); + int length = _payload[6]; + commandClasses = new ArrayList<>(); + for (int i = 7; i < length + 7; i++) { + if (_payload[i] != (byte) 0xef) { + CommandClass commandClass = CommandClass.fromByte(_payload[i]); + if (commandClass != CommandClass.NO_OPERATION) { + logger.debug("Received command class: {} for node: {}", commandClass.name(), nodeId); + commandClasses.add(commandClass); + } + } + } + } + + public List getCommandClasses() { + return commandClasses; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/BinarySwitchSetRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/BinarySwitchSetRequest.java new file mode 100644 index 0000000..a00af7f --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/BinarySwitchSetRequest.java @@ -0,0 +1,39 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.SendDataRequestMessage; + +public class BinarySwitchSetRequest extends SendDataRequestMessage { + private boolean on; + + public BinarySwitchSetRequest() { + this(true); + } + + public BinarySwitchSetRequest(boolean _on) { + this((byte)0, _on); + } + + public BinarySwitchSetRequest(byte _nodeId, boolean _on) { + super(_nodeId, CommandClass.SWITCH_BINARY, (byte)0x01); + on = _on; + } + + public boolean isOn() { + return on; + } + + public void setOn(boolean _on) { + on = _on; + } + + @Override + public byte[] getPayload() { + return asByteArray(on?0xFF:0); + } + + @Override + public String describe() { + return name() + " node: " + nodeId + " on: " + on; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ByteMessage.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ByteMessage.java new file mode 100644 index 0000000..cb98eaf --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ByteMessage.java @@ -0,0 +1,30 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.NoCommandRequestMessage; + +public class ByteMessage extends NoCommandRequestMessage { + private byte b; + + public ByteMessage() { + super(ControllerMessageType.None); + } + + public ByteMessage(byte _b) { + super(ControllerMessageType.None); + b = _b; + } + + public byte getByte() { + return b; + } + + public void setByte(byte _b) { + b = _b; + } + + @Override + public byte[] toByteArray(byte _transmitOptions, byte _callbackId) { + return asByteArray(b); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerCapabilitiesRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerCapabilitiesRequest.java new file mode 100644 index 0000000..1812a2f --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerCapabilitiesRequest.java @@ -0,0 +1,10 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.NoCommandRequestMessage; + +public class ControllerCapabilitiesRequest extends NoCommandRequestMessage { + public ControllerCapabilitiesRequest() { + super(ControllerMessageType.GetCapabilities); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerCapabilitiesResponse.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerCapabilitiesResponse.java new file mode 100644 index 0000000..32325ef --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerCapabilitiesResponse.java @@ -0,0 +1,75 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.NoCommandResponseMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Set; + +public class ControllerCapabilitiesResponse extends NoCommandResponseMessage { + private static final Logger logger = LoggerFactory.getLogger(ControllerCapabilitiesResponse.class); + + private String serialAPIVersion; + private int manufacturerId; + private int deviceType; + private int deviceId; + private Set supportedCommandClasses; + + public ControllerCapabilitiesResponse() { + super(ControllerMessageType.GetCapabilities); + } + + @Override + public void fromPayload(byte[] _payload) { + serialAPIVersion = String.format("%d.%d", _payload[4], _payload[5]); + manufacturerId = getShort(_payload, 6); + deviceType = getShort(_payload, 8); + deviceId = getShort(_payload, 10); + supportedCommandClasses = new HashSet<>(); + for (int by = 12; by < _payload.length-1; by++) { + for (int bi = 0; bi < 8; bi++) { + if ((_payload[by] & (0x01 << bi)) != 0) { + byte commandClassByte = (byte) (((by - 12) << 3) + bi + 1); + CommandClass commandClass = CommandClass.fromByte(commandClassByte); + if (commandClass != CommandClass.NO_OPERATION) { + logger.debug("Supports command class: {}", commandClass.label); + supportedCommandClasses.add(commandClass); + } else { + logger.debug("Supports unknown command class: {}", commandClassByte); + } + } + } + } + } + + private int getShort(byte[] _data, int _offset) { + return (toShort(_data[_offset]) << 8) | toShort(_data[_offset + 1]); + } + + private int toShort(byte _bt) { + return _bt & 0xFF; + } + + public String getSerialAPIVersion() { + return serialAPIVersion; + } + + public int getManufacturerId() { + return manufacturerId; + } + + public int getDeviceType() { + return deviceType; + } + + public int getDeviceId() { + return deviceId; + } + + public Set getSupportedCommandClasses() { + return supportedCommandClasses; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerInitialDataRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerInitialDataRequest.java new file mode 100644 index 0000000..bba4350 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerInitialDataRequest.java @@ -0,0 +1,10 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.NoCommandRequestMessage; + +public class ControllerInitialDataRequest extends NoCommandRequestMessage { + public ControllerInitialDataRequest() { + super(ControllerMessageType.SerialApiGetInitData); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerInitialDataResponse.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerInitialDataResponse.java new file mode 100644 index 0000000..ccbef8e --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ControllerInitialDataResponse.java @@ -0,0 +1,50 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.NoCommandResponseMessage; + +import java.util.ArrayList; +import java.util.List; + +public class ControllerInitialDataResponse extends NoCommandResponseMessage { + private boolean master; + private boolean primary; + private List nodeIds; + + public ControllerInitialDataResponse() { + super(ControllerMessageType.SerialApiGetInitData); + } + + @Override + public void fromPayload(byte[] _payload) { + int length = _payload[6]; + if (length == 29) { + byte nodeId = 1; + nodeIds = new ArrayList<>(); + for (int i = 7; i < 7 + length; i++) { + byte curByte = _payload[i]; + for (int j = 0; j < 8; j++) { + int bit = 1 << j; + if ((curByte & bit) == bit) { + nodeIds.add(nodeId); + } + nodeId++; + } + } + master = (_payload[5] & 0x1) == 0; + primary = (_payload[5] & 0x4) == 0; + } + } + + public boolean isMaster() { + return master; + } + + public boolean isPrimary() { + return primary; + } + + public List getNodeIds() { + return nodeIds; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/DeviceManufacturerActionRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/DeviceManufacturerActionRequest.java new file mode 100644 index 0000000..fa990c5 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/DeviceManufacturerActionRequest.java @@ -0,0 +1,15 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.RequestMessage; + +public class DeviceManufacturerActionRequest extends RequestMessage { + public DeviceManufacturerActionRequest() { + this((byte) 0); + } + + public DeviceManufacturerActionRequest(byte _nodeId) { + super(_nodeId, ControllerMessageType.SendData, CommandClass.MANUFACTURER_SPECIFIC, (byte) 0x04); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/GetControllerIdRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/GetControllerIdRequest.java new file mode 100644 index 0000000..260de2b --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/GetControllerIdRequest.java @@ -0,0 +1,10 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.NoCommandRequestMessage; + +public class GetControllerIdRequest extends NoCommandRequestMessage { + public GetControllerIdRequest() { + super(ControllerMessageType.MemoryGetId); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/GetControllerIdResponse.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/GetControllerIdResponse.java new file mode 100644 index 0000000..c9698cc --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/GetControllerIdResponse.java @@ -0,0 +1,39 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.NoCommandResponseMessage; + +public class GetControllerIdResponse extends NoCommandResponseMessage { + private long homeId; + private int controllerId; + + public GetControllerIdResponse() { + super(ControllerMessageType.MemoryGetId); + } + + @Override + public void fromPayload(byte[] _payload) { + homeId = (getByte(_payload, 4) << 24) | (getByte(_payload, 5) << 16) | (getByte(_payload, 6) << 8) | getByte(_payload, 7); + controllerId = _payload[8]; + } + + public long getHomeId() { + return homeId; + } + + public void setHomeId(int _homeId) { + homeId = _homeId; + } + + public int getControllerId() { + return controllerId; + } + + public void setControllerId(int _controllerId) { + controllerId = _controllerId; + } + + private long getByte(byte[] _data, int _offset) { + return _data[_offset] & 0xFF; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSensorGetRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSensorGetRequest.java new file mode 100644 index 0000000..f661a3e --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSensorGetRequest.java @@ -0,0 +1,20 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.RequestMessage; + +public class MultilevelSensorGetRequest extends RequestMessage { + public MultilevelSensorGetRequest() { + this((byte)0); + } + + public MultilevelSensorGetRequest(byte _nodeId) { + super(_nodeId, ControllerMessageType.SendData, CommandClass.SENSOR_MULTILEVEL, (byte)0x04); + } + + @Override + public String describe() { + return name() + " node: " + nodeId; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSensorReportRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSensorReportRequest.java new file mode 100644 index 0000000..e8ae404 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSensorReportRequest.java @@ -0,0 +1,34 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.MessageUtil; +import com.lanternsoftware.zwave.message.RequestMessage; + +public class MultilevelSensorReportRequest extends RequestMessage { + private double temperature; + + public MultilevelSensorReportRequest() { + this((byte) 0); + } + + public MultilevelSensorReportRequest(byte _nodeId) { + super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.SENSOR_MULTILEVEL, (byte) 0x05); + } + + @Override + public void fromPayload(byte[] _payload) { + nodeId = _payload[5]; + if (_payload[9] == (byte) 1) + temperature = MessageUtil.getTemperatureCelsius(_payload, 10); + } + + public double getTemperatureCelsius() { + return temperature; + } + + @Override + public String describe() { + return name() + " node: " + nodeId + " level: " + temperature; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSwitchReportRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSwitchReportRequest.java new file mode 100644 index 0000000..afa8935 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSwitchReportRequest.java @@ -0,0 +1,28 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.RequestMessage; + +public class MultilevelSwitchReportRequest extends RequestMessage { + private int level; + + public MultilevelSwitchReportRequest() { + super(ControllerMessageType.ApplicationCommandHandler, CommandClass.SWITCH_MULTILEVEL, (byte) 0x03); + } + + @Override + public void fromPayload(byte[] _payload) { + nodeId = _payload[5]; + level = _payload[9]; + } + + public int getLevel() { + return level; + } + + @Override + public String describe() { + return name() + " node: " + nodeId + " level: " + level; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSwitchSetRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSwitchSetRequest.java new file mode 100644 index 0000000..e3490b1 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/MultilevelSwitchSetRequest.java @@ -0,0 +1,31 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.SendDataRequestMessage; + +public class MultilevelSwitchSetRequest extends SendDataRequestMessage { + private int level; + + public MultilevelSwitchSetRequest() { + this(99); + } + + public MultilevelSwitchSetRequest(int _level) { + this((byte)0, _level); + } + + public MultilevelSwitchSetRequest(byte _nodeId, int _level) { + super(_nodeId, CommandClass.SWITCH_MULTILEVEL, (byte) 0x01); + level = _level; + } + + @Override + public byte[] getPayload() { + return asByteArray(level); + } + + @Override + public String describe() { + return name() + " node: " + nodeId + " level: " + level; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/NodeInfoRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/NodeInfoRequest.java new file mode 100644 index 0000000..c8716c0 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/NodeInfoRequest.java @@ -0,0 +1,14 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.NoCommandRequestMessage; + +public class NodeInfoRequest extends NoCommandRequestMessage { + public NodeInfoRequest() { + super(ControllerMessageType.RequestNodeInfo); + } + + public NodeInfoRequest(byte _nodeId) { + super(_nodeId, ControllerMessageType.RequestNodeInfo); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/NodeInfoResponse.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/NodeInfoResponse.java new file mode 100644 index 0000000..b03b669 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/NodeInfoResponse.java @@ -0,0 +1,10 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.NoCommandResponseMessage; + +public class NodeInfoResponse extends NoCommandResponseMessage { + public NodeInfoResponse() { + super(ControllerMessageType.RequestNodeInfo); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/SendDataRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/SendDataRequest.java new file mode 100644 index 0000000..7e7a458 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/SendDataRequest.java @@ -0,0 +1,60 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.RequestMessage; + +public class SendDataRequest extends RequestMessage { + public enum TransmissionState { + COMPLETE_OK((byte) 0x00, "Transmission complete and ACK received"), + COMPLETE_NO_ACK((byte) 0x01, "Transmission complete, no ACK received"), + COMPLETE_FAIL((byte) 0x02, "Transmission failed"), + COMPLETE_NOT_IDLE((byte) 0x03, "Transmission failed, network busy"), + COMPLETE_NOROUTE((byte) 0x04, "Tranmission complete, no return route"); + + public byte key; + public String label; + + TransmissionState(byte _key, String _label) { + key = _key; + label = _label; + } + + public static TransmissionState fromKey(byte _key) { + for (TransmissionState state : values()) { + if (state.key == _key) + return state; + } + return COMPLETE_FAIL; + } + } + + private TransmissionState state; + private byte callbackId; + + public SendDataRequest() { + super(ControllerMessageType.SendData, CommandClass.NO_OPERATION, (byte) 0); + } + + @Override + public void fromPayload(byte[] _payload) { + if (CollectionUtils.length(_payload) > 5) { + callbackId = _payload[4]; + state = TransmissionState.fromKey(_payload[5]); + } + } + + public TransmissionState getState() { + return state; + } + + public byte getCallbackId() { + return callbackId; + } + + @Override + public String describe() { + return name() + " callbackId: " + callbackId + " state: " + state.name(); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/SendDataResponse.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/SendDataResponse.java new file mode 100644 index 0000000..4a3a96f --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/SendDataResponse.java @@ -0,0 +1,33 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.ResponseMessage; + +public class SendDataResponse extends ResponseMessage { + private byte response; + + public SendDataResponse() { + this((byte) 0); + } + + public SendDataResponse(byte _response) { + super(ControllerMessageType.SendData, CommandClass.NO_OPERATION, (byte) 0); + response = _response; + } + + @Override + public void fromPayload(byte[] _payload) { + if (CollectionUtils.length(_payload) > 0) + response = _payload[0]; + } + + public boolean isSuccess() { + return response != 0; + } + + public String describe() { + return name() + ": " + (isSuccess() ? "SUCCESS" : "FAILURE"); + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatModeGetRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatModeGetRequest.java new file mode 100644 index 0000000..c82f9fc --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatModeGetRequest.java @@ -0,0 +1,19 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.SendDataRequestMessage; + +public class ThermostatModeGetRequest extends SendDataRequestMessage { + public ThermostatModeGetRequest() { + this((byte) 0); + } + + public ThermostatModeGetRequest(byte _nodeId) { + super(_nodeId, CommandClass.THERMOSTAT_MODE, (byte) 0x02); + } + + @Override + public String describe() { + return name() + " node: " + nodeId; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatModeSetRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatModeSetRequest.java new file mode 100644 index 0000000..f4a28dd --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatModeSetRequest.java @@ -0,0 +1,28 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.SendDataRequestMessage; +import com.lanternsoftware.zwave.message.thermostat.ThermostatMode; + +public class ThermostatModeSetRequest extends SendDataRequestMessage { + private ThermostatMode mode; + + public ThermostatModeSetRequest() { + this((byte) 0, ThermostatMode.OFF); + } + + public ThermostatModeSetRequest(byte _nodeId, ThermostatMode _mode) { + super(_nodeId, CommandClass.THERMOSTAT_MODE, (byte) 0x01); + mode = _mode; + } + + @Override + public byte[] getPayload() { + return asByteArray(mode.data); + } + + @Override + public String describe() { + return name() + " node: " + nodeId; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointGetRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointGetRequest.java new file mode 100644 index 0000000..737e14a --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointGetRequest.java @@ -0,0 +1,28 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.SendDataRequestMessage; +import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex; + +public class ThermostatSetPointGetRequest extends SendDataRequestMessage { + private ThermostatSetPointIndex index; + + public ThermostatSetPointGetRequest() { + this((byte) 0, ThermostatSetPointIndex.HEATING); + } + + public ThermostatSetPointGetRequest(byte _nodeId, ThermostatSetPointIndex _index) { + super(_nodeId, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x02); + index = _index; + } + + @Override + public byte[] getPayload() { + return asByteArray(index.index); + } + + @Override + public String describe() { + return name() + " node: " + nodeId; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointReportRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointReportRequest.java new file mode 100644 index 0000000..7a889e6 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointReportRequest.java @@ -0,0 +1,41 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.MessageUtil; +import com.lanternsoftware.zwave.message.RequestMessage; +import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex; + +public class ThermostatSetPointReportRequest extends RequestMessage { + private ThermostatSetPointIndex index; + private double temperature; + + public ThermostatSetPointReportRequest() { + this((byte) 0); + } + + public ThermostatSetPointReportRequest(byte _nodeId) { + super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x03); + } + + @Override + public void fromPayload(byte[] _payload) { + nodeId = _payload[5]; + index = ThermostatSetPointIndex.fromIndex(_payload[9]); + if (index != null) + temperature = MessageUtil.getTemperatureCelsius(_payload, 10); + } + + public ThermostatSetPointIndex getIndex() { + return index; + } + + public double getTemperatureCelsius() { + return temperature; + } + + @Override + public String describe() { + return name() + " node: " + nodeId + " level: " + temperature; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSetRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSetRequest.java new file mode 100644 index 0000000..eab9743 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSetRequest.java @@ -0,0 +1,34 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.SendDataRequestMessage; +import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex; + +public class ThermostatSetPointSetRequest extends SendDataRequestMessage { + private ThermostatSetPointIndex index; + private int level; + + public ThermostatSetPointSetRequest() { + this(ThermostatSetPointIndex.HEATING, 72); + } + + public ThermostatSetPointSetRequest(ThermostatSetPointIndex _index, int _level) { + this((byte)0, _index, _level); + } + + public ThermostatSetPointSetRequest(byte _nodeId, ThermostatSetPointIndex _index, int _level) { + super(_nodeId, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x01); + index = _index; + level = _level; + } + + @Override + public byte[] getPayload() { + return asByteArray(index.index, (byte)9, (byte)level); + } + + @Override + public String describe() { + return name() + " node: " + nodeId + " level: " + level; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSupportedGetRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSupportedGetRequest.java new file mode 100644 index 0000000..64c6ac6 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSupportedGetRequest.java @@ -0,0 +1,19 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.SendDataRequestMessage; + +public class ThermostatSetPointSupportedGetRequest extends SendDataRequestMessage { + public ThermostatSetPointSupportedGetRequest() { + this((byte) 0); + } + + public ThermostatSetPointSupportedGetRequest(byte _nodeId) { + super(_nodeId, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x04); + } + + @Override + public String describe() { + return name() + " node: " + nodeId; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSupportedReportRequest.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSupportedReportRequest.java new file mode 100644 index 0000000..9c912b5 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/impl/ThermostatSetPointSupportedReportRequest.java @@ -0,0 +1,46 @@ +package com.lanternsoftware.zwave.message.impl; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.zwave.message.CommandClass; +import com.lanternsoftware.zwave.message.ControllerMessageType; +import com.lanternsoftware.zwave.message.RequestMessage; + +import java.util.Set; +import java.util.TreeSet; + +public class ThermostatSetPointSupportedReportRequest extends RequestMessage { + private Set supportedSetPointIndices; + + public ThermostatSetPointSupportedReportRequest() { + this((byte) 0); + } + + public ThermostatSetPointSupportedReportRequest(byte _nodeId) { + super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x05); + } + + @Override + public void fromPayload(byte[] _payload) { + supportedSetPointIndices = new TreeSet<>(); + for (int i = 9; i < _payload.length - 1; ++i) { + for (int bit = 0; bit < 8; ++bit) { + if ((_payload[i] & (1 << bit)) != 0) { + supportedSetPointIndices.add((byte)(((i - 9) << 3) + bit)); + } + } + } + } + + public Set getSupportedSetPointIndices() { + return supportedSetPointIndices; + } + + public boolean isSupportedIndex(byte _index) { + return CollectionUtils.contains(supportedSetPointIndices, _index); + } + + @Override + public String describe() { + return name() + " node: " + nodeId; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/thermostat/ThermostatMode.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/thermostat/ThermostatMode.java new file mode 100644 index 0000000..b0d1b90 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/thermostat/ThermostatMode.java @@ -0,0 +1,23 @@ +package com.lanternsoftware.zwave.message.thermostat; + +public enum ThermostatMode { + OFF((byte)0), + HEAT((byte)1), + COOL((byte)2), + AUTO((byte)3), + AUXILIARY((byte)4); + + public final byte data; + + ThermostatMode(byte _data) { + data = _data; + } + + public static ThermostatMode fromByte(byte _bt) { + for (ThermostatMode mode : values()) { + if (mode.data == _bt) + return mode; + } + return null; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/thermostat/ThermostatSetPointIndex.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/thermostat/ThermostatSetPointIndex.java new file mode 100644 index 0000000..7ffcf3c --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/message/thermostat/ThermostatSetPointIndex.java @@ -0,0 +1,48 @@ +package com.lanternsoftware.zwave.message.thermostat; + +public enum ThermostatSetPointIndex { + HEATING((byte)1), + COOLING((byte)2), + FURNACE((byte)7), + DRY_AIR((byte)8), + MOIST_AIR((byte)9), + AUTO_CHANGEOVER((byte)10), + HEATING_ECON((byte)11), + COOLING_ECON((byte)12), + AWAY_HEATING((byte)13), + COOLING_HEATING((byte)14), + HEATING_MINIMUM((byte)101), + COOLING_MINIMUM((byte)102), + FURNACE_MINIMUM((byte)107), + DRY_AIR_MINIMUM((byte)108), + MOIST_AIR_MINIMUM((byte)109), + AUTO_CHANGEOVER_MINIMUM((byte)110), + HEATING_ECON_MINIMUM((byte)111), + COOLING_ECON_MINIMUM((byte)112), + AWAY_HEATING_MINIMUM((byte)113), + COOLING_HEATING_MINIMUM((byte)114), + HEATING_MAXIMUM((byte)201), + COOLING_MAXIMUM((byte)202), + FURNACE_MAXIMUM((byte)207), + DRY_AIR_MAXIMUM((byte)208), + MOIST_AIR_MAXIMUM((byte)209), + AUTO_CHANGEOVER_MAXIMUM((byte)210), + HEATING_ECON_MAXIMUM((byte)211), + COOLING_ECON_MAXIMUM((byte)212), + AWAY_HEATING_MAXIMUM((byte)213), + COOLING_HEATING_MAXIMUM((byte)214); + + public final byte index; + + ThermostatSetPointIndex(byte _index) { + index = _index; + } + + public static ThermostatSetPointIndex fromIndex(byte _index) { + for (ThermostatSetPointIndex idx : values()) { + if (idx.index == _index) + return idx; + } + return null; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/node/Node.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/node/Node.java new file mode 100644 index 0000000..870c668 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/node/Node.java @@ -0,0 +1,34 @@ +package com.lanternsoftware.zwave.node; + +import com.lanternsoftware.zwave.message.CommandClass; + +import java.util.Set; + +public class Node { + private byte id; + private Set commandClasses; + + public Node() { + } + + public Node(byte _id, Set _commandClasses) { + id = _id; + commandClasses = _commandClasses; + } + + public byte getId() { + return id; + } + + public void setId(byte _id) { + id = _id; + } + + public Set getCommandClasses() { + return commandClasses; + } + + public void setCommandClasses(Set _commandClasses) { + commandClasses = _commandClasses; + } +} diff --git a/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/node/NodeManager.java b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/node/NodeManager.java new file mode 100644 index 0000000..acd5101 --- /dev/null +++ b/zwave/lantern-zwave/src/main/java/com/lanternsoftware/zwave/node/NodeManager.java @@ -0,0 +1,108 @@ +package com.lanternsoftware.zwave.node; + +import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.zwave.controller.Controller; +import com.lanternsoftware.zwave.message.IMessageSubscriber; +import com.lanternsoftware.zwave.message.MessageEngine; +import com.lanternsoftware.zwave.message.impl.ApplicationUpdateRequest; +import com.lanternsoftware.zwave.message.impl.ControllerInitialDataResponse; +import com.lanternsoftware.zwave.message.impl.GetControllerIdResponse; +import com.lanternsoftware.zwave.message.impl.NodeInfoRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class NodeManager { + private static final Logger logger = LoggerFactory.getLogger(NodeManager.class); + + private Controller controller; + private ControllerInitialDataResponse initialDataResponse; + private GetControllerIdResponse controllerIdResponse; + private final Set missingNodes = new HashSet<>(); + private Map nodes = new HashMap<>(); + + public NodeManager(final Controller _controller) { + controller = _controller; + MessageEngine.subscribe(new IMessageSubscriber() { + @Override + public Class getHandledMessageClass() { + return ControllerInitialDataResponse.class; + } + + @Override + public void onMessage(ControllerInitialDataResponse _response) { + synchronized (NodeManager.this) { + initialDataResponse = _response; + init(); + } + } + }); + MessageEngine.subscribe(new IMessageSubscriber() { + @Override + public Class getHandledMessageClass() { + return GetControllerIdResponse.class; + } + + @Override + public void onMessage(GetControllerIdResponse _response) { + synchronized (NodeManager.this) { + controllerIdResponse = _response; + init(); + } + } + }); + MessageEngine.subscribe(new IMessageSubscriber() { + @Override + public Class getHandledMessageClass() { + return ApplicationUpdateRequest.class; + } + + @Override + public void onMessage(ApplicationUpdateRequest _request) { + synchronized (NodeManager.this) { + if (missingNodes.remove(_request.getNodeId())) + nodes.put(_request.getNodeId(), new Node(_request.getNodeId(), CollectionUtils.asHashSet(_request.getCommandClasses()))); + + logger.debug("Received command classes for node: {}", _request.getNodeId()); + requestNodeInfo(); + } + } + }); + } + + private void init() { + if (!isStarted()) + return; + missingNodes.clear(); + logger.info("Node Ids:{}", CollectionUtils.transformToCommaSeparated(initialDataResponse.getNodeIds(), String::valueOf)); +// missingNodes.addAll(CollectionUtils.filter(initialDataResponse.getNodeIds(), _b->_b != controllerIdResponse.getControllerId())); + requestNodeInfo(); + } + + private void requestNodeInfo() { + if (!missingNodes.isEmpty()) + controller.send(new NodeInfoRequest(CollectionUtils.getFirst(missingNodes))); + else + notify(); + } + + private boolean isStarted() { + return ((initialDataResponse != null) && (controllerIdResponse != null)); + } + + public void waitForStartup() { + synchronized (this) { + if (!isStarted()) { + try { + wait(); + } catch (InterruptedException _e) { + _e.printStackTrace(); + } + } + } + } +} diff --git a/zwave/lantern-zwave/src/main/resources/META-INF/services/com.lanternsoftware.zwave.message.Message b/zwave/lantern-zwave/src/main/resources/META-INF/services/com.lanternsoftware.zwave.message.Message new file mode 100644 index 0000000..4480d56 --- /dev/null +++ b/zwave/lantern-zwave/src/main/resources/META-INF/services/com.lanternsoftware.zwave.message.Message @@ -0,0 +1,24 @@ +com.lanternsoftware.zwave.message.impl.ApplicationUpdateRequest +com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest +com.lanternsoftware.zwave.message.impl.ByteMessage +com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesRequest +com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesResponse +com.lanternsoftware.zwave.message.impl.ControllerInitialDataRequest +com.lanternsoftware.zwave.message.impl.ControllerInitialDataResponse +com.lanternsoftware.zwave.message.impl.DeviceManufacturerActionRequest +com.lanternsoftware.zwave.message.impl.GetControllerIdRequest +com.lanternsoftware.zwave.message.impl.GetControllerIdResponse +com.lanternsoftware.zwave.message.impl.MultilevelSensorGetRequest +com.lanternsoftware.zwave.message.impl.MultilevelSensorReportRequest +com.lanternsoftware.zwave.message.impl.MultilevelSwitchReportRequest +com.lanternsoftware.zwave.message.impl.MultilevelSwitchSetRequest +com.lanternsoftware.zwave.message.impl.NodeInfoRequest +com.lanternsoftware.zwave.message.impl.NodeInfoResponse +com.lanternsoftware.zwave.message.impl.SendDataRequest +com.lanternsoftware.zwave.message.impl.SendDataResponse +com.lanternsoftware.zwave.message.impl.ThermostatModeGetRequest +com.lanternsoftware.zwave.message.impl.ThermostatSetPointGetRequest +com.lanternsoftware.zwave.message.impl.ThermostatSetPointReportRequest +com.lanternsoftware.zwave.message.impl.ThermostatSetPointSetRequest +com.lanternsoftware.zwave.message.impl.ThermostatSetPointSupportedGetRequest +com.lanternsoftware.zwave.message.impl.ThermostatSetPointSupportedReportRequest \ No newline at end of file diff --git a/zwave/lantern-zwave/src/test/java/com/lanternsoftware/zwave/TestBits.java b/zwave/lantern-zwave/src/test/java/com/lanternsoftware/zwave/TestBits.java new file mode 100644 index 0000000..e4792a4 --- /dev/null +++ b/zwave/lantern-zwave/src/test/java/com/lanternsoftware/zwave/TestBits.java @@ -0,0 +1,8 @@ +package com.lanternsoftware.zwave; + +public class TestBits { + public static void main(String[] args) { + System.out.println(Integer.toBinaryString(6)); + System.out.println(Integer.toBinaryString(0x18)); + } +} diff --git a/zwave/pom.xml b/zwave/pom.xml new file mode 100644 index 0000000..2e9738e --- /dev/null +++ b/zwave/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + pom + com.lanternsoftware.zwave + zwave-reactor + zwave-reactor + 1.0.0 + + + 1.8 + 1.8 + + + + lantern-datamodel-zwave + lantern-service-thermometer + lantern-service-zwave + lantern-uirt + lantern-zwave + + \ No newline at end of file