urbanairship / java-library Goto Github PK
View Code? Open in Web Editor NEWJava client library for the Urban Airship API
License: Other
Java client library for the Urban Airship API
License: Other
Is there any way to delete a scheduled push via Client?
According to the documentation (https://docs.airship.com/api/ua/#schemas/customeventobject), a custom event request should accept a NamedUserId. However, the current implementation (https://github.com/urbanairship/java-library/blob/master/src/main/java/com/urbanairship/api/customevents/model/CustomEventUser.java) allows setting only Channels. Please consider changing the model, so that it supports NamedUserIds.
Our application using 1.0.0 of the api now throws exceptions during report since 2017-05-22:
12:17:50.952 [New I/O worker #1] ERROR c.u.api.client.ResponseAsyncHandler - Exception thrown during response processing
java.lang.IllegalStateException: Optional.get() cannot be called on an absent value
at com.google.common.base.Absent.get(Absent.java:49)
at com.urbanairship.api.reports.parse.PushDetailResponseReader.readPlatforms(PushDetailResponseReader.java:87)
at com.urbanairship.api.reports.parse.PushDetailResponseDeserializer$1.parse(PushDetailResponseDeserializer.java:87)
at com.urbanairship.api.reports.parse.PushDetailResponseDeserializer$1.parse(PushDetailResponseDeserializer.java:84)
at com.urbanairship.api.common.parse.StandardObjectDeserializer.deserialize(StandardObjectDeserializer.java:42)
at com.urbanairship.api.reports.parse.PushDetailResponseDeserializer.deserialize(PushDetailResponseDeserializer.java:109)
at com.urbanairship.api.reports.parse.PushDetailResponseDeserializer.deserialize(PushDetailResponseDeserializer.java:20)
...
Changed API version to 1.3.0, same problem. Stacktrace is attached.
report-exception.txt
APIClient builder or api methods such as
public APIClientResponse<APIPushResponse> push(PushPayload payload)
should accept org.apache.http.params.BasicHttpParams so that we can set
By default these values are set to 0 which means infinite timeout which causes performance problem in code using this client.
According to http://docs.urbanairship.com/api/ua.html#push-options we can set an expiry option.
Using this Java library I create an expiry push like this:
DevicePayloadOverride devicePayload = null;
DeviceTypeData deviceTypeData = null;
Selector selector = null;
PushExpiry pushExpiry = null;
if (expireAt != null) {
final PushExpiry.Builder pushExpiryBuilder = PushExpiry.newBuilder();
pushExpiryBuilder.setExpiryTimeStamp(new DateTime(expireAt));
pushExpiry = pushExpiryBuilder.build();
}
if (device.getOperatingSystem() == DeviceImpl.OperatingSystem.ANDROID) {
final AndroidDevicePayload.Builder androidDevicePayloadBuilder = AndroidDevicePayload.newBuilder().setAlert(message);
if (!CollectionUtils.isEmpty(extraEntries)) {
for (final Map.Entry<String, String> extraEntry : extraEntries.entrySet()) {
androidDevicePayloadBuilder.addExtraEntry(extraEntry.getKey(), extraEntry.getValue());
}
}
androidDevicePayloadBuilder.setTimeToLive(pushExpiry);
devicePayload = androidDevicePayloadBuilder.build();
deviceTypeData = DeviceTypeData.of(DeviceType.ANDROID);
selector = Selectors.apid(device.getPushToken());
} else if (device.getOperatingSystem() == DeviceImpl.OperatingSystem.IOS) {
final IOSDevicePayload.Builder iosDevicePayloadBuilder = IOSDevicePayload.newBuilder().setAlert(message);
if (!CollectionUtils.isEmpty(extraEntries)) {
for (final Map.Entry<String, String> extraEntry : extraEntries.entrySet()) {
iosDevicePayloadBuilder.addExtraEntry(extraEntry.getKey(), extraEntry.getValue());
}
}
iosDevicePayloadBuilder.setExpiry(pushExpiry);
devicePayload = iosDevicePayloadBuilder.build();
deviceTypeData = DeviceTypeData.of(DeviceType.IOS);
selector = Selectors.deviceToken(device.getPushToken());
} else {
logger.warn(MARKER, "Unsupported OperationSystem {} for push, aborting.", device.getOperatingSystem());
return;
}
final Notification notification = Notifications.notification(devicePayload);
final PushPayload pushPayload =
PushPayload.newBuilder().setAudience(selector).setNotification(notification).setDeviceTypes(deviceTypeData).build();
final APIClientResponse<APIPushResponse> response = apiClient.push(pushPayload);
Note: expireAt is a java.util.Date.
In my opinion, this is correct usage of the API, but I get the following response from the REST service:
APIRequestException:
Message:Bad Request
HttpResponse:HTTP/1.1 400 Bad Request [Content-Type: application/vnd.urbanairship+json; version=3, Server: Jetty(8.y.z-SNAPSHOT), Date: Tue, 24 Mar 2015 10:12:07 GMT, Connection: close]
Error:APIError:Could not parse request body.
Code:Optional.of(40422)
Details:
APIErrorDetails:
Path:notification.android.time_to_live
Error:Unexpected token 'START_OBJECT' while parsing expiry time
Optional Location:Optional.of(Location:
Line:1
Column:163)
I debugged a bit and found the JSON sent to the server...
...when using expirySeconds:
{
"audience":{
"apid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"device_types":[
"android"
],
"notification":{
"android":{
"alert":"Blaaaa",
"time_to_live":{
"expirySeconds":396546366
}
}
}
}
...when using expiryTimestamp:
{
"audience":{
"apid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"device_types":[
"android"
],
"notification":{
"android":{
"alert":"Test",
"time_to_live":{
"expiryTimestamp":"2015-03-29T08:20:00"
}
}
}
}
Am I doing something wrong or is the Java client incorrectly serializing the JSON that gets sent to the server?
I am looking forward to any hint or feedback, thank you!
PS: I'm using version 0.3.2 from Maven central.
Hi,
I would like to mock UrbanAirship http requests but it goes directly to an URL 'go.urbanairship.com'.
Is it possible to do this? Basically to test latencies, and unexpected errors
Thank you
Following docs while setting up a basic connection to client like so
return UrbanAirshipClient.newBuilder()
.setKey(airshipProperties.key)
.setSecret(airshipProperties.secret)
.build()
Kept getting 401s. My mobile app developer colleagues were facing the same issue and got an email from support urging them (in the SDK) to set datacenter to EU programmatically. The client was invoking the US datacenter and our data was in EU, resulting in said errors. I thought I had to replicate the proposed solution in my server application. I can overwrite the client and use
.setClient(..)
but this is a little bit cryptic. I'd expect setting the correct baseUri to be exposed as configuration in the AsyncRequestClient or the docs referring to the client ovewrite specifically.
In version 0.1.3 of the APIClient class, the API_VALIDATE_PATH value is "/api/validate/" but it should be "/api/push/validate".
In its current form you always get a 404 response when calling APIClient.validate(PushPayload);
We are sometimes getting a NoHttpResponseException when using the Push API. I'm not sure exactly how to fix this problem. I remember in older versions of Apache HttpClient you could configure auto retries. We've used that mechanism before to get around this problem. Right now, we need to implement the retry logic ourselves, but it seems like this type of low level problem should be handled internally in the library itself.
21-Oct-2013 13:31:05,303 ERROR [pushNotificationMessageListenerContainer-10] com.xoom.notification.push.urbanairship.service.UrbanAirshipServiceImpl.makePushRequest(114) - Failed to make Urban Airship API push request
org.apache.http.NoHttpResponseException: The target server failed to respond
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:95)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:62)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:254)
at org.apache.http.impl.AbstractHttpClientConnection.receiveResponseHeader(AbstractHttpClientConnection.java:289)
at org.apache.http.impl.conn.DefaultClientConnection.receiveResponseHeader(DefaultClientConnection.java:252)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.receiveResponseHeader(ManagedClientConnectionImpl.java:191)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:300)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:127)
at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:715)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:520)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
at org.apache.http.client.fluent.Executor.execute(Executor.java:202)
at com.urbanairship.api.client.APIClient.executePushRequest(APIClient.java:174)
at com.urbanairship.api.client.APIClient.push(APIClient.java:201)
at com.xoom.notification.push.urbanairship.service.UrbanAirshipServiceImpl.makePushRequest(UrbanAirshipServiceImpl.java:103)
at com.xoom.notification.push.urbanairship.service.UrbanAirshipServiceImpl.pushMessageToUser(UrbanAirshipServiceImpl.java:72)
Hello,
I'm using your client to send notification to both iOS and Android platform.
For Apple, everything works as expected because I have the device token. However for Android, I only have the GCM Registration ID. Is there any way to use your client with that information in order to target one specific android device ?
BR,
the urbanairship java-client 1.0.0, is not updated with the jackson libraries, so, in runtime gives an Exception because don't find the objects.
this are the correct libraries:
groupId>com.fasterxml.jackson.core
artifactId>jackson-databind
version>2.7.0
groupId>com.fasterxml.jackson.core
artifactId>jackson-core
version>2.7.0
Please implement the missing actions functions necessary for opening deep links, urls and landing pages.
All bugs, feature requests, implementation concerns or general queries should be sent to our support team.
You are welcome to submit an issue here for bugs, but please also reach out to our support team as well.
Before completing the form below, please check the following:
Do you think there will be an update to Maven Central anytime soon? It's still showing code from 2013 and lacks support for ADM.
http://mvnrepository.com/artifact/com.urbanairship/java-client
Being able to send a ChannelTagRequest using Bearer authentication.
The REST API seems to allow Bearer authentication for this endpoint (https://docs.airship.com/api/ua/#operation/api/channels/tags/post).
Exception in thread "main" java.lang.IllegalStateException: Optional.get() cannot be called on an absent value
at com.google.common.base.Absent.get(Absent.java:43)
at com.urbanairship.api.client.UrbanAirshipClient.createHeaders(UrbanAirshipClient.java:97)
at com.urbanairship.api.client.UrbanAirshipClient.executeAsync(UrbanAirshipClient.java:58)
at com.urbanairship.api.client.UrbanAirshipClient.execute(UrbanAirshipClient.java:71)
at com.urbanairship.api.client.UrbanAirshipClient.execute(UrbanAirshipClient.java:66)
Tested with Java SDK 5.0.0.
Override bearerTokenAuthRequired in ChannelTagRequest
ChannelTagRequest request = new ChannelTagRequest() {
@Override
public boolean bearerTokenAuthRequired() {
return true;
}
}
.addIOSChannels("XXX")
.addTags("subscriptions", tags);
With the following code (given valid credentials and channel id), the exception is produced.
UrbanAirshipClient client = UrbanAirshipClient.newBuilder()
.setBaseUri("https://go.airship.eu")
.setKey("XXX")
.setBearerToken("XXX")
.build();
Set<String> tags = Set.of("tag");
ChannelTagRequest request = ChannelTagRequest.newRequest()
.addIOSChannels("XXX")
.addTags("subscriptions", tags);
Response<String> response = client.execute(request);
Before completing the form below, please check the following:
I even checked the most recent snapshot
Push API supports notification_channel key in platform override section for Android.
See
https://docs.urbanairship.com/api/ua/#schemas/androidoverrideobject/notification_channel
AndroidDevicePayload does not allow to specify notification_channel value, because correspondent property is missing
add notification_channel field to AndroidDevicePayload and correspondent serializer/deserializer
Will the following CVEs be addressed and a new version be released?
https://nvd.nist.gov/vuln/detail/CVE-2019-14540
and
https://nvd.nist.gov/vuln/detail/CVE-2019-16335
NIST.gov has been running slowly today but the jist is jackson-databind need to be updated to 2.9.10
I somehow try to make sense of PushDetailResponse:
private final long richDeletions;
private final long richResponses;
private final long richSends;
private final long sends;
private final long directResponses;
private final long influencedResponses;
but there is absolutely no javadoc in the class file. What do these fields do / mean?
version 19 and 21 are incompatible.
This api call is not present in the library:
http://docs.urbanairship.com/api/ua.html#post--api-named_users-tags-
As it seems like log4j is only being directly references from within a single test class src/test/java/com/urbanairship/api/client/UrbanAirshipClientTest.java
I don't see why log4j-core
and -api
dependencies are scoped compile
instead of test
.
Due to this, any project using the java-client
also virtually depend on log4j.
Before completing the form below, please check the following:
https://docs.airship.com/api/libraries/java/#disassociate-named-user shows how to disassociate channels from a named user. The example is:
NamedUserRequest request = NamedUserRequest.newDisassociationRequest()
.setChannel("ee4b5101-164c-485c-ad91-68b1d3d753cc", ChannelType.IOS)
.setChannel("0ab7d6f0-0f61-4963-afe0-5ef53735b00d", ChannelType.ANDROID);
Response<String> response = client.execute(request);
This suggests it possible to disassociate multiple channels from a named user.
Looking at the contents of the .setChannel
method I noticed it overwrites the channel_id
entry in the payload map, so calling .setChannel
the second time undoes the effect of the first call. So, only the channel referenced in the last call to .setChannel
is actually disassociated.
Adjust either the documentation to reflect only a single channel can be disassociated per request, or allow the client to disassociate multiple channels using a single request.
NamedUserRequest
like this in a test:NamedUserRequest request = NamedUserRequest.newDisassociationRequest()
.setChannel("ee4b5101-164c-485c-ad91-68b1d3d753cc", ChannelType.IOS)
.setChannel("0ab7d6f0-0f61-4963-afe0-5ef53735b00d", ChannelType.ANDROID);
payload
map only contains an entry for 0ab7d6f0-0f61-4963-afe0-5ef53735b00d
. There is no sign of ee4b5101-164c-485c-ad91-68b1d3d753cc
.trying to batch push multiple notifiacitons to different devices are per the V3 migration guide.
http://docs.urbanairship.com/reference/api/v3/api-v3-migration-guide.html#api-push
but the client only allows a single PushPayload in the push method.
i was expecting something like
apiClient.push(payload1, payload2, payload3, payload4)
is that correct or have i messed up?
Hope someone from Urban Airship Team can respond to this.
With the default setting for associating named users with a channel being on the server-side only, it seems important that we include this functionality in the Java library.
As of 1.x.x version of this library, we are running into an issue. We use several Urban Airship applications to push to a network of linked apps. Each UrbanAirshipClient creates an underlying AHC, with its own thread pool. The AHC is performance heavy and should be reused, so we don't want many of them around in our application, but since each UrbanAirshipClient is linked to the key/secret and creates a new AHC each time, it's too resource-intensive for us to use the 1.x.x library to push to our network of multiple Urban Airship apps. We were running out of threads due to creating too many UrbanAirshipClients.
Is there a workaround? I could see a way that possibly could allow you to re-use the AHC between multiple UrbanAirshipClients. Thanks,
Today is not possible to create a segment using static_list criteria on Java Client since SelectorType enum doesn't have this option.
In python library is easy, example:
from urbanairship.devices.segment import Segment
segment = Segment()
segment.criteria = {"and":[{"static_list":"my_list"}]}
segment.display_name = "my_segment"
segment.create(airship)
Dependabot has been acquired by GitHub is now free to use on open GitHub repositories. I'd recommend setting this up for the repository, so Dependabot will automatically notify you of outdated dependencies and it will even prepare pull requests.
When I create an IOSAlertData with a body it works, but when I create it with loc-key, I don't receive anything.
Any idea what I'm missing?
I've something like that:
IOSAlertData iosAlertData = IOSAlertData.newBuilder()
.setLocKey(locKey)
.setLocArgs(locArgs)
.build();
The documentation for the v3 Push API indicates there is an options key that allows for setting an expiry key/value pair:
{
"audience" : "all",
"device_types" : [ "ios" ],
"notification" : {
"ios" : {
"badge" : "+1"
},
"options" : {"expiry" : "2015-04-01T12:00:00"}
}
}
See http://docs.urbanairship.com/reference/api/v3/push.html#expiry.
The Java API does not have support for this feature. Please add it. Thanks!
I am integrating to Airship platform behind an API proxy running in application layer. Hence have the need to add additional request headers to pass the proxy server authentication.
Didn't find a proper way of doing so as the UrbanAirshipClient
always:
Authorization
header and X-UA-Appkey
header value based on client config.Accept custom headers in the Client Builder class. So that one can easily pass through proxies & firewalls
Hello,
Does the Java API support the "Interactive" field?
http://docs.urbanairship.com/api/ua.html#interactive-notifications
I am using version 1.0.0 and proxy configured with .setProxyInfo(ProxyInfo.newBuilder().setHost("host").setPassword(password")
.setPrincipal("user").setPort(port).setProtocol(ProxyInfo.ProxyInfoProtocol.HTTPS)
.build()
but throw Exception
java.net.UnknownHostException: go.urbanairship.com: unknown error
at java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.net.InetAddress$2.lookupAllHostAddr(InetAddress.java:928)
at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1323)
at java.net.InetAddress.getAllByName0(InetAddress.java:1276)
at java.net.InetAddress.getAllByName(InetAddress.java:1192)
at java.net.InetAddress.getAllByName(InetAddress.java:1126)
at java.net.InetAddress.getByName(InetAddress.java:1076)
When I add PushOptions to the PushPayload it serializes as: ..., "options":{"present":true}...
Looks like it is just a missing call to get()
in
jgen.writeObjectField("options", payload.getPushOptions());
here
According to api https://docs.airship.com/api/ua/#operation/api/named_users/associate/post
I should not set type email
with namedUser Association. If I set it as null it throws a null pointer exception inside the client
Example with type:
NamedUserRequest requestNamed = NamedUserRequest.newAssociationRequest()
.setChannel(userData.getChannelId(), ChannelType.EMAIL)
.setNamedUserId("3647824");
Current Behavior:
{
"ok": false,
"error": "Could not parse request body.",
"error_code": 40002,
"details": {
"error": "Unrecognized device type email"
}
}
Example with type null:
NamedUserRequest requestNamed = NamedUserRequest.newAssociationRequest()
.setChannel(userData.getChannelId(), null)
.setNamedUserId("3647824");
Current Behavior:
throws a NullpointerException.
ChannelType is a required field in the client method call but not for the api
I am using the following to send push notifications
String appKey = "appKey";
String appSecret = "appSecret";
// Setup an authenticated APIClient with your application key and
// application master secret.
APIClient apiClient = APIClient.newBuilder()
.setKey(appKey)
.setSecret(appSecret)
.build();
// Setup a push payload to send to the API with our handy builders
PushPayload payload = PushPayload.newBuilder()
.setAudience(Selectors.all())
.setNotification(Notifications.notification("UA Push"))
.setDeviceTypes(DeviceTypeData.of(DeviceType.IOS))
.build();
// Try and send, handle anything that comes up
try {
APIClientResponse response = apiClient.push(payload);
logger.info("Sent a push message!");
}
// Non 200 responses throw an APIRequestException. Check the documentation
// to debug your request.
catch (APIRequestException ex){
logger.error("Non 200 request, checking error details and taking action");
}
// An underlying error occurred, most likely outside of the scope of the
// UA library, do some HTTP debugging
catch (IOException e){
logger.error("Broken pipe what?");
}
I want to add a extra key like "key":"value" to the PushPayload. How can i do it ?
There is currently no support for the UA Tag API. Is support for this planned?
Hi,
I need to reduce the size of the request (we have a large number of tags) and would like to be able to construct an implicit compound selector:
http://docs.urbanairship.com/api/ua.html#compound-selectors
Can't seem to figure it out though?
We are experiencing frequent SocketExceptions when using the Push API. The problem is a connection reset. I've included an example stack trace below. I'm not sure exactly how to fix this problem. I remember in older versions of Apache HttpClient you could configure auto retries. We've used that mechanism before to get around this problem. Right now, we need to implement the retry logic ourselves, but it seems like this type of low level problem should be handled internally in the library itself.
21-Oct-2013 13:52:55,502 ERROR [pushNotificationMessageListenerContainer-6] com.xoom.notification.push.urbanairship.service.UrbanAirshipServiceImpl.makePushRequest(114) - Failed to make Urban Airship API push request
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(Unknown Source)
at com.sun.net.ssl.internal.ssl.InputRecord.readFully(Unknown Source)
at com.sun.net.ssl.internal.ssl.InputRecord.read(Unknown Source)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(Unknown Source)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readDataRecord(Unknown Source)
at com.sun.net.ssl.internal.ssl.AppInputStream.read(Unknown Source)
at org.apache.http.impl.io.AbstractSessionInputBuffer.fillBuffer(AbstractSessionInputBuffer.java:166)
at org.apache.http.impl.io.SocketInputBuffer.fillBuffer(SocketInputBuffer.java:90)
at org.apache.http.impl.io.AbstractSessionInputBuffer.readLine(AbstractSessionInputBuffer.java:281)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:92)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:62)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:254)
at org.apache.http.impl.AbstractHttpClientConnection.receiveResponseHeader(AbstractHttpClientConnection.java:289)
at org.apache.http.impl.conn.DefaultClientConnection.receiveResponseHeader(DefaultClientConnection.java:252)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.receiveResponseHeader(ManagedClientConnectionImpl.java:191)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:300)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:127)
at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:715)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:520)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
at org.apache.http.client.fluent.Executor.execute(Executor.java:202)
at com.urbanairship.api.client.APIClient.executePushRequest(APIClient.java:174)
at com.urbanairship.api.client.APIClient.push(APIClient.java:201)
at com.xoom.notification.push.urbanairship.service.UrbanAirshipServiceImpl.makePushRequest(UrbanAirshipServiceImpl.java:103)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.