forked from community/product-iots
Merge branch 'master' of https://github.com/wso2/product-iots
commit
b777fa9764
@ -1,164 +1,234 @@
|
||||
<!--
|
||||
~ Copyright 2005-2010 WSO2, Inc. (http://wso2.com)
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="content-type" content=""/>
|
||||
<title>WSO2 Enterprise Mobility Manager ${product.version} Release Notes</title>
|
||||
<link href="./tools/cmd/css/gs-dist-doc.css" rel="stylesheet"
|
||||
type="text/css" media="all"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Enterprise Mobility Manager ${product.version} Release Notes</h1>
|
||||
|
||||
<p>
|
||||
WSO2 Enterprise Mobility Manager (EMM) is a unique solution designed to specifically address the mobile enterprise needs. EMM includes of two key aspects:
|
||||
Mobile Device Management (MDM) and Mobile Application Management (MAM). WSO2 EMM also supports single sign-on (SSO) and multi-tenancy.
|
||||
</p>
|
||||
<p>
|
||||
EMM enables organizations to secure, manage and monitor Android and iOS powered devices (i.e., smart phones, ipod touch devices and tablet PCs), irrespective of the mobile operator, service provider, or the organization. Users need to accept the Policy agreement, which states all the actions that can be carried out on the device when enrolling with EMM. EMM only controls the corporate data that is present on the devices, while the personal data is left untouched.
|
||||
</p>
|
||||
|
||||
<h3>
|
||||
What is new in this release
|
||||
</h3>
|
||||
<p>
|
||||
<b>First user experience</b>
|
||||
</p>
|
||||
<p>
|
||||
In the previous WSO2 EMM releases it was mandatory for the users to fully configure WSO2 EMM before being able to run the product. Now, WSO2 EMM 1.1.0
|
||||
binary pack comprises of a default Android Agent. Thereby, enabling users to seamlessly download and run EMM without any prior configurations. This will
|
||||
allow users to use Android devices to get a better understanding of WSO2 EMM.
|
||||
</p>
|
||||
<p>
|
||||
<b>Local notification method for Android devices</b>
|
||||
</p>
|
||||
<p>
|
||||
The GCM server has a limitation on the number of messages that can be sent to the GCM server. However, this new Local notification method for Android
|
||||
devices does not make use of the GCM server. Thereby, the number of messages sent via the GCM will be minimized. Furthermore, the Local notification method
|
||||
does not send the messages via the payload, as it did in previous EMM versions. Thereby, this will minimize the MDM command loss.
|
||||
</p>
|
||||
<p>
|
||||
<b>Tenant based configuration</b>
|
||||
</p>
|
||||
<p>
|
||||
The tenant admin will be able to use the Configuration Manager wizard to setup the tenant's general, Android and iOS configuration details. Thereby, this
|
||||
allows each tenant to customize their settings based on their requirements. For more information, see Tenant Based Settings.
|
||||
</p>
|
||||
<p>
|
||||
<b>Ability to change password</b>
|
||||
</p>
|
||||
<p>
|
||||
Previously, users were not allowed to change the default password that was assigned by WSO2 EMM. However, now users are allowed to change their password.
|
||||
Thereby, users will be able to change their default password to a password of their choice.
|
||||
</p>
|
||||
<p>
|
||||
<b>Removal of devices from WSO2 EMM</b>
|
||||
</p>
|
||||
<p>
|
||||
Previously, administrators were not allowed to remove devices from WSO2 EMM after they were enrolled. However, now the administrators can use the
|
||||
Enterprise Wipe feature to remove enrolled devices from WSO2 EMM. Thereby, this will enable the administrators to maintain WSO2 EMM efficiently.
|
||||
</p>
|
||||
<p>
|
||||
<b>Improved API security</b>
|
||||
</p>
|
||||
<p>
|
||||
The APIs that are exposed to the devices have been secured in WSO2 EMM using OAuth.
|
||||
</p>
|
||||
<p>
|
||||
<b>Improved compliance monitoring</b>
|
||||
</p>
|
||||
<p>
|
||||
When the system is clustered, then using the nTask Component, only one node will be allowed to perform the compliance monitoring that will dispatch
|
||||
messages to the GCM or APNS.
|
||||
</p>
|
||||
|
||||
<h2>Features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Self-service device enrollment and management with end-user MDM console</li>
|
||||
<li>Policy-driven device management for security, data, and device features (Camera, Password Policy)</li>
|
||||
<li>Deploy policies over-the-air</li>
|
||||
<li>Compliance monitoring for reporting, alerting, and device deprovisioning</li>
|
||||
<li>Role based permissions for device management</li>
|
||||
<li>Provisioning and deprovisioning applications to enrolled devices</li>
|
||||
<li>Blacklisting of applications for Android</li>
|
||||
<li>Supports App management</li>
|
||||
<li>App approval process through a lifecycle</li>
|
||||
<li>Discover mobile apps through an Enterprise App Store</li>
|
||||
<li>Self-provisioning of mobile apps to devices</li>
|
||||
</ul>
|
||||
<p>
|
||||
<b>Bug Fixes / Improvements</b>
|
||||
</p>
|
||||
<p>
|
||||
For the list of fixed issues, go to <a href="https://wso2.org/jira/issues/?filter=11896">WSO2 EMM 1.1.0 - Fixed Issues</a>.
|
||||
</p>
|
||||
<p>
|
||||
<b>Known Issues</b>
|
||||
</p>
|
||||
<p>
|
||||
For the list of known issues, go to <a href="https://wso2.org/jira/issues/?filter=11894">WSO2 EMM 1.1.0 - Known Issues</a>.
|
||||
</p>
|
||||
<p>
|
||||
<b>Reporting Problems</b>
|
||||
</p>
|
||||
<p>
|
||||
Issues can be reported using the <a href="https://wso2.org/jira/browse/EMM"> public JIRA</a>.
|
||||
</p>
|
||||
<p>
|
||||
<b>Engaging with Community</b>
|
||||
</p>
|
||||
<p>
|
||||
<b>Mailing Lists</b>
|
||||
</p>
|
||||
<p>
|
||||
Join our mailing list and correspond with the developers directly.
|
||||
</p>
|
||||
<p>
|
||||
Developer List : dev@wso2.org | Subscribe |<a href="http://wso2.org/mailarchive/dev/"> Mail Archive</a>
|
||||
</p>
|
||||
<p>
|
||||
<b>Reporting Issues</b>
|
||||
</p>
|
||||
<p>
|
||||
WSO2 encourages you to report issues, enhancements and feature requests for WSO2 EMM. Use the <a href="https://wso2.org/jira/browse/EMM">issue tracker</a>
|
||||
for reporting issues.
|
||||
</p>
|
||||
<p>
|
||||
<b>Discussion Forums</b>
|
||||
</p>
|
||||
<p>
|
||||
We encourage to use <a href="http://stackoverflow.com/tags/wso2/">stackoverflow</a> to engage with developers as well as other users.
|
||||
</p>
|
||||
<p>
|
||||
<b>Support</b>
|
||||
</p>
|
||||
<p>
|
||||
We are committed to ensuring that your enterprise middleware deployment is completely supported from evaluation to production. Our unique approach ensures
|
||||
that all support leverages our open development methodology and is provided by the very same engineers who build the technology.
|
||||
</p>
|
||||
<p>
|
||||
For additional support information please refer to <a href="http://wso2.com/support/">http://wso2.com/support/</a>
|
||||
</p>
|
||||
<p>
|
||||
We welcome your feedback and would love to hear your thoughts on this release of WSO2 EMM.
|
||||
</p>
|
||||
<p>
|
||||
--WSO2 EMM Development Team--
|
||||
</p>
|
||||
<body lang=EN-US link=blue vlink=purple style='tab-interval:36.0pt'>
|
||||
|
||||
<div class=WordSection1>
|
||||
|
||||
<p class=MsoNormal style='text-align:justify;text-justify:inter-ideograph'><b><u><span
|
||||
style='font-family:Arial;mso-fareast-font-family:"Times New Roman";mso-bidi-font-family:
|
||||
"Times New Roman";color:#333333'>WSO2 <span class=SpellE>IoT</span> Server
|
||||
1.0.0 Alpha Released</span></u></b><span style='font-family:Arial;
|
||||
mso-fareast-font-family:"Times New Roman";mso-bidi-font-family:"Times New Roman";
|
||||
color:#222222'><o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='text-align:justify;text-justify:inter-ideograph'><span
|
||||
style='font-family:Arial;mso-fareast-font-family:"Times New Roman";mso-bidi-font-family:
|
||||
"Times New Roman";color:#222222'>We are pleased to announce WSO2 <span
|
||||
class=SpellE>IoT</span> Server 1.0.0 alpha release. It can be
|
||||
downloaded from<a
|
||||
href="https://github.com/wso2-incubator/product-iot-server/releases/tag/IoTS-1.0.0-M3"><span
|
||||
style='color:#1155CC'>https://github.com/wso2-incubator/product-iot-server/releases/tag/IoTS-1.0.0-</span></a>alpha<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='text-align:justify;text-justify:inter-ideograph'><span
|
||||
style='font-family:Arial;mso-fareast-font-family:"Times New Roman";mso-bidi-font-family:
|
||||
"Times New Roman";color:#222222'><o:p> </o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='text-align:justify;text-justify:inter-ideograph'><span
|
||||
style='font-family:Arial;mso-bidi-font-family:"Times New Roman";color:black'>WSO2
|
||||
<span class=SpellE>IoT</span> Server is an extensible, open-source, multi
|
||||
tenant, Internet of Things Platform for implementing server-side of <span
|
||||
class=SpellE>IoT</span> Reference Architecture. It comes with a few reference
|
||||
implementations for the device layer.</span><span style='font-family:Arial;
|
||||
mso-bidi-font-family:"Times New Roman";color:#222222'><o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='text-align:justify;text-justify:inter-ideograph'><span
|
||||
style='font-family:Arial;mso-bidi-font-family:"Times New Roman";color:black'><br
|
||||
style='mso-special-character:line-break'>
|
||||
</p>
|
||||
|
||||
<p class=MsoNormal style='text-align:justify;text-justify:inter-ideograph'><span
|
||||
style='font-family:Arial;mso-bidi-font-family:"Times New Roman";color:black'>WSO2
|
||||
<span class=SpellE>IoT</span> Server supports:</span><span style='font-family:
|
||||
Arial;mso-bidi-font-family:"Times New Roman";color:#222222'><o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='text-align:justify;text-justify:inter-ideograph'><span
|
||||
style='font-family:Arial;mso-bidi-font-family:"Times New Roman";color:#222222'><o:p> </o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:18.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l1 level1 lfo1;tab-stops:list 6.75pt;
|
||||
vertical-align:baseline'><b><span
|
||||
style='font-family:Arial;mso-bidi-font-family:"Times New Roman";color:black'>Device
|
||||
Management<o:p></o:p></span></b></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l1 level2 lfo1;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Extensions for registering device types<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l1 level2 lfo1;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Self-service enrolment and management of
|
||||
connected devices<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l1 level2 lfo1;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Group, manage and monitor connected devices<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l1 level2 lfo1;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Share device operations / data with other users<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l1 level2 lfo1;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Distribution and management of applications and
|
||||
firmware to devices<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l1 level2 lfo1;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Edge computing powered by WSO2 CEP streaming
|
||||
engine (Siddhi - <a href="https://github.com/wso2/siddhi"><span
|
||||
style='color:#1155CC'>https://github.com/wso2/siddhi</span></a>)<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l1 level2 lfo1;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>OOTB support for some known device types such as
|
||||
Raspberry Pi, <span class=SpellE>Arduino</span> Uno, Android<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:18.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level1 lfo2;tab-stops:list 36.0pt;
|
||||
vertical-align:baseline'><span class=SpellE><b><span style='font-family:
|
||||
Arial;mso-bidi-font-family:"Times New Roman";color:black'>IoT</span></b></span><b><span
|
||||
style='font-family:Arial;mso-bidi-font-family:"Times New Roman";color:black'>
|
||||
Protocol Support<o:p></o:p></span></b></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Leverage MQTT, HTTP, <span class=SpellE>Websockets</span>
|
||||
and XMPP protocols for device communications with <span class=SpellE>IoT</span>
|
||||
Server<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Framework extension for adding more protocols
|
||||
and data formats<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:18.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level1 lfo2;tab-stops:list 36.0pt;
|
||||
vertical-align:baseline'><b><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Analytics<o:p></o:p></span></b></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Support for batch, interactive, real-time and
|
||||
predictive analytics through WSO2 DAS<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:18.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level1 lfo2;tab-stops:list 36.0pt;
|
||||
vertical-align:baseline'><b><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Pre-built <span class=SpellE>visualisation</span>
|
||||
support for sensor readings<o:p></o:p></span></b></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>View instant, <span class=SpellE>visualised</span>
|
||||
statistics of individual or multiple devices<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Traverse through, <span class=SpellE>analyse</span>
|
||||
and zoom in/out of filtered data<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Stats-API to write your own <span class=SpellE>visualisation</span><o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Pre-built graphs for common sensor reading types
|
||||
like temperature, velocity<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:18.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level1 lfo2;tab-stops:list 36.0pt;
|
||||
vertical-align:baseline'><b><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>API Management for App Development<o:p></o:p></span></b></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Devices are represented as REST APIs<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Develop applications using Device APIs<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:18.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level1 lfo2;tab-stops:list 36.0pt;
|
||||
vertical-align:baseline'><b><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Identity and Access Management<o:p></o:p></span></b></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Identity Management for devices<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='margin-left:54.0pt;text-align:justify;text-justify:
|
||||
inter-ideograph;text-indent:-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;
|
||||
vertical-align:baseline'><span style='font-family:Arial;mso-bidi-font-family:
|
||||
"Times New Roman";color:black'>Token based access control for devices &
|
||||
operations (protect back end services via exposing device type APIs)<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal style='mso-margin-top-alt:auto;mso-margin-bottom-alt:auto;
|
||||
margin-left:54.0pt;text-align:justify;text-justify:inter-ideograph;text-indent:
|
||||
-18.0pt;mso-list:l0 level2 lfo2;tab-stops:list 72.0pt;vertical-align:baseline'><span style='font-family:Arial;mso-fareast-font-family:
|
||||
"Times New Roman";mso-bidi-font-family:"Times New Roman";color:black'>Support
|
||||
for SCEP protocol (encryption and authenticity)<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal><span style='font-family:Arial;mso-fareast-font-family:"Times New Roman";
|
||||
mso-bidi-font-family:"Times New Roman";color:black'><br>
|
||||
</span><span style='font-family:Arial;mso-fareast-font-family:"Times New Roman";
|
||||
mso-bidi-font-family:"Times New Roman";color:#333333'>List of known issues</span><span
|
||||
style='font-family:Arial;mso-fareast-font-family:"Times New Roman";mso-bidi-font-family:
|
||||
"Times New Roman";color:#222222'><br style='mso-special-character:line-break'>
|
||||
<o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal><span style='font-family:Symbol;mso-ascii-font-family:Arial;
|
||||
mso-fareast-font-family:"Times New Roman";mso-bidi-font-family:"Times New Roman";
|
||||
color:#222222'>á</span><span style='font-family:Arial;mso-fareast-font-family:
|
||||
"Times New Roman";mso-bidi-font-family:"Times New Roman";color:#222222'><span
|
||||
style="mso-spacerun:yes"> </span><a
|
||||
href="https://wso2.org/jira/issues/?filter=13055"><span style='color:#1155CC'>https://wso2.org/jira/issues/?filter=13055</span></a><o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal><span style='font-family:Arial;mso-fareast-font-family:"Times New Roman";
|
||||
mso-bidi-font-family:"Times New Roman";color:#222222'><o:p> </o:p></span></p>
|
||||
|
||||
<p class=MsoNormal><span style='font-family:Arial;mso-fareast-font-family:"Times New Roman";
|
||||
mso-bidi-font-family:"Times New Roman";color:#222222'><o:p> </o:p></span></p>
|
||||
|
||||
<p class=MsoNormal><span style='font-family:Arial;mso-fareast-font-family:"Times New Roman";
|
||||
mso-bidi-font-family:"Times New Roman";color:black'>Thanks and Regards,</span><span
|
||||
style='font-family:Arial;mso-fareast-font-family:"Times New Roman";mso-bidi-font-family:
|
||||
"Times New Roman";color:#222222'><o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal><span style='font-family:Arial;mso-fareast-font-family:"Times New Roman";
|
||||
mso-bidi-font-family:"Times New Roman";color:#222222'><o:p> </o:p></span></p>
|
||||
|
||||
<p class=MsoNormal><b><span style='font-family:Arial;mso-fareast-font-family:
|
||||
"Times New Roman";mso-bidi-font-family:"Times New Roman";color:black'>~ EMM / <span
|
||||
class=SpellE>IoTS</span> Team ~</span></b><span style='font-family:Arial;
|
||||
mso-fareast-font-family:"Times New Roman";mso-bidi-font-family:"Times New Roman";
|
||||
color:#222222'><o:p></o:p></span></p>
|
||||
|
||||
<p class=MsoNormal><o:p> </o:p></p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -1,167 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
~
|
||||
~ WSO2 Inc. licenses this file to you under the Apache License,
|
||||
~ Version 2.0 (the "License"); you may not use this file except
|
||||
~ in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing,
|
||||
~ software distributed under the License is distributed on an
|
||||
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
~ KIND, either express or implied. See the License for the
|
||||
~ specific language governing permissions and limitations
|
||||
~ under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>das-extensions</artifactId>
|
||||
<groupId>org.wso2.iot</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>org.wso2.carbon.event.input.adapter.extensions</artifactId>
|
||||
<packaging>bundle</packaging>
|
||||
<name>WSO2 Carbon - Event Input MQTT Adapter Module</name>
|
||||
<description>This provides the capability of connecting to existing broker that supports OAUTH</description>
|
||||
<url>http://wso2.org</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon.analytics-common</groupId>
|
||||
<artifactId>org.wso2.carbon.event.input.adapter.core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon</groupId>
|
||||
<artifactId>org.wso2.carbon.logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon</groupId>
|
||||
<artifactId>org.wso2.carbon.core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.paho</groupId>
|
||||
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.wso2</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wso2.orbit.org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.googlecode.json-simple.wso2</groupId>
|
||||
<artifactId>json-simple</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon.devicemgt</groupId>
|
||||
<artifactId>org.wso2.carbon.identity.jwt.client.extension</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon.identity</groupId>
|
||||
<artifactId>org.wso2.carbon.identity.oauth.stub</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-scr-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-scr-descriptor</id>
|
||||
<goals>
|
||||
<goal>scr</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
|
||||
<Bundle-Name>${project.artifactId}</Bundle-Name>
|
||||
<Private-Package>
|
||||
org.wso2.carbon.event.input.adapter.extensions.internal,
|
||||
org.wso2.carbon.event.input.adapter.extensions.internal.*
|
||||
</Private-Package>
|
||||
<Export-Package>
|
||||
!org.wso2.carbon.event.input.adapter.extensions.internal,
|
||||
!org.wso2.carbon.event.input.adapter.extensions.internal.*,
|
||||
org.wso2.carbon.event.input.adapter.extensions.*
|
||||
</Export-Package>
|
||||
<Import-Package>
|
||||
org.wso2.carbon.event.input.adapter.core,
|
||||
org.wso2.carbon.event.input.adapter.core.*,
|
||||
javax.xml.namespace; version=0.0.0,
|
||||
org.eclipse.paho.client.mqttv3.*,
|
||||
org.apache.http;version="${httpclient.version.range}",
|
||||
org.apache.http.message;version="${httpclient.version.range}",
|
||||
org.apache.http.client;version="${httpclient.version.range}",
|
||||
org.apache.http.impl;version="${httpclient.version.range}",
|
||||
org.apache.http.conn.*;version="${httpclient.version.range}",
|
||||
org.apache.http.util;version="${httpclient.version.range}",
|
||||
org.apache.http.client.entity;version="${httpclient.version.range}",
|
||||
org.apache.http.client.methods;version="${httpclient.version.range}",
|
||||
org.apache.http.impl.client;version="${httpclient.version.range}",
|
||||
org.json.simple.*,
|
||||
org.wso2.carbon.identity.jwt.client.extension.*,
|
||||
com.jayway.jsonpath.*,
|
||||
javax.net.ssl,
|
||||
org.apache.commons.logging,
|
||||
org.apache.http.entity,
|
||||
org.osgi.framework,
|
||||
org.osgi.service.component,
|
||||
org.wso2.carbon.context,
|
||||
org.wso2.carbon.core,
|
||||
javax.servlet,
|
||||
javax.servlet.http,
|
||||
org.apache.axiom.om.util,
|
||||
org.osgi.service.http,
|
||||
org.wso2.carbon.user.api,
|
||||
org.wso2.carbon.user.core.service,
|
||||
org.wso2.carbon.user.core.tenant,
|
||||
org.wso2.carbon.utils,
|
||||
org.wso2.carbon.utils.multitenancy,
|
||||
org.wso2.carbon.identity.oauth2.stub;version="${carbon.identity.version.range}",
|
||||
org.wso2.carbon.identity.oauth2.stub.dto;version="${carbon.identity.version.range}",
|
||||
org.apache.axis2,
|
||||
org.apache.axis2.client,
|
||||
org.apache.axis2.context,
|
||||
org.apache.axis2.transport.http,
|
||||
org.apache.commons.httpclient,
|
||||
org.apache.commons.httpclient.contrib.ssl,
|
||||
org.apache.commons.httpclient.params,
|
||||
org.apache.commons.httpclient.protocol,
|
||||
org.apache.commons.pool,
|
||||
org.apache.commons.pool.impl,
|
||||
org.apache.log4j,
|
||||
org.wso2.carbon.base,
|
||||
org.wso2.carbon.core.util
|
||||
</Import-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.event.input.adapter.extensions;
|
||||
|
||||
/**
|
||||
* This is the return type of the ContentValidator.
|
||||
*/
|
||||
public class ContentInfo {
|
||||
/**
|
||||
* true if the content is valid if not when false then content will not be published.
|
||||
*/
|
||||
private boolean isValidContent;
|
||||
/**
|
||||
* msgText to be returned. eg: if the content is encrypted then we can decrypt the content and then validate and
|
||||
* return it.
|
||||
*/
|
||||
private String msgText;
|
||||
|
||||
public ContentInfo(boolean isValidContent, String msgText) {
|
||||
this.isValidContent = isValidContent;
|
||||
this.msgText = msgText;
|
||||
}
|
||||
|
||||
public boolean isValidContent() {
|
||||
return isValidContent;
|
||||
}
|
||||
|
||||
public void setIsValidContent(boolean isValidContent) {
|
||||
this.isValidContent = isValidContent;
|
||||
}
|
||||
|
||||
public String getMsgText() {
|
||||
return msgText;
|
||||
}
|
||||
|
||||
public void setMsgText(String msgText) {
|
||||
this.msgText = msgText;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.event.input.adapter.extensions;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This interface will be triggered to validate the stream content before publishing.
|
||||
*/
|
||||
public interface ContentValidator {
|
||||
/**
|
||||
*
|
||||
* @param params that related to input adapter to identify the client and the content
|
||||
* @return
|
||||
*/
|
||||
ContentInfo validate(String msgPayload, Map<String, String> params);
|
||||
}
|
@ -1,213 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.event.input.adapter.extensions.http;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
import org.wso2.carbon.context.PrivilegedCarbonContext;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapter;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterConfiguration;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterListener;
|
||||
import org.wso2.carbon.event.input.adapter.core.exception.InputEventAdapterException;
|
||||
import org.wso2.carbon.event.input.adapter.core.exception.InputEventAdapterRuntimeException;
|
||||
import org.wso2.carbon.event.input.adapter.core.exception.TestConnectionNotSupportedException;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.http.util.HTTPEventAdapterConstants;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.internal.EventAdapterServiceDataHolder;
|
||||
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class HTTPEventAdapter implements InputEventAdapter {
|
||||
|
||||
private final InputEventAdapterConfiguration eventAdapterConfiguration;
|
||||
private final Map<String, String> globalProperties;
|
||||
private InputEventAdapterListener eventAdaptorListener;
|
||||
private final String id = UUID.randomUUID().toString();
|
||||
public static ExecutorService executorService;
|
||||
private static final Log log = LogFactory.getLog(HTTPEventAdapter.class);
|
||||
private boolean isConnected = false;
|
||||
|
||||
public HTTPEventAdapter(InputEventAdapterConfiguration eventAdapterConfiguration,
|
||||
Map<String, String> globalProperties) {
|
||||
this.eventAdapterConfiguration = eventAdapterConfiguration;
|
||||
this.globalProperties = globalProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(InputEventAdapterListener eventAdaptorListener) throws InputEventAdapterException {
|
||||
this.eventAdaptorListener = eventAdaptorListener;
|
||||
|
||||
//ThreadPoolExecutor will be assigned if it is null
|
||||
if (executorService == null) {
|
||||
int minThread;
|
||||
int maxThread;
|
||||
long defaultKeepAliveTime;
|
||||
int jobQueueSize;
|
||||
|
||||
//If global properties are available those will be assigned else constant values will be assigned
|
||||
if (globalProperties.get(HTTPEventAdapterConstants.ADAPTER_MIN_THREAD_POOL_SIZE_NAME) != null) {
|
||||
minThread = Integer
|
||||
.parseInt(globalProperties.get(HTTPEventAdapterConstants.ADAPTER_MIN_THREAD_POOL_SIZE_NAME));
|
||||
} else {
|
||||
minThread = HTTPEventAdapterConstants.ADAPTER_MIN_THREAD_POOL_SIZE;
|
||||
}
|
||||
|
||||
if (globalProperties.get(HTTPEventAdapterConstants.ADAPTER_MAX_THREAD_POOL_SIZE_NAME) != null) {
|
||||
maxThread = Integer
|
||||
.parseInt(globalProperties.get(HTTPEventAdapterConstants.ADAPTER_MAX_THREAD_POOL_SIZE_NAME));
|
||||
} else {
|
||||
maxThread = HTTPEventAdapterConstants.ADAPTER_MAX_THREAD_POOL_SIZE;
|
||||
}
|
||||
|
||||
if (globalProperties.get(HTTPEventAdapterConstants.ADAPTER_KEEP_ALIVE_TIME_NAME) != null) {
|
||||
defaultKeepAliveTime = Integer
|
||||
.parseInt(globalProperties.get(HTTPEventAdapterConstants.ADAPTER_KEEP_ALIVE_TIME_NAME));
|
||||
} else {
|
||||
defaultKeepAliveTime = HTTPEventAdapterConstants.DEFAULT_KEEP_ALIVE_TIME_IN_MILLS;
|
||||
}
|
||||
|
||||
if (globalProperties.get(HTTPEventAdapterConstants.ADAPTER_EXECUTOR_JOB_QUEUE_SIZE_NAME) != null) {
|
||||
jobQueueSize = Integer
|
||||
.parseInt(globalProperties.get(HTTPEventAdapterConstants.ADAPTER_EXECUTOR_JOB_QUEUE_SIZE_NAME));
|
||||
} else {
|
||||
jobQueueSize = HTTPEventAdapterConstants.ADAPTER_EXECUTOR_JOB_QUEUE_SIZE;
|
||||
}
|
||||
|
||||
RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
try {
|
||||
executor.getQueue().put(r);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Exception while adding event to executor queue : " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
executorService = new ThreadPoolExecutor(minThread, maxThread, defaultKeepAliveTime, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(jobQueueSize), rejectedExecutionHandler);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testConnect() throws TestConnectionNotSupportedException {
|
||||
throw new TestConnectionNotSupportedException("not-supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
registerDynamicEndpoint(eventAdapterConfiguration.getName());
|
||||
isConnected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (isConnected){
|
||||
isConnected = false;
|
||||
unregisterDynamicEndpoint(eventAdapterConfiguration.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof HTTPEventAdapter))
|
||||
return false;
|
||||
|
||||
HTTPEventAdapter that = (HTTPEventAdapter) o;
|
||||
|
||||
return id.equals(that.id);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEventDuplicatedInCluster() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPolling() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void registerDynamicEndpoint(String adapterName) {
|
||||
|
||||
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
|
||||
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
|
||||
|
||||
String endpoint;
|
||||
if (MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) {
|
||||
endpoint = HTTPEventAdapterConstants.ENDPOINT_PREFIX + adapterName;
|
||||
} else {
|
||||
endpoint = HTTPEventAdapterConstants.ENDPOINT_PREFIX + HTTPEventAdapterConstants.ENDPOINT_TENANT_KEY
|
||||
+ HTTPEventAdapterConstants.ENDPOINT_URL_SEPARATOR + tenantDomain
|
||||
+ HTTPEventAdapterConstants.ENDPOINT_URL_SEPARATOR + adapterName;
|
||||
}
|
||||
|
||||
try {
|
||||
HttpService httpService = EventAdapterServiceDataHolder.getHTTPService();
|
||||
if (httpService == null) {
|
||||
throw new InputEventAdapterRuntimeException(
|
||||
"HttpService not available, Error in registering endpoint " + endpoint);
|
||||
}
|
||||
httpService.registerServlet(endpoint, new HTTPMessageServlet(eventAdaptorListener, tenantId,
|
||||
eventAdapterConfiguration),
|
||||
new Hashtable(), httpService.createDefaultHttpContext());
|
||||
} catch (ServletException | NamespaceException e) {
|
||||
throw new InputEventAdapterRuntimeException("Error in registering endpoint " + endpoint, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void unregisterDynamicEndpoint(String adapterName) {
|
||||
HttpService httpService = EventAdapterServiceDataHolder.getHTTPService();
|
||||
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
|
||||
String endpoint;
|
||||
if (MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) {
|
||||
endpoint = HTTPEventAdapterConstants.ENDPOINT_PREFIX + adapterName;
|
||||
} else {
|
||||
endpoint = HTTPEventAdapterConstants.ENDPOINT_PREFIX + HTTPEventAdapterConstants.ENDPOINT_TENANT_KEY
|
||||
+ HTTPEventAdapterConstants.ENDPOINT_URL_SEPARATOR + tenantDomain
|
||||
+ HTTPEventAdapterConstants.ENDPOINT_URL_SEPARATOR + adapterName;
|
||||
}
|
||||
if (httpService != null) {
|
||||
httpService.unregister(endpoint);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2005 - 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.http;
|
||||
|
||||
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapter;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterConfiguration;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterFactory;
|
||||
import org.wso2.carbon.event.input.adapter.core.MessageType;
|
||||
import org.wso2.carbon.event.input.adapter.core.Property;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.http.util.HTTPEventAdapterConstants;
|
||||
import org.wso2.carbon.utils.CarbonUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* The http event adapter factory class to create a http input adapter
|
||||
*/
|
||||
public class HTTPEventAdapterFactory extends InputEventAdapterFactory {
|
||||
|
||||
private ResourceBundle resourceBundle =
|
||||
ResourceBundle.getBundle("org.wso2.carbon.event.input.adapter.extensions.http.i18n.Resources", Locale.getDefault());
|
||||
private int httpPort;
|
||||
private int httpsPort;
|
||||
private int portOffset;
|
||||
|
||||
public HTTPEventAdapterFactory() {
|
||||
portOffset = getPortOffset();
|
||||
httpPort = HTTPEventAdapterConstants.DEFAULT_HTTP_PORT + portOffset;
|
||||
httpsPort = HTTPEventAdapterConstants.DEFAULT_HTTPS_PORT + portOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return HTTPEventAdapterConstants.ADAPTER_TYPE_HTTP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSupportedMessageFormats() {
|
||||
List<String> supportInputMessageTypes = new ArrayList<String>();
|
||||
supportInputMessageTypes.add(MessageType.JSON);
|
||||
return supportInputMessageTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Property> getPropertyList() {
|
||||
|
||||
List<Property> propertyList = new ArrayList<Property>();
|
||||
|
||||
// Transport Exposed
|
||||
Property exposedTransportsProperty = new Property(HTTPEventAdapterConstants.EXPOSED_TRANSPORTS);
|
||||
exposedTransportsProperty.setRequired(true);
|
||||
exposedTransportsProperty.setDisplayName(
|
||||
resourceBundle.getString(HTTPEventAdapterConstants.EXPOSED_TRANSPORTS));
|
||||
exposedTransportsProperty.setOptions(
|
||||
new String[]{HTTPEventAdapterConstants.HTTPS, HTTPEventAdapterConstants.HTTP,
|
||||
HTTPEventAdapterConstants.LOCAL, HTTPEventAdapterConstants.ALL});
|
||||
exposedTransportsProperty.setDefaultValue(HTTPEventAdapterConstants.ALL);
|
||||
propertyList.add(exposedTransportsProperty);
|
||||
|
||||
// OAUTH validation endpoint admin service username
|
||||
Property username = new Property(HTTPEventAdapterConstants.USERNAME);
|
||||
username.setRequired(true);
|
||||
username.setDisplayName(resourceBundle.getString(HTTPEventAdapterConstants.USERNAME));
|
||||
username.setHint(resourceBundle.getString(HTTPEventAdapterConstants.USERNAME_HINT));
|
||||
propertyList.add(username);
|
||||
|
||||
// OAUTH validation endpoint admin service password
|
||||
Property password = new Property(HTTPEventAdapterConstants.PASSWORD);
|
||||
password.setRequired(true);
|
||||
password.setDisplayName(resourceBundle.getString(HTTPEventAdapterConstants.PASSWORD));
|
||||
password.setHint(resourceBundle.getString(HTTPEventAdapterConstants.PASSWORD_HINT));
|
||||
propertyList.add(password);
|
||||
|
||||
// OAUTH validation endpoint
|
||||
Property tokenValidationEndpoint = new Property(HTTPEventAdapterConstants.TOKEN_VALIDATION_ENDPOINT_URL);
|
||||
tokenValidationEndpoint.setRequired(true);
|
||||
tokenValidationEndpoint.setDisplayName(resourceBundle.getString(HTTPEventAdapterConstants.TOKEN_VALIDATION_ENDPOINT_URL));
|
||||
tokenValidationEndpoint.setHint(resourceBundle.getString(HTTPEventAdapterConstants.TOKEN_VALIDATION_ENDPOINT_URL_HINT));
|
||||
propertyList.add(tokenValidationEndpoint);
|
||||
|
||||
Property maximumHttpConnectionPerHost = new Property(HTTPEventAdapterConstants.MAXIMUM_HTTP_CONNECTION_PER_HOST);
|
||||
maximumHttpConnectionPerHost.setRequired(true);
|
||||
maximumHttpConnectionPerHost.setDisplayName(resourceBundle.getString(
|
||||
HTTPEventAdapterConstants.MAXIMUM_HTTP_CONNECTION_PER_HOST));
|
||||
maximumHttpConnectionPerHost.setHint(resourceBundle.getString(
|
||||
HTTPEventAdapterConstants.MAXIMUM_HTTP_CONNECTION_PER_HOST_HINT));
|
||||
maximumHttpConnectionPerHost.setDefaultValue(HTTPEventAdapterConstants.MAX_HTTP_CONNECTION);
|
||||
propertyList.add(maximumHttpConnectionPerHost);
|
||||
|
||||
Property maxTotalHttpConnection = new Property(HTTPEventAdapterConstants.MAXIMUM_TOTAL_HTTP_CONNECTION);
|
||||
maxTotalHttpConnection.setRequired(true);
|
||||
maxTotalHttpConnection.setDisplayName(resourceBundle.getString(
|
||||
HTTPEventAdapterConstants.MAXIMUM_TOTAL_HTTP_CONNECTION));
|
||||
maxTotalHttpConnection.setHint(resourceBundle.getString(
|
||||
HTTPEventAdapterConstants.MAXIMUM_TOTAL_HTTP_CONNECTION_HINT));
|
||||
maxTotalHttpConnection.setDefaultValue(HTTPEventAdapterConstants.MAX_TOTAL_HTTP_CONNECTION);
|
||||
propertyList.add(maxTotalHttpConnection);
|
||||
|
||||
//Content Validator details
|
||||
Property contentValidator = new Property(HTTPEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME);
|
||||
contentValidator.setDisplayName(
|
||||
resourceBundle.getString(HTTPEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME));
|
||||
contentValidator.setRequired(false);
|
||||
contentValidator.setHint(
|
||||
resourceBundle.getString(HTTPEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME_HINT));
|
||||
contentValidator.setDefaultValue(HTTPEventAdapterConstants.DEFAULT);
|
||||
propertyList.add(contentValidator);
|
||||
|
||||
//Content Validator Params details
|
||||
Property contentValidatorParams = new Property(HTTPEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS);
|
||||
contentValidatorParams.setDisplayName(
|
||||
resourceBundle.getString(HTTPEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS));
|
||||
contentValidatorParams.setRequired(false);
|
||||
contentValidatorParams.setHint(
|
||||
resourceBundle.getString(HTTPEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS_HINT));
|
||||
contentValidatorParams.setDefaultValue(HTTPEventAdapterConstants.MQTT_CONTENT_VALIDATION_DEFAULT_PARAMETERS);
|
||||
propertyList.add(contentValidatorParams);
|
||||
return propertyList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageTips() {
|
||||
return resourceBundle.getString(HTTPEventAdapterConstants.ADAPTER_USAGE_TIPS_PREFIX) + httpPort +
|
||||
resourceBundle.getString(HTTPEventAdapterConstants.ADAPTER_USAGE_TIPS_MID1) + httpsPort +
|
||||
resourceBundle.getString(HTTPEventAdapterConstants.ADAPTER_USAGE_TIPS_MID2) + httpPort +
|
||||
resourceBundle.getString(HTTPEventAdapterConstants.ADAPTER_USAGE_TIPS_MID3) + httpsPort +
|
||||
resourceBundle.getString(HTTPEventAdapterConstants.ADAPTER_USAGE_TIPS_POSTFIX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputEventAdapter createEventAdapter(InputEventAdapterConfiguration eventAdapterConfiguration,
|
||||
Map<String, String> globalProperties) {
|
||||
return new HTTPEventAdapter(eventAdapterConfiguration, globalProperties);
|
||||
}
|
||||
|
||||
private int getPortOffset() {
|
||||
return CarbonUtils.getPortFromServerConfig(HTTPEventAdapterConstants.CARBON_CONFIG_PORT_OFFSET_NODE) + 1;
|
||||
}
|
||||
}
|
@ -1,372 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2005 - 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.event.input.adapter.extensions.http;
|
||||
|
||||
import org.apache.axis2.context.ServiceContext;
|
||||
import org.apache.axis2.transport.http.HTTPConstants;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.commons.pool.impl.GenericObjectPool;
|
||||
import org.wso2.carbon.context.PrivilegedCarbonContext;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterConfiguration;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterListener;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.ContentInfo;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.ContentValidator;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.http.oauth.OAuthTokenValidaterStubFactory;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.http.util.AuthenticationInfo;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.http.util.HTTPContentValidator;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.http.util.HTTPEventAdapterConstants;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.internal.EventAdapterServiceDataHolder;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.exception.MQTTContentValidatorInitializationException;
|
||||
import org.wso2.carbon.identity.oauth2.stub.OAuth2TokenValidationServiceStub;
|
||||
import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationRequestDTO;
|
||||
import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationRequestDTO_OAuth2AccessToken;
|
||||
import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationResponseDTO;
|
||||
import org.wso2.carbon.user.api.UserStoreException;
|
||||
import org.wso2.carbon.user.core.service.RealmService;
|
||||
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.rmi.RemoteException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* This will act as the event reciver.
|
||||
*/
|
||||
public class HTTPMessageServlet extends HttpServlet {
|
||||
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
private static final String AUTH_MESSAGE_STORE_AUTHENTICATION_INFO = "AUTH_MESSAGE_STORE_AUTHENTICATION_INFO";
|
||||
private static final String AUTH_FAILURE_RESPONSE = "_AUTH_FAILURE_";
|
||||
private static final Pattern PATTERN = Pattern.compile("[B|b]earer\\s");
|
||||
private static final String TOKEN_TYPE = "bearer";
|
||||
private static String cookie;
|
||||
private static Log log = LogFactory.getLog(HTTPMessageServlet.class);
|
||||
private GenericObjectPool stubs;
|
||||
private static Map<String, String> contentValidationProperties;
|
||||
private static ContentValidator contentValidator;
|
||||
private InputEventAdapterListener eventAdaptorListener;
|
||||
private int tenantId;
|
||||
private String exposedTransports;
|
||||
|
||||
public HTTPMessageServlet(InputEventAdapterListener eventAdaptorListener, int tenantId,
|
||||
InputEventAdapterConfiguration eventAdapterConfiguration) {
|
||||
this.eventAdaptorListener = eventAdaptorListener;
|
||||
this.tenantId = tenantId;
|
||||
this.exposedTransports = eventAdapterConfiguration.getProperties().get(
|
||||
HTTPEventAdapterConstants.EXPOSED_TRANSPORTS);
|
||||
this.stubs = new GenericObjectPool(new OAuthTokenValidaterStubFactory(eventAdapterConfiguration));
|
||||
this.contentValidationProperties = new HashMap<String, String>();
|
||||
String contentValidationParams = eventAdapterConfiguration.getProperties().get(
|
||||
HTTPEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS);
|
||||
if (contentValidationParams != null && !contentValidationParams.isEmpty()) {
|
||||
String validationParams[] = contentValidationParams.split(",");
|
||||
for (String validationParam : validationParams) {
|
||||
String[] validationProperty = validationParam.split(":");
|
||||
if (validationProperty.length == 2) {
|
||||
contentValidationProperties.put(validationProperty[0], validationProperty[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String className = eventAdapterConfiguration.getProperties().get(
|
||||
HTTPEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME);
|
||||
if (HTTPEventAdapterConstants.DEFAULT.equals(className)) {
|
||||
contentValidator = new HTTPContentValidator();
|
||||
} else {
|
||||
try {
|
||||
Class<? extends ContentValidator> contentValidatorClass = Class.forName(className)
|
||||
.asSubclass(ContentValidator.class);
|
||||
contentValidator = contentValidatorClass.newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new MQTTContentValidatorInitializationException(
|
||||
"Unable to find the class authorizer: " + className, e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new MQTTContentValidatorInitializationException(
|
||||
"Unable to create an instance of :" + className, e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new MQTTContentValidatorInitializationException("Access of the instance in not allowed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getBearerToken(HttpServletRequest request) {
|
||||
String authorizationHeader = request.getHeader(AUTHORIZATION_HEADER);
|
||||
if (authorizationHeader != null) {
|
||||
Matcher matcher = PATTERN.matcher(authorizationHeader);
|
||||
if (matcher.find()) {
|
||||
authorizationHeader = authorizationHeader.substring(matcher.end());
|
||||
}
|
||||
}
|
||||
return authorizationHeader;
|
||||
}
|
||||
|
||||
private AuthenticationInfo checkAuthentication(HttpServletRequest req) {
|
||||
AuthenticationInfo authenticationInfo = (AuthenticationInfo) req.getSession().getAttribute(
|
||||
AUTH_MESSAGE_STORE_AUTHENTICATION_INFO);
|
||||
if (authenticationInfo != null) {
|
||||
return authenticationInfo;
|
||||
}
|
||||
String bearerToken = getBearerToken(req);
|
||||
if (bearerToken == null) {
|
||||
return authenticationInfo;
|
||||
}
|
||||
|
||||
RealmService realmService = EventAdapterServiceDataHolder.getRealmService();
|
||||
try {
|
||||
authenticationInfo = validateToken(bearerToken);
|
||||
boolean success = authenticationInfo.isAuthenticated();
|
||||
if (success) {
|
||||
req.getSession().setAttribute(AUTH_MESSAGE_STORE_AUTHENTICATION_INFO, authenticationInfo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("checkAuthentication() fail: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return authenticationInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets a string accessToken and validates it
|
||||
*
|
||||
* @param token which need to be validated.
|
||||
* @return AuthenticationInfo with the validated results.
|
||||
*/
|
||||
private AuthenticationInfo validateToken(String token) {
|
||||
OAuth2TokenValidationServiceStub tokenValidationServiceStub = null;
|
||||
try {
|
||||
Object stub = this.stubs.borrowObject();
|
||||
if (stub != null) {
|
||||
tokenValidationServiceStub = (OAuth2TokenValidationServiceStub) stub;
|
||||
if (cookie != null) {
|
||||
tokenValidationServiceStub._getServiceClient().getOptions().setProperty(
|
||||
HTTPConstants.COOKIE_STRING, cookie);
|
||||
}
|
||||
return getAuthenticationInfo(token, tokenValidationServiceStub);
|
||||
} else {
|
||||
log.warn("Stub initialization failed.");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
log.error("Error on connecting with the validation endpoint.", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Error occurred in borrowing an validation stub from the pool.", e);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
if (tokenValidationServiceStub != null) {
|
||||
this.stubs.returnObject(tokenValidationServiceStub);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Error occurred while returning the object back to the oauth token validation service " +
|
||||
"stub pool.", e);
|
||||
}
|
||||
}
|
||||
AuthenticationInfo authenticationInfo = new AuthenticationInfo();
|
||||
authenticationInfo.setAuthenticated(false);
|
||||
authenticationInfo.setTenantId(-1);
|
||||
return authenticationInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates an AuthenticationInfo object that is used for authorization. This method will validate the token
|
||||
* and
|
||||
* sets the required parameters to the object.
|
||||
*
|
||||
* @param token that needs to be validated.
|
||||
* @param tokenValidationServiceStub stub that is used to call the external service.
|
||||
* @return AuthenticationInfo This contains the information related to authenticated client.
|
||||
* @throws RemoteException that triggers when failing to call the external service..
|
||||
*/
|
||||
private AuthenticationInfo getAuthenticationInfo(String token,
|
||||
OAuth2TokenValidationServiceStub tokenValidationServiceStub)
|
||||
throws RemoteException, UserStoreException {
|
||||
AuthenticationInfo authenticationInfo = new AuthenticationInfo();
|
||||
OAuth2TokenValidationRequestDTO validationRequest = new OAuth2TokenValidationRequestDTO();
|
||||
OAuth2TokenValidationRequestDTO_OAuth2AccessToken accessToken =
|
||||
new OAuth2TokenValidationRequestDTO_OAuth2AccessToken();
|
||||
accessToken.setTokenType(TOKEN_TYPE);
|
||||
accessToken.setIdentifier(token);
|
||||
validationRequest.setAccessToken(accessToken);
|
||||
boolean authenticated;
|
||||
OAuth2TokenValidationResponseDTO tokenValidationResponse;
|
||||
tokenValidationResponse = tokenValidationServiceStub.validate(validationRequest);
|
||||
if (tokenValidationResponse == null) {
|
||||
authenticationInfo.setAuthenticated(false);
|
||||
return authenticationInfo;
|
||||
}
|
||||
authenticated = tokenValidationResponse.getValid();
|
||||
if (authenticated) {
|
||||
String authorizedUser = tokenValidationResponse.getAuthorizedUser();
|
||||
String username = MultitenantUtils.getTenantAwareUsername(authorizedUser);
|
||||
String tenantDomain = MultitenantUtils.getTenantDomain(authorizedUser);
|
||||
authenticationInfo.setUsername(username);
|
||||
authenticationInfo.setTenantDomain(tenantDomain);
|
||||
RealmService realmService = EventAdapterServiceDataHolder.getRealmService();
|
||||
int tenantId = realmService.getTenantManager().getTenantId(authenticationInfo.getTenantDomain());
|
||||
authenticationInfo.setTenantId(tenantId);
|
||||
} else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Token validation failed for token: " + token);
|
||||
}
|
||||
}
|
||||
ServiceContext serviceContext = tokenValidationServiceStub._getServiceClient()
|
||||
.getLastOperationContext().getServiceContext();
|
||||
cookie = (String) serviceContext.getProperty(HTTPConstants.COOKIE_STRING);
|
||||
authenticationInfo.setAuthenticated(authenticated);
|
||||
return authenticationInfo;
|
||||
}
|
||||
|
||||
|
||||
private String inputStreamToString(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buff = new byte[1024];
|
||||
int i;
|
||||
while ((i = in.read(buff)) > 0) {
|
||||
out.write(buff, 0, i);
|
||||
}
|
||||
out.close();
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req,
|
||||
HttpServletResponse res) throws IOException {
|
||||
|
||||
String data = this.inputStreamToString(req.getInputStream());
|
||||
if (data == null) {
|
||||
log.warn("Event Object is empty/null");
|
||||
return;
|
||||
}
|
||||
AuthenticationInfo authenticationInfo = null;
|
||||
if (exposedTransports.equalsIgnoreCase(HTTPEventAdapterConstants.HTTPS)) {
|
||||
if (!req.isSecure()) {
|
||||
res.setStatus(403);
|
||||
log.error("Only Secured endpoint is enabled for requests");
|
||||
return;
|
||||
} else {
|
||||
authenticationInfo = this.checkAuthentication(req);
|
||||
int tenantId = authenticationInfo != null ? authenticationInfo.getTenantId() : -1;
|
||||
if (tenantId == -1) {
|
||||
res.getOutputStream().write(AUTH_FAILURE_RESPONSE.getBytes());
|
||||
res.setStatus(401);
|
||||
log.error("Authentication failed for the request");
|
||||
return;
|
||||
} else if (tenantId != this.tenantId) {
|
||||
res.getOutputStream().write(AUTH_FAILURE_RESPONSE.getBytes());
|
||||
res.setStatus(401);
|
||||
log.error("Authentication failed for the request");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (exposedTransports.equalsIgnoreCase(HTTPEventAdapterConstants.HTTP)) {
|
||||
if (req.isSecure()) {
|
||||
res.setStatus(403);
|
||||
log.error("Only unsecured endpoint is enabled for requests");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
authenticationInfo = this.checkAuthentication(req);
|
||||
int tenantId = authenticationInfo != null ? authenticationInfo.getTenantId() : -1;
|
||||
if (tenantId == -1) {
|
||||
res.getOutputStream().write(AUTH_FAILURE_RESPONSE.getBytes());
|
||||
res.setStatus(401);
|
||||
log.error("Authentication failed for the request");
|
||||
return;
|
||||
} else if (tenantId != this.tenantId) {
|
||||
res.getOutputStream().write(AUTH_FAILURE_RESPONSE.getBytes());
|
||||
res.setStatus(401);
|
||||
log.error("Authentication failed for the request");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Message : " + data);
|
||||
}
|
||||
|
||||
if (authenticationInfo != null) {
|
||||
Map<String, String> paramMap = new HashMap<>();
|
||||
paramMap.putAll(contentValidationProperties);
|
||||
Enumeration<String> reqParameterNames = req.getParameterNames();
|
||||
while (reqParameterNames.hasMoreElements()) {
|
||||
String paramterName = reqParameterNames.nextElement();
|
||||
paramMap.put(paramterName, req.getParameter(paramterName));
|
||||
}
|
||||
paramMap.put(HTTPEventAdapterConstants.USERNAME_TAG, authenticationInfo.getUsername());
|
||||
paramMap.put(HTTPEventAdapterConstants.TENANT_DOMAIN_TAG, authenticationInfo.getTenantDomain());
|
||||
if (contentValidator != null) {
|
||||
ContentInfo contentInfo = contentValidator.validate(data, paramMap);
|
||||
if (contentInfo != null && contentInfo.isValidContent()) {
|
||||
HTTPEventAdapter.executorService.submit(new HTTPRequestProcessor(eventAdaptorListener,
|
||||
contentInfo.getMsgText(), tenantId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req,
|
||||
HttpServletResponse res) throws IOException {
|
||||
doPost(req, res);
|
||||
}
|
||||
|
||||
public class HTTPRequestProcessor implements Runnable {
|
||||
|
||||
private InputEventAdapterListener inputEventAdapterListener;
|
||||
private String payload;
|
||||
private int tenantId;
|
||||
|
||||
public HTTPRequestProcessor(InputEventAdapterListener inputEventAdapterListener,
|
||||
String payload, int tenantId) {
|
||||
this.inputEventAdapterListener = inputEventAdapterListener;
|
||||
this.payload = payload;
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
PrivilegedCarbonContext.startTenantFlow();
|
||||
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantId(tenantId);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Event received in HTTP Event Adapter - " + payload);
|
||||
}
|
||||
|
||||
if (payload.trim() != null) {
|
||||
inputEventAdapterListener.onEvent(payload);
|
||||
} else {
|
||||
log.warn("Dropping the empty/null event received through http adapter");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error while parsing http request for processing: " + e.getMessage(), e);
|
||||
} finally {
|
||||
PrivilegedCarbonContext.endTenantFlow();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.http.oauth;
|
||||
|
||||
import org.apache.axis2.AxisFault;
|
||||
import org.apache.axis2.Constants;
|
||||
import org.apache.axis2.client.Options;
|
||||
import org.apache.axis2.client.ServiceClient;
|
||||
import org.apache.axis2.transport.http.HTTPConstants;
|
||||
import org.apache.axis2.transport.http.HttpTransportProperties;
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.commons.httpclient.HttpConnectionManager;
|
||||
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
|
||||
import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;
|
||||
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
|
||||
import org.apache.commons.httpclient.protocol.Protocol;
|
||||
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
|
||||
import org.apache.commons.pool.BasePoolableObjectFactory;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.wso2.carbon.core.util.Utils;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterConfiguration;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.http.oauth.exception.OAuthTokenValidationException;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.http.util.HTTPEventAdapterConstants;
|
||||
import org.wso2.carbon.identity.oauth2.stub.OAuth2TokenValidationServiceStub;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* This follows object pool pattern to manage the stub for oauth validation service.
|
||||
*/
|
||||
public class OAuthTokenValidaterStubFactory extends BasePoolableObjectFactory {
|
||||
private static final Logger log = Logger.getLogger(OAuthTokenValidaterStubFactory.class);
|
||||
private HttpClient httpClient;
|
||||
InputEventAdapterConfiguration eventAdapterConfiguration;
|
||||
|
||||
|
||||
public OAuthTokenValidaterStubFactory(InputEventAdapterConfiguration eventAdapterConfiguration) {
|
||||
this.eventAdapterConfiguration = eventAdapterConfiguration;
|
||||
this.httpClient = createHttpClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a OAuth2TokenValidationServiceStub object to the pool.
|
||||
*
|
||||
* @return an OAuthValidationStub object
|
||||
* @throws Exception thrown when creating the object.
|
||||
*/
|
||||
@Override
|
||||
public Object makeObject() throws Exception {
|
||||
return this.generateStub();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to clean up the OAuth validation stub and releases to the object pool.
|
||||
*
|
||||
* @param o object that needs to be released.
|
||||
* @throws Exception throws when failed to release to the pool
|
||||
*/
|
||||
@Override
|
||||
public void passivateObject(Object o) throws Exception {
|
||||
if (o instanceof OAuth2TokenValidationServiceStub) {
|
||||
OAuth2TokenValidationServiceStub stub = (OAuth2TokenValidationServiceStub) o;
|
||||
stub._getServiceClient().cleanupTransport();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to create a stub which will be triggered through object pool factory, which will create an
|
||||
* instance of it.
|
||||
*
|
||||
* @return OAuth2TokenValidationServiceStub stub that is used to call an external service.
|
||||
* @throws OAuthTokenValidationException will be thrown when initialization failed.
|
||||
*/
|
||||
private OAuth2TokenValidationServiceStub generateStub() throws OAuthTokenValidationException {
|
||||
OAuth2TokenValidationServiceStub stub;
|
||||
try {
|
||||
URL hostURL = new URL(Utils.replaceSystemProperty(eventAdapterConfiguration.getProperties().get(
|
||||
HTTPEventAdapterConstants.TOKEN_VALIDATION_ENDPOINT_URL)));
|
||||
if (hostURL != null) {
|
||||
stub = new OAuth2TokenValidationServiceStub(hostURL.toString());
|
||||
if (stub != null) {
|
||||
ServiceClient client = stub._getServiceClient();
|
||||
client.getServiceContext().getConfigurationContext().setProperty(
|
||||
HTTPConstants.CACHED_HTTP_CLIENT, httpClient);
|
||||
|
||||
HttpTransportProperties.Authenticator auth =
|
||||
new HttpTransportProperties.Authenticator();
|
||||
auth.setPreemptiveAuthentication(true);
|
||||
String username = eventAdapterConfiguration.getProperties().get(HTTPEventAdapterConstants
|
||||
.USERNAME);
|
||||
String password = eventAdapterConfiguration.getProperties().get(HTTPEventAdapterConstants
|
||||
.PASSWORD);
|
||||
auth.setPassword(username);
|
||||
auth.setUsername(password);
|
||||
Options options = client.getOptions();
|
||||
options.setProperty(HTTPConstants.AUTHENTICATE, auth);
|
||||
options.setProperty(HTTPConstants.REUSE_HTTP_CLIENT, Constants.VALUE_TRUE);
|
||||
client.setOptions(options);
|
||||
if (hostURL.getProtocol().equals("https")) {
|
||||
// set up ssl factory since axis2 https transport is used.
|
||||
EasySSLProtocolSocketFactory sslProtocolSocketFactory =
|
||||
createProtocolSocketFactory();
|
||||
Protocol authhttps = new Protocol(hostURL.getProtocol(),
|
||||
(ProtocolSocketFactory) sslProtocolSocketFactory,
|
||||
hostURL.getPort());
|
||||
Protocol.registerProtocol(hostURL.getProtocol(), authhttps);
|
||||
options.setProperty(HTTPConstants.CUSTOM_PROTOCOL_HANDLER, authhttps);
|
||||
}
|
||||
} else {
|
||||
String errorMsg = "OAuth Validation instanization failed.";
|
||||
throw new OAuthTokenValidationException(errorMsg);
|
||||
}
|
||||
} else {
|
||||
String errorMsg = "host url is invalid";
|
||||
throw new OAuthTokenValidationException(errorMsg);
|
||||
}
|
||||
} catch (AxisFault axisFault) {
|
||||
throw new OAuthTokenValidationException(
|
||||
"Error occurred while creating the OAuth2TokenValidationServiceStub.", axisFault);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new OAuthTokenValidationException(
|
||||
"Error occurred while parsing token endpoint URL", e);
|
||||
}
|
||||
|
||||
return stub;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is required to create a trusted connection with the external entity.
|
||||
* Have to manually configure it since we use CommonHTTPTransport(axis2 transport) in axis2.
|
||||
*
|
||||
* @return an EasySSLProtocolSocketFactory for SSL communication.
|
||||
*/
|
||||
private EasySSLProtocolSocketFactory createProtocolSocketFactory() throws OAuthTokenValidationException {
|
||||
try {
|
||||
EasySSLProtocolSocketFactory easySSLPSFactory = new EasySSLProtocolSocketFactory();
|
||||
return easySSLPSFactory;
|
||||
} catch (IOException e) {
|
||||
String errorMsg = "Failed to initiate EasySSLProtocolSocketFactory.";
|
||||
throw new OAuthTokenValidationException(errorMsg, e);
|
||||
} catch (GeneralSecurityException e) {
|
||||
String errorMsg = "Failed to set the key material in easy ssl factory.";
|
||||
throw new OAuthTokenValidationException(errorMsg, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This created httpclient pool that can be used to connect to external entity. This connection can be configured
|
||||
* via broker.xml by setting up the required http connection parameters.
|
||||
*
|
||||
* @return an instance of HttpClient that is configured with MultiThreadedHttpConnectionManager
|
||||
*/
|
||||
private HttpClient createHttpClient() {
|
||||
HttpConnectionManagerParams params = new HttpConnectionManagerParams();
|
||||
params.setDefaultMaxConnectionsPerHost(Integer.parseInt(eventAdapterConfiguration.getProperties().get(
|
||||
HTTPEventAdapterConstants.MAXIMUM_HTTP_CONNECTION_PER_HOST)));
|
||||
params.setMaxTotalConnections(Integer.parseInt(eventAdapterConfiguration.getProperties().get(
|
||||
HTTPEventAdapterConstants.MAXIMUM_TOTAL_HTTP_CONNECTION)));
|
||||
HttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
|
||||
connectionManager.setParams(params);
|
||||
return new HttpClient(connectionManager);
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.http.oauth.exception;
|
||||
|
||||
/**
|
||||
* This Exception will be thrown, when there any interference with token validation flow.
|
||||
*/
|
||||
public class OAuthTokenValidationException extends Exception {
|
||||
private String errMessage;
|
||||
|
||||
public OAuthTokenValidationException(String msg, Exception nestedEx) {
|
||||
super(msg, nestedEx);
|
||||
setErrorMessage(msg);
|
||||
}
|
||||
|
||||
public OAuthTokenValidationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
setErrorMessage(message);
|
||||
}
|
||||
|
||||
public OAuthTokenValidationException(String msg) {
|
||||
super(msg);
|
||||
setErrorMessage(msg);
|
||||
}
|
||||
|
||||
public OAuthTokenValidationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public OAuthTokenValidationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errMessage) {
|
||||
this.errMessage = errMessage;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.event.input.adapter.extensions.http.util;
|
||||
|
||||
/**
|
||||
* This will be return after authentication and this will consist of the authenticated user info.
|
||||
*/
|
||||
public class AuthenticationInfo {
|
||||
|
||||
/**
|
||||
* this variable is used to check whether the client is authenticated.
|
||||
*/
|
||||
private boolean authenticated;
|
||||
private String username;
|
||||
private String tenantDomain;
|
||||
private int tenantId;
|
||||
/**
|
||||
* returns whether the client is authenticated
|
||||
*/
|
||||
public boolean isAuthenticated() {
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
public void setAuthenticated(boolean authenticated) {
|
||||
this.authenticated = authenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the authenticated client username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the authenticated client tenant domain
|
||||
*/
|
||||
public String getTenantDomain() {
|
||||
return tenantDomain;
|
||||
}
|
||||
|
||||
public void setTenantDomain(String tenantDomain) {
|
||||
this.tenantDomain = tenantDomain;
|
||||
}
|
||||
|
||||
public int getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(int tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.event.input.adapter.extensions.http.util;
|
||||
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.ContentInfo;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.ContentValidator;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class HTTPContentValidator implements ContentValidator {
|
||||
private static final Log log = LogFactory.getLog(HTTPContentValidator.class);
|
||||
private static String JSON_ARRAY_START_CHAR = "[";
|
||||
|
||||
@Override
|
||||
public ContentInfo validate(String msgPayload, Map<String, String> paramMap) {
|
||||
String deviceId = paramMap.get("deviceId");
|
||||
String msg = msgPayload;
|
||||
String deviceIdJsonPath = paramMap.get(HTTPEventAdapterConstants.DEVICE_ID_JSON_PATH);
|
||||
boolean status;
|
||||
if (msg.startsWith(JSON_ARRAY_START_CHAR)) {
|
||||
status = processMultipleEvents(msg, deviceId, deviceIdJsonPath);
|
||||
} else {
|
||||
status = processSingleEvent(msg, deviceId, deviceIdJsonPath);
|
||||
}
|
||||
return new ContentInfo(status, msg);
|
||||
}
|
||||
|
||||
private boolean processSingleEvent(String msg, String deviceIdFromTopic, String deviceIdJsonPath) {
|
||||
Object res = JsonPath.read(msg, deviceIdJsonPath);
|
||||
String deviceIdFromContent = (res != null) ? res.toString() : "";
|
||||
if (deviceIdFromContent.equals(deviceIdFromTopic)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean processMultipleEvents(String msg, String deviceIdFromTopic, String deviceIdJsonPath) {
|
||||
try {
|
||||
JSONParser jsonParser = new JSONParser();
|
||||
JSONArray jsonArray = (JSONArray) jsonParser.parse(msg);
|
||||
boolean status = false;
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
status = processSingleEvent(jsonArray.get(i).toString(), deviceIdFromTopic, deviceIdJsonPath);
|
||||
if (!status) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
} catch (ParseException e) {
|
||||
log.error("Invalid input " + msg, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.http.util;
|
||||
|
||||
/**
|
||||
* This holds the constants related to HTTP event adapter.
|
||||
*/
|
||||
public final class HTTPEventAdapterConstants {
|
||||
|
||||
private HTTPEventAdapterConstants() {
|
||||
}
|
||||
|
||||
public static final String ADAPTER_TYPE_HTTP = "oauth-http";
|
||||
public static final String ADAPTER_USAGE_TIPS_PREFIX = "http.usage.tips_prefix";
|
||||
public static final String ADAPTER_USAGE_TIPS_MID1 = "http.usage.tips_mid1";
|
||||
public static final String ADAPTER_USAGE_TIPS_MID2 = "http.usage.tips_mid2";
|
||||
public static final String ADAPTER_USAGE_TIPS_MID3 = "http.usage.tips_mid3";
|
||||
public static final String ADAPTER_USAGE_TIPS_POSTFIX = "http.usage.tips_postfix";
|
||||
public static final int ADAPTER_MIN_THREAD_POOL_SIZE = 8;
|
||||
public static final int ADAPTER_MAX_THREAD_POOL_SIZE = 100;
|
||||
public static final int ADAPTER_EXECUTOR_JOB_QUEUE_SIZE = 10000;
|
||||
public static final long DEFAULT_KEEP_ALIVE_TIME_IN_MILLS = 20000;
|
||||
public static final String ENDPOINT_PREFIX = "/endpoints/";
|
||||
public static final String ENDPOINT_URL_SEPARATOR = "/";
|
||||
public static final String ENDPOINT_TENANT_KEY = "t";
|
||||
public static final String ADAPTER_MIN_THREAD_POOL_SIZE_NAME = "minThread";
|
||||
public static final String ADAPTER_MAX_THREAD_POOL_SIZE_NAME = "maxThread";
|
||||
public static final String ADAPTER_KEEP_ALIVE_TIME_NAME = "keepAliveTimeInMillis";
|
||||
public static final String ADAPTER_EXECUTOR_JOB_QUEUE_SIZE_NAME = "jobQueueSize";
|
||||
public static final String EXPOSED_TRANSPORTS = "transports";
|
||||
public static final String HTTPS = "https";
|
||||
public static final String HTTP = "http";
|
||||
public static final String LOCAL = "local";
|
||||
public static final String ALL = "all";
|
||||
public static final String CARBON_CONFIG_PORT_OFFSET_NODE = "Ports.Offset";
|
||||
public static final int DEFAULT_HTTP_PORT = 9763;
|
||||
public static final int DEFAULT_HTTPS_PORT = 9443;
|
||||
public static final String MAXIMUM_TOTAL_HTTP_CONNECTION = "maximumTotalHttpConnection";
|
||||
public static final String MAXIMUM_TOTAL_HTTP_CONNECTION_HINT = "maximumTotalHttpConnection.hint";
|
||||
public static final String MAXIMUM_HTTP_CONNECTION_PER_HOST = "maximumHttpConnectionPerHost";
|
||||
public static final String MAXIMUM_HTTP_CONNECTION_PER_HOST_HINT = "maximumHttpConnectionPerHost.hint";
|
||||
public static final String TOKEN_VALIDATION_ENDPOINT_URL = "tokenValidationEndpointUrl";
|
||||
public static final String TOKEN_VALIDATION_ENDPOINT_URL_HINT = "tokenValidationEndpointUrl.hint";
|
||||
public static final String USERNAME = "username";
|
||||
public static final String USERNAME_HINT = "username.hint";
|
||||
public static final String PASSWORD = "password";
|
||||
public static final String PASSWORD_HINT = "password.hint";
|
||||
public static final String DEFAULT_STRING = "default";
|
||||
public static final String MAX_HTTP_CONNECTION = "2";
|
||||
public static final String MAX_TOTAL_HTTP_CONNECTION = "100";
|
||||
public static final String TENANT_DOMAIN_TAG = "tenantDomain";
|
||||
public static final String USERNAME_TAG = "username";
|
||||
public static final String PAYLOAD_TAG = "payload";
|
||||
public static final String DEVICE_ID_JSON_PATH = "device_id_json_path";
|
||||
public static final String ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME = "contentValidation";
|
||||
public static final String ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME_HINT = "contentValidation.hint";
|
||||
public static final String ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS = "contentValidationParams";
|
||||
public static final String ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS_HINT = "contentValidationParams.hint";
|
||||
public static final String DEFAULT = "default";
|
||||
public static final String MQTT_CONTENT_VALIDATION_DEFAULT_PARAMETERS =
|
||||
"device_id_json_path:meta_deviceId";
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.internal;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterFactory;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.http.HTTPEventAdapterFactory;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.MQTTEventAdapterFactory;
|
||||
import org.wso2.carbon.user.core.service.RealmService;
|
||||
|
||||
/**
|
||||
* @scr.component component.name="input.iot.Mqtt.AdapterService.component" immediate="true"
|
||||
*/
|
||||
|
||||
/**
|
||||
* @scr.component name="org.wso2.carbon.event.input.adapter.extension.EventAdapterServiceComponent" immediate="true"
|
||||
* @scr.reference name="user.realmservice.default"
|
||||
* interface="org.wso2.carbon.user.core.service.RealmService" cardinality="1..1"
|
||||
* policy="dynamic" bind="setRealmService" unbind="unsetRealmService"
|
||||
* @scr.reference name="http.service" interface="org.osgi.service.http.HttpService"
|
||||
* cardinality="1..1" policy="dynamic" bind="setHttpService" unbind="unsetHttpService"
|
||||
*/
|
||||
public class EventAdapterServiceComponent {
|
||||
|
||||
private static final Log log = LogFactory.getLog(EventAdapterServiceComponent.class);
|
||||
|
||||
protected void activate(ComponentContext context) {
|
||||
try {
|
||||
InputEventAdapterFactory mqttEventAdapterFactory = new MQTTEventAdapterFactory();
|
||||
context.getBundleContext().registerService(InputEventAdapterFactory.class.getName(),
|
||||
mqttEventAdapterFactory, null);
|
||||
InputEventAdapterFactory httpEventEventAdapterFactory = new HTTPEventAdapterFactory();
|
||||
context.getBundleContext().registerService(InputEventAdapterFactory.class.getName(),
|
||||
httpEventEventAdapterFactory, null);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Successfully deployed the input IoT-MQTT adapter service");
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Can not create the input IoT-MQTT adapter service ", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setRealmService(RealmService realmService) {
|
||||
EventAdapterServiceDataHolder.registerRealmService(realmService);
|
||||
}
|
||||
|
||||
protected void unsetRealmService(RealmService realmService) {
|
||||
EventAdapterServiceDataHolder.registerRealmService(null);
|
||||
}
|
||||
|
||||
protected void setHttpService(HttpService httpService) {
|
||||
EventAdapterServiceDataHolder.registerHTTPService(httpService);
|
||||
}
|
||||
|
||||
protected void unsetHttpService(HttpService httpService) {
|
||||
EventAdapterServiceDataHolder.registerHTTPService(null);
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.internal;
|
||||
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.wso2.carbon.user.core.service.RealmService;
|
||||
|
||||
/**
|
||||
* common place to hold some OSGI service references.
|
||||
*/
|
||||
public final class EventAdapterServiceDataHolder {
|
||||
|
||||
private static RealmService realmService;
|
||||
private static HttpService httpService;
|
||||
|
||||
private EventAdapterServiceDataHolder() {
|
||||
}
|
||||
|
||||
public static void registerRealmService(
|
||||
RealmService realmService) {
|
||||
EventAdapterServiceDataHolder.realmService = realmService;
|
||||
}
|
||||
|
||||
public static RealmService getRealmService() {
|
||||
return realmService;
|
||||
}
|
||||
|
||||
public static void registerHTTPService(
|
||||
HttpService httpService) {
|
||||
EventAdapterServiceDataHolder.httpService = httpService;
|
||||
}
|
||||
|
||||
public static HttpService getHTTPService() {
|
||||
return httpService;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt;
|
||||
|
||||
/**
|
||||
* This holds the constants related to MQTT input adapter.
|
||||
*/
|
||||
public class Constants {
|
||||
public static final String EMPTY_STRING = "";
|
||||
public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer refresh_token";
|
||||
public static final String TOKEN_SCOPE = "production";
|
||||
public static final String APPLICATION_TYPE = "device";
|
||||
public static final String CLIENT_ID = "client_id";
|
||||
public static final String CLIENT_SECRET = "client_secret";
|
||||
public static final String CLIENT_NAME = "client_name";
|
||||
public static final String DEFAULT = "default";
|
||||
public static final String MQTT_CONTENT_VALIDATION_DEFAULT_PARAMETERS =
|
||||
"device_id_json_path:event.metaData.deviceId,device_id_topic_hierarchy_index:2";
|
||||
public static final String TOPIC = "topic";
|
||||
public static final String PAYLOAD = "payload";
|
||||
public static final String DEVICE_ID_JSON_PATH = "device_id_json_path";
|
||||
public static final String DEVICE_ID_TOPIC_HIERARCHY_INDEX = "device_id_topic_hierarchy_index";
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt;
|
||||
|
||||
import org.wso2.carbon.context.PrivilegedCarbonContext;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapter;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterConfiguration;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterListener;
|
||||
import org.wso2.carbon.event.input.adapter.core.exception.InputEventAdapterException;
|
||||
import org.wso2.carbon.event.input.adapter.core.exception.TestConnectionNotSupportedException;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.util.MQTTAdapterListener;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.util.MQTTBrokerConnectionConfiguration;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.util.MQTTEventAdapterConstants;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Input MQTTEventAdapter will be used to receive events with MQTT protocol using specified broker and topic.
|
||||
*/
|
||||
public class MQTTEventAdapter implements InputEventAdapter {
|
||||
|
||||
private final InputEventAdapterConfiguration eventAdapterConfiguration;
|
||||
private final Map<String, String> globalProperties;
|
||||
private InputEventAdapterListener eventAdapterListener;
|
||||
private final String id = UUID.randomUUID().toString();
|
||||
private MQTTAdapterListener mqttAdapterListener;
|
||||
private MQTTBrokerConnectionConfiguration mqttBrokerConnectionConfiguration;
|
||||
|
||||
|
||||
public MQTTEventAdapter(InputEventAdapterConfiguration eventAdapterConfiguration,
|
||||
Map<String, String> globalProperties) {
|
||||
this.eventAdapterConfiguration = eventAdapterConfiguration;
|
||||
this.globalProperties = globalProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(InputEventAdapterListener eventAdapterListener) throws InputEventAdapterException {
|
||||
this.eventAdapterListener = eventAdapterListener;
|
||||
try {
|
||||
int keepAlive;
|
||||
|
||||
//If global properties are available those will be assigned else constant values will be assigned
|
||||
if (globalProperties.get(MQTTEventAdapterConstants.ADAPTER_CONF_KEEP_ALIVE) != null) {
|
||||
keepAlive = Integer.parseInt((globalProperties.get(MQTTEventAdapterConstants.ADAPTER_CONF_KEEP_ALIVE)));
|
||||
} else {
|
||||
keepAlive = MQTTEventAdapterConstants.ADAPTER_CONF_DEFAULT_KEEP_ALIVE;
|
||||
}
|
||||
String contentValidationParams = eventAdapterConfiguration.getProperties().get(MQTTEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS);
|
||||
String params[] = contentValidationParams.split(",");
|
||||
Map<String, String> paramsMap = new HashMap<>();
|
||||
for (String param: params) {
|
||||
String paramsKeyAndValue[] = splitOnFirst(param, ':');
|
||||
if (paramsKeyAndValue.length != 2) {
|
||||
throw new InputEventAdapterException("Invalid parameters for content validation - " + param);
|
||||
}
|
||||
paramsMap.put(paramsKeyAndValue[0], paramsKeyAndValue[1]);
|
||||
}
|
||||
|
||||
mqttBrokerConnectionConfiguration = new MQTTBrokerConnectionConfiguration(
|
||||
eventAdapterConfiguration.getProperties().get(MQTTEventAdapterConstants.ADAPTER_CONF_URL),
|
||||
eventAdapterConfiguration.getProperties().get(MQTTEventAdapterConstants.ADAPTER_CONF_USERNAME),
|
||||
eventAdapterConfiguration.getProperties().get(MQTTEventAdapterConstants.ADAPTER_CONF_SCOPES),
|
||||
eventAdapterConfiguration.getProperties().get(MQTTEventAdapterConstants.ADAPTER_CONF_DCR_URL),
|
||||
eventAdapterConfiguration.getProperties().get(MQTTEventAdapterConstants.ADAPTER_CONF_CLEAN_SESSION),
|
||||
keepAlive,
|
||||
eventAdapterConfiguration.getProperties().get(MQTTEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME),
|
||||
paramsMap
|
||||
);
|
||||
|
||||
|
||||
mqttAdapterListener = new MQTTAdapterListener(mqttBrokerConnectionConfiguration,
|
||||
eventAdapterConfiguration.getProperties().get(MQTTEventAdapterConstants.ADAPTER_MESSAGE_TOPIC),
|
||||
eventAdapterConfiguration.getProperties().get(MQTTEventAdapterConstants.ADAPTER_CONF_CLIENTID),
|
||||
eventAdapterListener, PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId());
|
||||
|
||||
} catch (Throwable t) {
|
||||
throw new InputEventAdapterException(t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] splitOnFirst(String str, char c) {
|
||||
int idx = str.indexOf(c);
|
||||
String head = str.substring(0, idx);
|
||||
String tail = str.substring(idx + 1);
|
||||
return new String[] { head, tail} ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testConnect() throws TestConnectionNotSupportedException {
|
||||
throw new TestConnectionNotSupportedException("not-supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
mqttAdapterListener.createConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
//when mqtt and this feature both together then this method becomes a blocking method, Therefore
|
||||
// have used a thread to skip it.
|
||||
try {
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
if (mqttAdapterListener != null) {
|
||||
mqttAdapterListener.stopListener(eventAdapterConfiguration.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
thread.join(2000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof MQTTEventAdapter)) return false;
|
||||
|
||||
MQTTEventAdapter that = (MQTTEventAdapter) o;
|
||||
|
||||
if (!id.equals(that.id)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEventDuplicatedInCluster() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPolling() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt;
|
||||
|
||||
import org.wso2.carbon.event.input.adapter.core.*;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.util.MQTTEventAdapterConstants;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The mqtt event adapter factory class to create a mqtt input adapter
|
||||
*/
|
||||
public class MQTTEventAdapterFactory extends InputEventAdapterFactory {
|
||||
|
||||
private ResourceBundle resourceBundle = ResourceBundle.getBundle
|
||||
("org.wso2.carbon.event.input.adapter.extensions.mqtt.i18n.Resources", Locale.getDefault());
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return MQTTEventAdapterConstants.ADAPTER_TYPE_MQTT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSupportedMessageFormats() {
|
||||
List<String> supportInputMessageTypes = new ArrayList<String>();
|
||||
supportInputMessageTypes.add(MessageType.JSON);
|
||||
return supportInputMessageTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Property> getPropertyList() {
|
||||
List<Property> propertyList = new ArrayList<Property>();
|
||||
|
||||
// set topic
|
||||
Property topicProperty = new Property(MQTTEventAdapterConstants.ADAPTER_MESSAGE_TOPIC);
|
||||
topicProperty.setDisplayName(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_MESSAGE_TOPIC));
|
||||
topicProperty.setRequired(true);
|
||||
topicProperty.setHint(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_MESSAGE_TOPIC_HINT));
|
||||
propertyList.add(topicProperty);
|
||||
|
||||
//Broker Url
|
||||
Property brokerUrl = new Property(MQTTEventAdapterConstants.ADAPTER_CONF_URL);
|
||||
brokerUrl.setDisplayName(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_URL));
|
||||
brokerUrl.setRequired(true);
|
||||
brokerUrl.setHint(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_URL_HINT));
|
||||
propertyList.add(brokerUrl);
|
||||
|
||||
//DCR endpoint details
|
||||
Property dcrUrl = new Property(MQTTEventAdapterConstants.ADAPTER_CONF_DCR_URL);
|
||||
dcrUrl.setDisplayName(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_DCR_URL));
|
||||
dcrUrl.setRequired(false);
|
||||
dcrUrl.setHint(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_DCR_URL_HINT));
|
||||
propertyList.add(dcrUrl);
|
||||
|
||||
//Content Validator details
|
||||
Property contentValidator = new Property(MQTTEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME);
|
||||
contentValidator.setDisplayName(
|
||||
resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME));
|
||||
contentValidator.setRequired(false);
|
||||
contentValidator.setHint(
|
||||
resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME_HINT));
|
||||
contentValidator.setDefaultValue(Constants.DEFAULT);
|
||||
propertyList.add(contentValidator);
|
||||
|
||||
//Content Validator Params details
|
||||
Property contentValidatorParams = new Property(MQTTEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS);
|
||||
contentValidatorParams.setDisplayName(
|
||||
resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS));
|
||||
contentValidatorParams.setRequired(false);
|
||||
contentValidatorParams.setHint(
|
||||
resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS_HINT));
|
||||
contentValidatorParams.setDefaultValue(Constants.MQTT_CONTENT_VALIDATION_DEFAULT_PARAMETERS);
|
||||
propertyList.add(contentValidatorParams);
|
||||
|
||||
//Broker Username
|
||||
Property userName = new Property(MQTTEventAdapterConstants.ADAPTER_CONF_USERNAME);
|
||||
userName.setDisplayName(
|
||||
resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_USERNAME));
|
||||
userName.setRequired(false);
|
||||
userName.setHint(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_USERNAME_HINT));
|
||||
propertyList.add(userName);
|
||||
|
||||
//Broker Required Scopes.
|
||||
Property scopes = new Property(MQTTEventAdapterConstants.ADAPTER_CONF_SCOPES);
|
||||
scopes.setDisplayName(
|
||||
resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_SCOPES));
|
||||
scopes.setRequired(false);
|
||||
scopes.setHint(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_SCOPES_HINT));
|
||||
propertyList.add(scopes);
|
||||
|
||||
//Broker clear session
|
||||
Property clearSession = new Property(MQTTEventAdapterConstants.ADAPTER_CONF_CLEAN_SESSION);
|
||||
clearSession.setDisplayName(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_CLEAN_SESSION));
|
||||
clearSession.setRequired(false);
|
||||
clearSession.setOptions(new String[]{"true", "false"});
|
||||
clearSession.setDefaultValue("true");
|
||||
clearSession.setHint(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_CLEAN_SESSION_HINT));
|
||||
propertyList.add(clearSession);
|
||||
|
||||
// set clientId
|
||||
Property clientId = new Property(MQTTEventAdapterConstants.ADAPTER_CONF_CLIENTID);
|
||||
clientId.setDisplayName(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_CLIENTID));
|
||||
clientId.setRequired(false);
|
||||
clientId.setHint(resourceBundle.getString(MQTTEventAdapterConstants.ADAPTER_CONF_CLIENTID_HINT));
|
||||
propertyList.add(clientId);
|
||||
|
||||
return propertyList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageTips() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputEventAdapter createEventAdapter(InputEventAdapterConfiguration eventAdapterConfiguration,
|
||||
Map<String, String> globalProperties) {
|
||||
return new MQTTEventAdapter(eventAdapterConfiguration, globalProperties);
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt.exception;
|
||||
|
||||
/**
|
||||
* This exception will thrown when content validator is failed to intialiaze.
|
||||
*/
|
||||
public class MQTTContentValidatorInitializationException extends RuntimeException {
|
||||
private String errMessage;
|
||||
|
||||
public MQTTContentValidatorInitializationException(String msg, Exception nestedEx) {
|
||||
super(msg, nestedEx);
|
||||
setErrorMessage(msg);
|
||||
}
|
||||
|
||||
public MQTTContentValidatorInitializationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
setErrorMessage(message);
|
||||
}
|
||||
|
||||
public MQTTContentValidatorInitializationException(String msg) {
|
||||
super(msg);
|
||||
setErrorMessage(msg);
|
||||
}
|
||||
|
||||
public MQTTContentValidatorInitializationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MQTTContentValidatorInitializationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errMessage) {
|
||||
this.errMessage = errMessage;
|
||||
}
|
||||
}
|
@ -1,284 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt.util;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.eclipse.paho.client.mqttv3.*;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.wso2.carbon.context.PrivilegedCarbonContext;
|
||||
import org.wso2.carbon.core.ServerStatus;
|
||||
import org.wso2.carbon.event.input.adapter.core.InputEventAdapterListener;
|
||||
import org.wso2.carbon.event.input.adapter.core.exception.InputEventAdapterRuntimeException;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.ContentInfo;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.ContentValidator;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.Constants;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.exception.MQTTContentValidatorInitializationException;
|
||||
import org.wso2.carbon.identity.jwt.client.extension.dto.AccessTokenInfo;
|
||||
import org.wso2.carbon.identity.jwt.client.extension.exception.JWTClientException;
|
||||
import org.wso2.carbon.identity.jwt.client.extension.service.JWTClientManagerService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
|
||||
public class MQTTAdapterListener implements MqttCallback, Runnable {
|
||||
private static final Log log = LogFactory.getLog(MQTTAdapterListener.class);
|
||||
|
||||
private MqttClient mqttClient;
|
||||
private MqttConnectOptions connectionOptions;
|
||||
private boolean cleanSession;
|
||||
private int keepAlive;
|
||||
|
||||
private MQTTBrokerConnectionConfiguration mqttBrokerConnectionConfiguration;
|
||||
private String mqttClientId;
|
||||
private String topic;
|
||||
private int tenantId;
|
||||
private boolean connectionSucceeded = false;
|
||||
ContentValidator contentValidator;
|
||||
Map<String, String> contentValidationParams;
|
||||
|
||||
private InputEventAdapterListener eventAdapterListener = null;
|
||||
|
||||
|
||||
public MQTTAdapterListener(MQTTBrokerConnectionConfiguration mqttBrokerConnectionConfiguration,
|
||||
String topic, String mqttClientId,
|
||||
InputEventAdapterListener inputEventAdapterListener, int tenantId) {
|
||||
|
||||
if(mqttClientId == null || mqttClientId.trim().isEmpty()){
|
||||
mqttClientId = MqttClient.generateClientId();
|
||||
}
|
||||
|
||||
this.mqttClientId = mqttClientId;
|
||||
this.mqttBrokerConnectionConfiguration = mqttBrokerConnectionConfiguration;
|
||||
this.cleanSession = mqttBrokerConnectionConfiguration.isCleanSession();
|
||||
this.keepAlive = mqttBrokerConnectionConfiguration.getKeepAlive();
|
||||
this.topic = topic;
|
||||
this.eventAdapterListener = inputEventAdapterListener;
|
||||
this.tenantId = tenantId;
|
||||
|
||||
//SORTING messages until the server fetches them
|
||||
String temp_directory = System.getProperty("java.io.tmpdir");
|
||||
MqttDefaultFilePersistence dataStore = new MqttDefaultFilePersistence(temp_directory);
|
||||
|
||||
|
||||
try {
|
||||
// Construct the connection options object that contains connection parameters
|
||||
// such as cleanSession and LWT
|
||||
connectionOptions = new MqttConnectOptions();
|
||||
connectionOptions.setCleanSession(cleanSession);
|
||||
connectionOptions.setKeepAliveInterval(keepAlive);
|
||||
|
||||
// Construct an MQTT blocking mode client
|
||||
mqttClient = new MqttClient(this.mqttBrokerConnectionConfiguration.getBrokerUrl(), this.mqttClientId,
|
||||
dataStore);
|
||||
|
||||
// Set this wrapper as the callback handler
|
||||
mqttClient.setCallback(this);
|
||||
String contentValidatorClassName = this.mqttBrokerConnectionConfiguration.getContentValidatorClassName();
|
||||
|
||||
if (contentValidatorClassName != null && contentValidatorClassName.equals(Constants.DEFAULT)) {
|
||||
contentValidator = new MQTTContentValidator();
|
||||
} else if (contentValidatorClassName != null && !contentValidatorClassName.isEmpty()) {
|
||||
try {
|
||||
Class<? extends ContentValidator> contentValidatorClass = Class.forName(contentValidatorClassName)
|
||||
.asSubclass(ContentValidator.class);
|
||||
contentValidator = contentValidatorClass.newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new MQTTContentValidatorInitializationException(
|
||||
"Unable to find the class authorizer: " + contentValidatorClassName, e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new MQTTContentValidatorInitializationException(
|
||||
"Unable to create an instance of :" + contentValidatorClassName, e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new MQTTContentValidatorInitializationException("Access of the instance in not allowed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
contentValidationParams = mqttBrokerConnectionConfiguration.getContentValidatorParams();
|
||||
|
||||
} catch (MqttException e) {
|
||||
log.error("Exception occurred while subscribing to MQTT broker at "
|
||||
+ mqttBrokerConnectionConfiguration.getBrokerUrl());
|
||||
throw new InputEventAdapterRuntimeException(e);
|
||||
} catch (Throwable e) {
|
||||
log.error("Exception occurred while subscribing to MQTT broker at "
|
||||
+ mqttBrokerConnectionConfiguration.getBrokerUrl());
|
||||
throw new InputEventAdapterRuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void startListener() throws MqttException {
|
||||
if (this.mqttBrokerConnectionConfiguration.getBrokerUsername() != null && this.mqttBrokerConnectionConfiguration.getDcrUrl() != null) {
|
||||
String username = this.mqttBrokerConnectionConfiguration.getBrokerUsername();
|
||||
String dcrUrlString = this.mqttBrokerConnectionConfiguration.getDcrUrl();
|
||||
String scopes = this.mqttBrokerConnectionConfiguration.getBrokerScopes();
|
||||
//getJWT Client Parameters.
|
||||
if (dcrUrlString != null && !dcrUrlString.isEmpty()) {
|
||||
PrivilegedCarbonContext.startTenantFlow();
|
||||
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantId(tenantId, true);
|
||||
PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(username);
|
||||
try {
|
||||
URL dcrUrl = new URL(dcrUrlString);
|
||||
HttpClient httpClient = MQTTUtil.getHttpClient(dcrUrl.getProtocol());
|
||||
HttpPost postMethod = new HttpPost(dcrUrlString);
|
||||
RegistrationProfile registrationProfile = new RegistrationProfile();
|
||||
registrationProfile.setCallbackUrl(Constants.EMPTY_STRING);
|
||||
registrationProfile.setGrantType(Constants.GRANT_TYPE);
|
||||
registrationProfile.setOwner(username);
|
||||
registrationProfile.setTokenScope(Constants.TOKEN_SCOPE);
|
||||
registrationProfile.setApplicationType(Constants.APPLICATION_TYPE);
|
||||
registrationProfile.setClientName(username + "_" + tenantId);
|
||||
String jsonString = registrationProfile.toJSON();
|
||||
StringEntity requestEntity = new StringEntity(jsonString, ContentType.APPLICATION_JSON);
|
||||
postMethod.setEntity(requestEntity);
|
||||
HttpResponse httpResponse = httpClient.execute(postMethod);
|
||||
String response = MQTTUtil.getResponseString(httpResponse);
|
||||
try {
|
||||
JSONParser jsonParser = new JSONParser();
|
||||
JSONObject jsonPayload = (JSONObject) jsonParser.parse(response);
|
||||
String clientId = (String) jsonPayload.get(Constants.CLIENT_ID);
|
||||
String clientSecret = (String) jsonPayload.get(Constants.CLIENT_SECRET);
|
||||
JWTClientManagerService jwtClientManagerService = MQTTUtil.getJWTClientManagerService();
|
||||
AccessTokenInfo accessTokenInfo = jwtClientManagerService.getJWTClient().getAccessToken(
|
||||
clientId, clientSecret, username, scopes);
|
||||
connectionOptions.setUserName(accessTokenInfo.getAccessToken());
|
||||
} catch (ParseException e) {
|
||||
String msg = "error occurred while parsing client credential payload";
|
||||
log.error(msg, e);
|
||||
} catch (JWTClientException e) {
|
||||
String msg = "error occurred while parsing the response from JWT Client";
|
||||
log.error(msg, e);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
log.error("Invalid dcrUrl : " + dcrUrlString);
|
||||
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | IOException e) {
|
||||
log.error("Failed to create an https connection.", e);
|
||||
} finally {
|
||||
PrivilegedCarbonContext.endTenantFlow();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Connect to the MQTT server
|
||||
mqttClient.connect(connectionOptions);
|
||||
|
||||
// Subscribe to the requested topic
|
||||
// The QoS specified is the maximum level that messages will be sent to the client at.
|
||||
// For instance if QoS 1 is specified, any messages originally published at QoS 2 will
|
||||
// be downgraded to 1 when delivering to the client but messages published at 1 and 0
|
||||
// will be received at the same level they were published at.
|
||||
mqttClient.subscribe(topic);
|
||||
}
|
||||
|
||||
public void stopListener(String adapterName) {
|
||||
if (connectionSucceeded) {
|
||||
try {
|
||||
// Un-subscribe accordingly and disconnect from the MQTT server.
|
||||
if (!ServerStatus.getCurrentStatus().equals(ServerStatus.STATUS_SHUTTING_DOWN) || cleanSession) {
|
||||
mqttClient.unsubscribe(topic);
|
||||
}
|
||||
mqttClient.disconnect(3000);
|
||||
} catch (MqttException e) {
|
||||
log.error("Can not unsubscribe from the destination " + topic
|
||||
+ " with the event adapter " + adapterName, e);
|
||||
}
|
||||
}
|
||||
//This is to stop all running reconnection threads
|
||||
connectionSucceeded = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionLost(Throwable throwable) {
|
||||
log.warn("MQTT connection not reachable " + throwable);
|
||||
connectionSucceeded = false;
|
||||
new Thread(this).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
|
||||
try {
|
||||
String msgText = mqttMessage.toString();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(msgText);
|
||||
}
|
||||
PrivilegedCarbonContext.startTenantFlow();
|
||||
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantId(tenantId);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Event received in MQTT Event Adapter - " + msgText);
|
||||
}
|
||||
|
||||
if (contentValidator != null) {
|
||||
ContentInfo contentInfo;
|
||||
synchronized (contentValidationParams) {
|
||||
contentValidationParams.put(Constants.TOPIC, topic);
|
||||
contentInfo = contentValidator.validate(msgText,contentValidationParams);
|
||||
contentValidationParams.remove(Constants.TOPIC);
|
||||
contentValidationParams.remove(Constants.PAYLOAD);
|
||||
}
|
||||
if (contentInfo != null && contentInfo.isValidContent()) {
|
||||
eventAdapterListener.onEvent(contentInfo.getMsgText());
|
||||
}
|
||||
} else {
|
||||
eventAdapterListener.onEvent(msgText);
|
||||
}
|
||||
} finally {
|
||||
PrivilegedCarbonContext.endTenantFlow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!connectionSucceeded) {
|
||||
try {
|
||||
MQTTEventAdapterConstants.initialReconnectDuration = MQTTEventAdapterConstants.initialReconnectDuration
|
||||
* MQTTEventAdapterConstants.reconnectionProgressionFactor;
|
||||
Thread.sleep(MQTTEventAdapterConstants.initialReconnectDuration);
|
||||
startListener();
|
||||
connectionSucceeded = true;
|
||||
log.info("MQTT Connection successful");
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Interruption occurred while waiting for reconnection", e);
|
||||
} catch (MqttException e) {
|
||||
log.error("MQTT Exception occurred when starting listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void createConnection() {
|
||||
new Thread(this).start();
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt.util;
|
||||
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.Constants;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.util.PropertyUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This holds the configurations related to MQTT Broker.
|
||||
*/
|
||||
public class MQTTBrokerConnectionConfiguration {
|
||||
|
||||
private String brokerUsername = null;
|
||||
private String brokerScopes = null;
|
||||
private boolean cleanSession = true;
|
||||
private int keepAlive;
|
||||
private String brokerUrl;
|
||||
private String dcrUrl;
|
||||
private String contentValidatorClassName;
|
||||
private Map<String, String> contentValidatorParams;
|
||||
|
||||
public String getBrokerScopes() {
|
||||
return brokerScopes;
|
||||
}
|
||||
|
||||
public void setBrokerScopes(String brokerScopes) {
|
||||
this.brokerScopes = brokerScopes;
|
||||
}
|
||||
|
||||
public String getBrokerUsername() {
|
||||
return brokerUsername;
|
||||
}
|
||||
|
||||
public void setBrokerUsername(String brokerUsername) {
|
||||
this.brokerUsername = brokerUsername;
|
||||
}
|
||||
|
||||
public void setCleanSession(boolean cleanSession) {
|
||||
this.cleanSession = cleanSession;
|
||||
}
|
||||
|
||||
|
||||
public boolean isCleanSession() {
|
||||
return cleanSession;
|
||||
}
|
||||
|
||||
public String getBrokerUrl() {
|
||||
return brokerUrl;
|
||||
}
|
||||
|
||||
public void setBrokerUrl(String brokerUrl) {
|
||||
this.brokerUrl = brokerUrl;
|
||||
}
|
||||
|
||||
public String getDcrUrl() {
|
||||
return dcrUrl;
|
||||
}
|
||||
|
||||
public void setDcrUrl(String dcrUrl) {
|
||||
this.dcrUrl = dcrUrl;
|
||||
}
|
||||
|
||||
public int getKeepAlive() {
|
||||
return keepAlive;
|
||||
}
|
||||
|
||||
public void setKeepAlive(int keepAlive) {
|
||||
this.keepAlive = keepAlive;
|
||||
}
|
||||
|
||||
public String getContentValidatorClassName() {
|
||||
return contentValidatorClassName;
|
||||
}
|
||||
|
||||
public void setContentValidatorClassName(String contentValidatorClassName) {
|
||||
this.contentValidatorClassName = contentValidatorClassName;
|
||||
}
|
||||
|
||||
public Map<String, String> getContentValidatorParams() {
|
||||
return contentValidatorParams;
|
||||
}
|
||||
|
||||
public void setContentValidatorParams(Map<String, String> contentValidatorParams) {
|
||||
this.contentValidatorParams = contentValidatorParams;
|
||||
}
|
||||
|
||||
public MQTTBrokerConnectionConfiguration(String brokerUrl, String brokerUsername, String brokerScopes,
|
||||
String dcrUrl, String cleanSession, int keepAlive,
|
||||
String contentValidatorClassName, Map<String, String> contentValidatorParams) {
|
||||
this.brokerUsername = brokerUsername;
|
||||
this.brokerScopes = brokerScopes;
|
||||
if (brokerScopes == null) {
|
||||
this.brokerScopes = Constants.EMPTY_STRING;
|
||||
}
|
||||
this.brokerUrl = PropertyUtils.replaceMqttProperty(brokerUrl);
|
||||
this.dcrUrl = PropertyUtils.replaceMqttProperty(dcrUrl);
|
||||
this.contentValidatorClassName = contentValidatorClassName;
|
||||
if (cleanSession != null) {
|
||||
this.cleanSession = Boolean.parseBoolean(cleanSession);
|
||||
}
|
||||
this.keepAlive = keepAlive;
|
||||
if (contentValidatorParams != null) {
|
||||
this.contentValidatorParams = contentValidatorParams;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt.util;
|
||||
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.ContentInfo;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.ContentValidator;
|
||||
import org.wso2.carbon.event.input.adapter.extensions.mqtt.Constants;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class MQTTContentValidator implements ContentValidator {
|
||||
private static String JSON_ARRAY_START_CHAR = "[";
|
||||
private static final Log log = LogFactory.getLog(MQTTContentValidator.class);
|
||||
|
||||
@Override
|
||||
public ContentInfo validate(String msgPayload, Map<String, String> params) {
|
||||
String topic = params.get(Constants.TOPIC);
|
||||
String topics[] = topic.split("/");
|
||||
|
||||
String msg = msgPayload;
|
||||
String deviceIdJsonPath = params.get(Constants.DEVICE_ID_JSON_PATH);
|
||||
String deviceIdInTopicHierarchyLevel = params.get(Constants.DEVICE_ID_TOPIC_HIERARCHY_INDEX);
|
||||
int deviceIdInTopicHierarchyLevelIndex = 0;
|
||||
if (deviceIdInTopicHierarchyLevel != null && !deviceIdInTopicHierarchyLevel.isEmpty()) {
|
||||
deviceIdInTopicHierarchyLevelIndex = Integer.parseInt(deviceIdInTopicHierarchyLevel);
|
||||
}
|
||||
String deviceIdFromTopic = topics[deviceIdInTopicHierarchyLevelIndex];
|
||||
boolean status;
|
||||
if (msg.startsWith(JSON_ARRAY_START_CHAR)) {
|
||||
status = processMultipleEvents(msg, deviceIdFromTopic, deviceIdJsonPath);
|
||||
} else {
|
||||
status = processSingleEvent(msg, deviceIdFromTopic, deviceIdJsonPath);
|
||||
}
|
||||
return new ContentInfo(status, msg);
|
||||
}
|
||||
|
||||
private boolean processSingleEvent(String msg, String deviceIdFromTopic, String deviceIdJsonPath) {
|
||||
Object res = JsonPath.read(msg, deviceIdJsonPath);
|
||||
String deviceIdFromContent = (res != null) ? res.toString() : "";
|
||||
if (deviceIdFromContent.equals(deviceIdFromTopic)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean processMultipleEvents(String msg, String deviceIdFromTopic, String deviceIdJsonPath) {
|
||||
try {
|
||||
JSONParser jsonParser = new JSONParser();
|
||||
JSONArray jsonArray = (JSONArray) jsonParser.parse(msg);
|
||||
boolean status = false;
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
status = processSingleEvent(jsonArray.get(i).toString(), deviceIdFromTopic, deviceIdJsonPath);
|
||||
if (!status) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
} catch (ParseException e) {
|
||||
log.error("Invalid input " + msg, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt.util;
|
||||
|
||||
|
||||
/**
|
||||
* This holds the constants related to mqtt event adapter.
|
||||
*/
|
||||
public class MQTTEventAdapterConstants {
|
||||
|
||||
public static final String ADAPTER_TYPE_MQTT = "oauth-mqtt";
|
||||
public static final String ADAPTER_CONF_URL = "url";
|
||||
public static final String ADAPTER_CONF_USERNAME = "username";
|
||||
public static final String ADAPTER_CONF_USERNAME_HINT = "username.hint";
|
||||
public static final String ADAPTER_CONF_SCOPES = "scopes";
|
||||
public static final String ADAPTER_CONF_SCOPES_HINT = "scopes.hint";
|
||||
public static final String ADAPTER_CONF_URL_HINT = "url.hint";
|
||||
public static final String ADAPTER_CONF_DCR_URL = "dcrUrl";
|
||||
public static final String ADAPTER_CONF_DCR_URL_HINT = "dcrUrl.hint";
|
||||
public static final String ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME = "contentValidation";
|
||||
public static final String ADAPTER_CONF_CONTENT_VALIDATOR_CLASSNAME_HINT = "contentValidation.hint";
|
||||
public static final String ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS = "contentValidationParams";
|
||||
public static final String ADAPTER_CONF_CONTENT_VALIDATOR_PARAMS_HINT = "contentValidationParams.hint";
|
||||
public static final String ADAPTER_MESSAGE_TOPIC = "topic";
|
||||
public static final String ADAPTER_MESSAGE_TOPIC_HINT = "topic.hint";
|
||||
public static final String ADAPTER_CONF_CLIENTID = "clientId";
|
||||
public static final String ADAPTER_CONF_CLIENTID_HINT = "clientId.hint";
|
||||
public static final String ADAPTER_CONF_CLEAN_SESSION = "cleanSession";
|
||||
public static final String ADAPTER_CONF_CLEAN_SESSION_HINT = "cleanSession.hint";
|
||||
public static final String ADAPTER_CONF_KEEP_ALIVE = "keepAlive";
|
||||
public static final int ADAPTER_CONF_DEFAULT_KEEP_ALIVE = 60000;
|
||||
|
||||
public static int initialReconnectDuration = 10000;
|
||||
public static final int reconnectionProgressionFactor = 2;
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt.util;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLContextBuilder;
|
||||
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.wso2.carbon.context.PrivilegedCarbonContext;
|
||||
import org.wso2.carbon.identity.jwt.client.extension.service.JWTClientManagerService;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* This is the utility class that is used for MQTT input adapater.
|
||||
*/
|
||||
public class MQTTUtil {
|
||||
private static final String HTTPS_PROTOCOL = "https";
|
||||
private static final Log log = LogFactory.getLog(MQTTUtil.class);
|
||||
/**
|
||||
* Return a http client instance
|
||||
*
|
||||
* @param protocol- service endpoint protocol http/https
|
||||
* @return
|
||||
*/
|
||||
public static HttpClient getHttpClient(String protocol)
|
||||
throws IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
|
||||
HttpClient httpclient;
|
||||
if (HTTPS_PROTOCOL.equals(protocol)) {
|
||||
SSLContextBuilder builder = new SSLContextBuilder();
|
||||
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
|
||||
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());
|
||||
httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
|
||||
} else {
|
||||
httpclient = HttpClients.createDefault();
|
||||
}
|
||||
return httpclient;
|
||||
}
|
||||
|
||||
public static String getResponseString(HttpResponse httpResponse) throws IOException {
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));
|
||||
String readLine;
|
||||
String response = "";
|
||||
while (((readLine = br.readLine()) != null)) {
|
||||
response += readLine;
|
||||
}
|
||||
return response;
|
||||
} finally {
|
||||
EntityUtils.consumeQuietly(httpResponse.getEntity());
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error while closing the connection! " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static JWTClientManagerService getJWTClientManagerService() {
|
||||
PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext();
|
||||
JWTClientManagerService jwtClientManagerService =
|
||||
(JWTClientManagerService) ctx.getOSGiService(JWTClientManagerService.class, null);
|
||||
if (jwtClientManagerService == null) {
|
||||
String msg = "JWT management service has not initialized.";
|
||||
log.error(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
return jwtClientManagerService;
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
package org.wso2.carbon.event.input.adapter.extensions.mqtt.util;
|
||||
|
||||
/**
|
||||
* This class represents the data that are required to register
|
||||
* the oauth application.
|
||||
*/
|
||||
public class RegistrationProfile {
|
||||
|
||||
private String callbackUrl;
|
||||
private String clientName;
|
||||
private String tokenScope;
|
||||
private String owner;
|
||||
private String grantType;
|
||||
private String applicationType;
|
||||
|
||||
private static final String TAG = RegistrationProfile.class.getSimpleName();
|
||||
|
||||
public String getCallbackUrl() {
|
||||
return callbackUrl;
|
||||
}
|
||||
|
||||
public void setCallbackUrl(String callBackUrl) {
|
||||
this.callbackUrl = callBackUrl;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return clientName;
|
||||
}
|
||||
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
public String getTokenScope() {
|
||||
return tokenScope;
|
||||
}
|
||||
|
||||
public void setTokenScope(String tokenScope) {
|
||||
this.tokenScope = tokenScope;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public String getGrantType() {
|
||||
return grantType;
|
||||
}
|
||||
|
||||
public void setGrantType(String grantType) {
|
||||
this.grantType = grantType;
|
||||
}
|
||||
|
||||
public String getApplicationType() {
|
||||
return applicationType;
|
||||
}
|
||||
|
||||
public void setApplicationType(String applicationType) {
|
||||
this.applicationType = applicationType;
|
||||
}
|
||||
|
||||
public String toJSON() {
|
||||
String jsonString =
|
||||
"{\"callbackUrl\": \"" + callbackUrl + "\",\"clientName\": \"" + clientName + "\", \"tokenScope\": " +
|
||||
"\"" + tokenScope + "\", \"owner\": \"" + owner + "\"," + "\"grantType\": \"" + grantType +
|
||||
"\", \"saasApp\" :false }\n";
|
||||
return jsonString;
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.event.input.adapter.extensions.util;
|
||||
|
||||
import org.wso2.carbon.base.ServerConfiguration;
|
||||
import org.wso2.carbon.core.util.Utils;
|
||||
|
||||
public class PropertyUtils {
|
||||
private static final String MQTT_PORT = "\\$\\{mqtt.broker.port\\}";
|
||||
private static final String MQTT_BROKER_HOST = "\\$\\{mqtt.broker.host\\}";
|
||||
private static final String CARBON_CONFIG_PORT_OFFSET = "Ports.Offset";
|
||||
private static final String DEFAULT_CARBON_SERVER_HOST_PROPERTY = "server.host";
|
||||
private static final int CARBON_DEFAULT_PORT_OFFSET = 0;
|
||||
private static final int DEFAULT_MQTT_PORT = 1883;
|
||||
|
||||
//This method is only used if the mb features are within DAS.
|
||||
public static String replaceMqttProperty (String urlWithPlaceholders) {
|
||||
urlWithPlaceholders = Utils.replaceSystemProperty(urlWithPlaceholders);
|
||||
urlWithPlaceholders = urlWithPlaceholders.replaceAll(MQTT_PORT, "" + (DEFAULT_MQTT_PORT + getPortOffset()));
|
||||
urlWithPlaceholders = urlWithPlaceholders.replaceAll(MQTT_BROKER_HOST, System.getProperty(DEFAULT_CARBON_SERVER_HOST_PROPERTY,
|
||||
"localhost"));
|
||||
return urlWithPlaceholders;
|
||||
}
|
||||
|
||||
private static int getPortOffset() {
|
||||
ServerConfiguration carbonConfig = ServerConfiguration.getInstance();
|
||||
String portOffset = System.getProperty("portOffset",
|
||||
carbonConfig.getFirstProperty(CARBON_CONFIG_PORT_OFFSET));
|
||||
try {
|
||||
if ((portOffset != null)) {
|
||||
return Integer.parseInt(portOffset.trim());
|
||||
} else {
|
||||
return CARBON_DEFAULT_PORT_OFFSET;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return CARBON_DEFAULT_PORT_OFFSET;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
#
|
||||
# WSO2 Inc. licenses this file to you under the Apache License,
|
||||
# Version 2.0 (the "License"); you may not use this file except
|
||||
# in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
transports=Transport(s)
|
||||
http.usage.tips_prefix=Following url formats are used to receive events</br>For super tenants:</br> <i>http://localhost:
|
||||
http.usage.tips_mid1=/endpoints/<event_receiver_name></i></br> <i>https://localhost:
|
||||
http.usage.tips_mid2=/endpoints/<event_receiver_name></i></br></br>For other tenants:</br> <i>http://localhost:
|
||||
http.usage.tips_mid3=/endpoints/t/<tenant_domain>/<event_receiver_name></i></br> <i>https://localhost:
|
||||
http.usage.tips_postfix=/endpoints/t/<tenant_domain>/<event_receiver_name></i>
|
||||
tokenValidationEndpointUrl=tokenEndpointUrl
|
||||
tokenValidationEndpointUrl.hint=OAUTH Token Validation Endpoint
|
||||
username=username
|
||||
username.hint=username of the user to connect to the admin services
|
||||
password=password
|
||||
password.hint=password of the user to connect to the admin services.
|
||||
maximumTotalHttpConnection=maximumTotalHttpConnection
|
||||
maximumTotalHttpConnection.hint=Maximum Total connection to be made with the endpoint
|
||||
maximumHttpConnectionPerHost=maximumHttpConnectionPerHost
|
||||
maximumHttpConnectionPerHost.hint=Maximum Http connection per host.
|
||||
contentValidation=contentValidation
|
||||
contentValidation.hint=Class Name of the content Validation or 'default' to set default class, required to implement (if required)
|
||||
contentValidationParams=contentValidationParams
|
||||
contentValidationParams.hint=ContentValidationParams, comma seperated. (if required)
|
@ -1,38 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
#
|
||||
# WSO2 Inc. licenses this file to you under the Apache License,
|
||||
# Version 2.0 (the "License"); you may not use this file except
|
||||
# in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
topic=Topic
|
||||
topic.hint=Topic subscribed
|
||||
clientId=Client Id
|
||||
clientId.hint=client identifier is used by the server to identify a client when it reconnects, It used for durable subscriptions or reliable delivery of messages is required.
|
||||
url=Broker Url
|
||||
username=Username
|
||||
username.hint=Username of the broker (if required)
|
||||
scopes=Scopes
|
||||
scopes.hint=Scopes required to connect to broker (if required)
|
||||
dcrUrl=dcrUrl
|
||||
dcrUrl.hint=dynamic client registration endpoint URL to create application (if required) eg: https://localhost:9443/dynamic-client-web/register
|
||||
contentValidation=contentValidation
|
||||
contentValidation.hint=Class Name of the content Validation or 'default' to set default class, required to implement (if required)
|
||||
contentValidationParams=contentValidationParams
|
||||
contentValidationParams.hint=ContentValidationParams, comma seperated. (if required)
|
||||
url.hint=MQTT broker url tcp://localhost:1883
|
||||
cleanSession=Clean Session
|
||||
cleanSession.hint=Persist topic subscriptions and ack positions across client sessions
|
||||
keepAlive=Keep Alive (In seconds)
|
||||
events.duplicated.in.cluster=Is events duplicated in cluster
|
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
~
|
||||
~ WSO2 Inc. licenses this file to you under the Apache License,
|
||||
~ Version 2.0 (the "License"); you may not use this file except
|
||||
~ in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing,
|
||||
~ software distributed under the License is distributed on an
|
||||
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
~ KIND, either express or implied. See the License for the
|
||||
~ specific language governing permissions and limitations
|
||||
~ under the License.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<parent>
|
||||
<artifactId>mb-extensions</artifactId>
|
||||
<groupId>org.wso2.iot</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization</artifactId>
|
||||
<packaging>bundle</packaging>
|
||||
<name>WSO2 Carbon - Component - MQTT - Authorization Manager</name>
|
||||
<description>MQTT authorization manager based on Carbon device manager</description>
|
||||
<url>http://wso2.org</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon</groupId>
|
||||
<artifactId>org.wso2.carbon.utils</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon</groupId>
|
||||
<artifactId>org.wso2.carbon.core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wso2.andes.wso2</groupId>
|
||||
<artifactId>andes</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon</groupId>
|
||||
<artifactId>org.wso2.carbon.user.api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon</groupId>
|
||||
<artifactId>org.wso2.carbon.user.core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-scr-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
|
||||
<Bundle-Name>${project.artifactId}</Bundle-Name>
|
||||
<Private-Package>
|
||||
org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization.internal
|
||||
</Private-Package>
|
||||
<Export-Package>
|
||||
!org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization.internal,
|
||||
org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization.*
|
||||
</Export-Package>
|
||||
<Import-Package>
|
||||
org.apache.log4j,
|
||||
org.dna.mqtt.moquette.server,
|
||||
org.wso2.andes.configuration.enums,
|
||||
org.wso2.andes.mqtt,
|
||||
org.wso2.carbon.context,
|
||||
org.apache.commons.logging,
|
||||
org.osgi.service.component,
|
||||
org.wso2.carbon.user.core.service,
|
||||
org.wso2.carbon.user.core.tenant,
|
||||
org.wso2.carbon.user.api
|
||||
</Import-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.dna.mqtt.moquette.server.IAuthorizer;
|
||||
import org.wso2.andes.configuration.enums.MQTTAuthoriztionPermissionLevel;
|
||||
import org.wso2.andes.mqtt.MQTTAuthorizationSubject;
|
||||
import org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization.internal.AuthorizationDataHolder;
|
||||
import org.wso2.carbon.context.PrivilegedCarbonContext;
|
||||
import org.wso2.carbon.user.api.UserRealm;
|
||||
import org.wso2.carbon.user.api.UserStoreException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Authorize the connecting users against Carbon Permission Model. Intended usage is
|
||||
* via providing fully qualified class name in broker.xml
|
||||
* <p/>
|
||||
* This is just a simple authorization model. For dynamic topics use an implementation based on IAuthorizer
|
||||
*/
|
||||
public class DeviceAccessBasedMQTTAuthorizer implements IAuthorizer {
|
||||
private static final Logger logger = Logger.getLogger(DeviceAccessBasedMQTTAuthorizer.class);
|
||||
private static final String CONNECTION_PERMISSION = "/permission/admin/device-mgt/user";
|
||||
private static final String SCOPE_IDENTIFIER = "scope";
|
||||
|
||||
/**
|
||||
* {@inheritDoc} Authorize the user against carbon device mgt model.
|
||||
*/
|
||||
@Override
|
||||
public boolean isAuthorizedForTopic(MQTTAuthorizationSubject authorizationSubject, String topic,
|
||||
MQTTAuthoriztionPermissionLevel permissionLevel) {
|
||||
String topics[] = topic.split("/");
|
||||
if (topics.length < 3) {
|
||||
return false;
|
||||
}
|
||||
String tenantIdFromTopic = topics[0];
|
||||
if (!tenantIdFromTopic.equals(authorizationSubject.getTenantDomain())) {
|
||||
return false;
|
||||
}
|
||||
String deviceTypeFromTopic = topics[1];
|
||||
String deviceIdFromTopic = topics[2];
|
||||
List<String> scopes = (List<String>) authorizationSubject.getProperties().get(SCOPE_IDENTIFIER);
|
||||
if (scopes != null) {
|
||||
for (String scope : scopes) {
|
||||
//TODO : have to validate token with scopes.
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc} Authorized the user against carbon device mgt model.
|
||||
*/
|
||||
@Override
|
||||
public boolean isAuthorizedToConnect(MQTTAuthorizationSubject authorizationSubject) {
|
||||
return isUserAuthorized(authorizationSubject, CONNECTION_PERMISSION, "ui.execute");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the client is authorized with the given permission and action.
|
||||
*
|
||||
* @param authorizationSubject this contains the client information
|
||||
* @param permission Carbon permission that requires for the use
|
||||
* @param action Carbon permission action that requires for the given permission.
|
||||
* @return boolean - true if user is authorized else return false.
|
||||
*/
|
||||
private boolean isUserAuthorized(MQTTAuthorizationSubject authorizationSubject, String permission, String action) {
|
||||
String username = authorizationSubject.getUsername();
|
||||
try {
|
||||
PrivilegedCarbonContext.startTenantFlow();
|
||||
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(
|
||||
authorizationSubject.getTenantDomain(), true);
|
||||
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
|
||||
UserRealm userRealm = AuthorizationDataHolder.getInstance().getRealmService()
|
||||
.getTenantUserRealm(tenantId);
|
||||
if (userRealm != null && userRealm.getAuthorizationManager() != null) {
|
||||
return userRealm.getAuthorizationManager().isUserAuthorized(username, permission, action);
|
||||
}
|
||||
return false;
|
||||
} catch (UserStoreException e) {
|
||||
String errorMsg = String.format("Unable to authorize the user : %s", username);
|
||||
logger.error(errorMsg, e);
|
||||
return false;
|
||||
} finally {
|
||||
PrivilegedCarbonContext.endTenantFlow();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* you may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization.internal;
|
||||
|
||||
import org.wso2.carbon.user.core.service.RealmService;
|
||||
import org.wso2.carbon.user.core.tenant.TenantManager;
|
||||
|
||||
public class AuthorizationDataHolder {
|
||||
|
||||
private RealmService realmService;
|
||||
private TenantManager tenantManager;
|
||||
|
||||
private static AuthorizationDataHolder thisInstance = new AuthorizationDataHolder();
|
||||
|
||||
private AuthorizationDataHolder() {}
|
||||
|
||||
public static AuthorizationDataHolder getInstance() {
|
||||
return thisInstance;
|
||||
}
|
||||
|
||||
public RealmService getRealmService() {
|
||||
return realmService;
|
||||
}
|
||||
|
||||
public void setRealmService(RealmService realmService) {
|
||||
this.realmService = realmService;
|
||||
this.setTenantManager(realmService);
|
||||
}
|
||||
|
||||
private void setTenantManager(RealmService realmService) {
|
||||
if (realmService == null) {
|
||||
throw new IllegalStateException("Realm service is not initialized properly");
|
||||
}
|
||||
this.tenantManager = realmService.getTenantManager();
|
||||
}
|
||||
|
||||
public TenantManager getTenantManager() {
|
||||
return tenantManager;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* you may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization.internal;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.wso2.carbon.user.core.service.RealmService;
|
||||
|
||||
/**
|
||||
* @scr.component name="org.wso2.carbon.devicemgt.policy.manager" immediate="true"
|
||||
* @scr.reference name="user.realmservice.default"
|
||||
* interface="org.wso2.carbon.user.core.service.RealmService"
|
||||
* cardinality="1..1"
|
||||
* policy="dynamic"
|
||||
* bind="setRealmService"
|
||||
* unbind="unsetRealmService"
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AuthorizationServiceComponent {
|
||||
|
||||
private static Log log = LogFactory.getLog(AuthorizationServiceComponent.class);
|
||||
|
||||
protected void activate(ComponentContext componentContext) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void deactivate(ComponentContext componentContext) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets Realm Service
|
||||
*
|
||||
* @param realmService An instance of RealmService
|
||||
*/
|
||||
protected void setRealmService(RealmService realmService) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Setting Realm Service");
|
||||
}
|
||||
AuthorizationDataHolder.getInstance().setRealmService(realmService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets Realm Service
|
||||
*
|
||||
* @param realmService An instance of RealmService
|
||||
*/
|
||||
protected void unsetRealmService(RealmService realmService) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Unsetting Realm Service");
|
||||
}
|
||||
AuthorizationDataHolder.getInstance().setRealmService(null);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
~
|
||||
~ WSO2 Inc. licenses this file to you under the Apache License,
|
||||
~ Version 2.0 (the "License"); you may not use this file except
|
||||
~ in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing,
|
||||
~ software distributed under the License is distributed on an
|
||||
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
~ KIND, either express or implied. See the License for the
|
||||
~ specific language governing permissions and limitations
|
||||
~ under the License.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>org.wso2.iot</groupId>
|
||||
<artifactId>iot-extensions</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>mb-extensions</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>WSO2 Carbon - MB Extension</name>
|
||||
<url>http://wso2.org</url>
|
||||
|
||||
<modules>
|
||||
<module>org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization</module>
|
||||
</modules>
|
||||
</project>
|
@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
~
|
||||
~ WSO2 Inc. licenses this file to you under the Apache License,
|
||||
~ Version 2.0 (the "License"); you may not use this file except
|
||||
~ in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing,
|
||||
~ software distributed under the License is distributed on an
|
||||
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
~ KIND, either express or implied. See the License for the
|
||||
~ specific language governing permissions and limitations
|
||||
~ under the License.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<parent>
|
||||
<artifactId>mb-extensions-feature</artifactId>
|
||||
<groupId>org.wso2.iot</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization.feature</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>WSO2 Carbon - MQTT Authorization Feature</name>
|
||||
<url>http://wso2.org</url>
|
||||
<description>This feature contains the bundles required for mqtt authorization</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.wso2.iot</groupId>
|
||||
<artifactId>org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.wso2.maven</groupId>
|
||||
<artifactId>carbon-p2-plugin</artifactId>
|
||||
<version>${carbon.p2.plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>4-p2-feature-generation</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>p2-feature-gen</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<id>org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization</id>
|
||||
<propertiesFile>../../etc/feature.properties</propertiesFile>
|
||||
<adviceFile>
|
||||
<properties>
|
||||
<propertyDef>org.wso2.carbon.p2.category.type:server</propertyDef>
|
||||
<propertyDef>org.eclipse.equinox.p2.type.group:true</propertyDef>
|
||||
</properties>
|
||||
</adviceFile>
|
||||
<bundles>
|
||||
<bundleDef>
|
||||
org.wso2.iot:org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization:${carbon.iot.device.mgt.version}
|
||||
</bundleDef>
|
||||
</bundles>
|
||||
<importFeatures>
|
||||
<importFeatureDef>org.wso2.carbon.core.server:${carbon.kernel.version}</importFeatureDef>
|
||||
</importFeatures>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
#
|
||||
# WSO2 Inc. licenses this file to you under the Apache License,
|
||||
# Version 2.0 (the "License"); you may not use this file except
|
||||
# in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
custom = true
|
@ -0,0 +1 @@
|
||||
instructions.configure = \
|
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
~
|
||||
~ WSO2 Inc. licenses this file to you under the Apache License,
|
||||
~ Version 2.0 (the "License"); you may not use this file except
|
||||
~ in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing,
|
||||
~ software distributed under the License is distributed on an
|
||||
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
~ KIND, either express or implied. See the License for the
|
||||
~ specific language governing permissions and limitations
|
||||
~ under the License.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>org.wso2.iot</groupId>
|
||||
<artifactId>iot-extensions</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>mb-extensions-feature</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>WSO2 Carbon - Device Management, MB Extensions Feature</name>
|
||||
<url>http://wso2.org</url>
|
||||
|
||||
<modules>
|
||||
<module>org.wso2.carbon.andes.extensions.device.mgt.mqtt.authorization.feature</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
Loading…
Reference in new issue