[sleepiq] Use constructor injection for ClientBuilder (#11700)

Fixes #11696

Signed-off-by: Mark Hilbush <mark@hilbush.com>
This commit is contained in:
Mark Hilbush 2021-12-05 05:20:20 -05:00 committed by GitHub
parent d0bf1e3313
commit c1de380771
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 160 deletions

View File

@ -17,6 +17,8 @@ package org.openhab.binding.sleepiq.api;
import java.util.List;
import javax.ws.rs.client.ClientBuilder;
import org.openhab.binding.sleepiq.api.impl.SleepIQImpl;
import org.openhab.binding.sleepiq.api.model.Bed;
import org.openhab.binding.sleepiq.api.model.FamilyStatus;
@ -29,8 +31,7 @@ import org.openhab.binding.sleepiq.api.model.Sleeper;
*
* @author Gregory Moyer
*/
public interface SleepIQ
{
public interface SleepIQ {
/**
* Login to the {@link Configuration configured} account. This method is not
* required to be called before other methods because all methods must
@ -94,8 +95,7 @@ public interface SleepIQ
* the configuration to use for the new instance
* @return a concrete implementation of this interface
*/
public static SleepIQ create(Configuration config)
{
return new SleepIQImpl(config);
public static SleepIQ create(Configuration config, ClientBuilder clientBuilder) {
return new SleepIQImpl(config, clientBuilder);
}
}

View File

@ -49,8 +49,7 @@ import org.openhab.binding.sleepiq.api.model.Sleeper;
import org.openhab.binding.sleepiq.api.model.SleepersResponse;
import org.openhab.binding.sleepiq.internal.GsonProvider;
public class SleepIQImpl extends AbstractClient implements SleepIQ
{
public class SleepIQImpl extends AbstractClient implements SleepIQ {
protected static final String PARAM_KEY = "_k";
protected static final String DATA_BED_ID = "bedId";
@ -59,47 +58,36 @@ public class SleepIQImpl extends AbstractClient implements SleepIQ
private volatile LoginInfo loginInfo;
public SleepIQImpl(Configuration config)
{
private final ClientBuilder clientBuilder;
public SleepIQImpl(Configuration config, ClientBuilder clientBuilder) {
this.config = config;
this.clientBuilder = clientBuilder;
}
@Override
public LoginInfo login() throws LoginException
{
if (loginInfo == null)
{
synchronized (this)
{
if (loginInfo == null)
{
Response response = getClient().target(config.getBaseUri())
.path(Endpoints.login())
.request(MediaType.APPLICATION_JSON_TYPE)
.put(Entity.json(new LoginRequest().withLogin(config.getUsername())
.withPassword(config.getPassword())));
public LoginInfo login() throws LoginException {
if (loginInfo == null) {
synchronized (this) {
if (loginInfo == null) {
Response response = getClient().target(config.getBaseUri()).path(Endpoints.login())
.request(MediaType.APPLICATION_JSON_TYPE).put(Entity.json(new LoginRequest()
.withLogin(config.getUsername()).withPassword(config.getPassword())));
if (isUnauthorized(response))
{
if (isUnauthorized(response)) {
throw new UnauthorizedException(response.readEntity(Failure.class));
}
if (!Status.Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily()))
{
if (!Status.Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily())) {
throw new LoginException(response.readEntity(Failure.class));
}
// add the received cookies to all future requests
getClient().register(new ClientRequestFilter()
{
getClient().register(new ClientRequestFilter() {
@Override
public void filter(ClientRequestContext requestContext) throws IOException
{
List<Object> cookies = response.getCookies()
.values()
.stream()
.map(newCookie -> newCookie.toCookie())
.collect(Collectors.toList());
public void filter(ClientRequestContext requestContext) throws IOException {
List<Object> cookies = response.getCookies().values().stream()
.map(newCookie -> newCookie.toCookie()).collect(Collectors.toList());
requestContext.getHeaders().put("Cookie", cookies);
}
});
@ -113,106 +101,76 @@ public class SleepIQImpl extends AbstractClient implements SleepIQ
}
@Override
public List<Bed> getBeds()
{
public List<Bed> getBeds() {
return getSessionResponse(this::getBedsResponse).readEntity(BedsResponse.class).getBeds();
}
protected Response getBedsResponse(Map<String, Object> data) throws LoginException
{
protected Response getBedsResponse(Map<String, Object> data) throws LoginException {
LoginInfo login = login();
return getClient().target(config.getBaseUri())
.path(Endpoints.bed())
.queryParam(PARAM_KEY, login.getKey())
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
return getClient().target(config.getBaseUri()).path(Endpoints.bed()).queryParam(PARAM_KEY, login.getKey())
.request(MediaType.APPLICATION_JSON_TYPE).get();
}
@Override
public List<Sleeper> getSleepers()
{
return getSessionResponse(this::getSleepersResponse).readEntity(SleepersResponse.class)
.getSleepers();
public List<Sleeper> getSleepers() {
return getSessionResponse(this::getSleepersResponse).readEntity(SleepersResponse.class).getSleepers();
}
protected Response getSleepersResponse(Map<String, Object> data) throws LoginException
{
protected Response getSleepersResponse(Map<String, Object> data) throws LoginException {
LoginInfo login = login();
return getClient().target(config.getBaseUri())
.path(Endpoints.sleeper())
.queryParam(PARAM_KEY, login.getKey())
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
return getClient().target(config.getBaseUri()).path(Endpoints.sleeper()).queryParam(PARAM_KEY, login.getKey())
.request(MediaType.APPLICATION_JSON_TYPE).get();
}
@Override
public FamilyStatus getFamilyStatus()
{
public FamilyStatus getFamilyStatus() {
return getSessionResponse(this::getFamilyStatusResponse).readEntity(FamilyStatus.class);
}
protected Response getFamilyStatusResponse(Map<String, Object> data) throws LoginException
{
protected Response getFamilyStatusResponse(Map<String, Object> data) throws LoginException {
LoginInfo login = login();
return getClient().target(config.getBaseUri())
.path(Endpoints.bed())
.path(Endpoints.familyStatus())
.queryParam(PARAM_KEY, login.getKey())
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
return getClient().target(config.getBaseUri()).path(Endpoints.bed()).path(Endpoints.familyStatus())
.queryParam(PARAM_KEY, login.getKey()).request(MediaType.APPLICATION_JSON_TYPE).get();
}
@Override
public PauseMode getPauseMode(String bedId) throws BedNotFoundException
{
public PauseMode getPauseMode(String bedId) throws BedNotFoundException {
Map<String, Object> data = new HashMap<>();
data.put(DATA_BED_ID, bedId);
Response response = getSessionResponse(this::getPauseModeResponse, data);
if (!Status.Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily()))
{
if (!Status.Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily())) {
throw new BedNotFoundException(response.readEntity(Failure.class));
}
return response.readEntity(PauseMode.class);
}
protected Response getPauseModeResponse(Map<String, Object> data) throws LoginException
{
protected Response getPauseModeResponse(Map<String, Object> data) throws LoginException {
LoginInfo login = login();
return getClient().target(config.getBaseUri())
.path(Endpoints.bed())
.path(data.get(DATA_BED_ID).toString())
.path(Endpoints.pauseMode())
.queryParam(PARAM_KEY, login.getKey())
.request(MediaType.APPLICATION_JSON_TYPE)
.get();
return getClient().target(config.getBaseUri()).path(Endpoints.bed()).path(data.get(DATA_BED_ID).toString())
.path(Endpoints.pauseMode()).queryParam(PARAM_KEY, login.getKey())
.request(MediaType.APPLICATION_JSON_TYPE).get();
}
protected boolean isUnauthorized(Response response)
{
protected boolean isUnauthorized(Response response) {
return Status.UNAUTHORIZED.getStatusCode() == response.getStatusInfo().getStatusCode();
}
protected synchronized void resetLogin()
{
protected synchronized void resetLogin() {
loginInfo = null;
}
protected Response getSessionResponse(Request request)
{
protected Response getSessionResponse(Request request) {
return getSessionResponse(request, Collections.emptyMap());
}
protected Response getSessionResponse(Request request, Map<String, Object> data)
{
try
{
protected Response getSessionResponse(Request request, Map<String, Object> data) {
try {
Response response = request.execute(data);
if (isUnauthorized(response))
{
if (isUnauthorized(response)) {
// session timed out
response.close();
resetLogin();
@ -220,35 +178,27 @@ public class SleepIQImpl extends AbstractClient implements SleepIQ
}
return response;
}
catch (LoginException e)
{
} catch (LoginException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
@Override
protected Client createClient()
{
ClientBuilder builder = ClientBuilder.newBuilder();
protected Client createClient() {
// setup Gson (de)serialization
GsonProvider<Object> gsonProvider = new GsonProvider<>(getGson());
builder.register(gsonProvider);
clientBuilder.register(gsonProvider);
// turn on logging if requested
if (config.isLogging())
{
builder.register(new LoggingFilter(Logger.getLogger(SleepIQImpl.class.getName()),
true));
if (config.isLogging()) {
clientBuilder.register(new LoggingFilter(Logger.getLogger(SleepIQImpl.class.getName()), true));
}
return builder.build();
return clientBuilder.build();
}
@FunctionalInterface
public static interface Request
{
public static interface Request {
public Response execute(Map<String, Object> data) throws LoginException;
}
}

View File

@ -16,46 +16,37 @@
package org.openhab.binding.sleepiq.api.model;
import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TimeSince
{
public class TimeSince {
private static final Pattern PATTERN = Pattern.compile("(([0-9]+) d )?([0-9]{2}):([0-9]{2}):([0-9]{2})",
Pattern.CASE_INSENSITIVE);
Pattern.CASE_INSENSITIVE);
private Duration duration;
public Duration getDuration()
{
public Duration getDuration() {
return duration;
}
public void setDuration(Duration duration)
{
public void setDuration(Duration duration) {
this.duration = duration == null ? null : duration.abs();
}
public TimeSince withDuration(long days, long hours, long minutes, long seconds)
{
return withDuration(Duration.ofSeconds(TimeUnit.DAYS.toSeconds(days)
+ TimeUnit.HOURS.toSeconds(hours)
+ TimeUnit.MINUTES.toSeconds(minutes)
+ seconds));
public TimeSince withDuration(long days, long hours, long minutes, long seconds) {
return withDuration(Duration.ofSeconds(TimeUnit.DAYS.toSeconds(days) + TimeUnit.HOURS.toSeconds(hours)
+ TimeUnit.MINUTES.toSeconds(minutes) + seconds));
}
public TimeSince withDuration(Duration duration)
{
public TimeSince withDuration(Duration duration) {
setDuration(duration);
return this;
}
@Override
public int hashCode()
{
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((duration == null) ? 0 : duration.hashCode());
@ -63,38 +54,29 @@ public class TimeSince
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null)
{
if (obj == null) {
return false;
}
if (!(obj instanceof TimeSince))
{
if (!(obj instanceof TimeSince)) {
return false;
}
TimeSince other = (TimeSince)obj;
if (duration == null)
{
if (other.duration != null)
{
TimeSince other = (TimeSince) obj;
if (duration == null) {
if (other.duration != null) {
return false;
}
}
else if (!duration.equals(other.duration))
{
} else if (!duration.equals(other.duration)) {
return false;
}
return true;
}
@Override
public String toString()
{
public String toString() {
long totalDays = duration.toDays();
long totalHours = duration.toHours();
long totalMinutes = duration.toMinutes();
@ -104,22 +86,19 @@ public class TimeSince
long minutes = totalMinutes - TimeUnit.HOURS.toMinutes(totalHours);
long seconds = totalSeconds - TimeUnit.MINUTES.toSeconds(totalMinutes);
if (totalDays > 0)
{
if (totalDays > 0) {
return String.format("%d d %02d:%02d:%02d", totalDays, hours, minutes, seconds);
}
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
public static TimeSince parse(CharSequence text)
{
public static TimeSince parse(CharSequence text) {
Objects.requireNonNull(text, "text");
Matcher matcher = PATTERN.matcher(text);
if (!matcher.matches())
{
throw new DateTimeParseException("Text cannot be parsed", text, 0);
if (!matcher.matches()) {
return new TimeSince().withDuration(Duration.ZERO);
}
String dayMatch = matcher.group(2);
@ -128,17 +107,10 @@ public class TimeSince
String secondMatch = matcher.group(5);
StringBuilder sb = new StringBuilder("P");
if (dayMatch != null)
{
if (dayMatch != null) {
sb.append(dayMatch).append('D');
}
sb.append('T')
.append(hourMatch)
.append('H')
.append(minuteMatch)
.append('M')
.append(secondMatch)
.append('S');
sb.append('T').append(hourMatch).append('H').append(minuteMatch).append('M').append(secondMatch).append('S');
return new TimeSince().withDuration(Duration.parse(sb.toString()));
}

View File

@ -20,6 +20,8 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.ws.rs.client.ClientBuilder;
import org.openhab.binding.sleepiq.internal.discovery.SleepIQBedDiscoveryService;
import org.openhab.binding.sleepiq.internal.handler.SleepIQCloudHandler;
import org.openhab.binding.sleepiq.internal.handler.SleepIQDualBedHandler;
@ -32,7 +34,9 @@ import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -49,8 +53,15 @@ public class SleepIQHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(SleepIQHandlerFactory.class);
private final ClientBuilder clientBuilder;
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceReg = new HashMap<>();
@Activate
public SleepIQHandlerFactory(@Reference ClientBuilder clientBuilder) {
this.clientBuilder = clientBuilder;
}
@Override
public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID);
@ -62,7 +73,7 @@ public class SleepIQHandlerFactory extends BaseThingHandlerFactory {
if (SleepIQCloudHandler.SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID)) {
logger.debug("Creating SleepIQ cloud thing handler");
SleepIQCloudHandler cloudHandler = new SleepIQCloudHandler((Bridge) thing);
SleepIQCloudHandler cloudHandler = new SleepIQCloudHandler((Bridge) thing, clientBuilder);
registerBedDiscoveryService(cloudHandler);
return cloudHandler;
} else if (SleepIQDualBedHandler.SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID)) {

View File

@ -25,6 +25,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.client.ClientBuilder;
import org.openhab.binding.sleepiq.api.Configuration;
import org.openhab.binding.sleepiq.api.LoginException;
import org.openhab.binding.sleepiq.api.SleepIQ;
@ -67,8 +69,11 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
private SleepIQ cloud;
public SleepIQCloudHandler(final Bridge bridge) {
private ClientBuilder clientBuilder;
public SleepIQCloudHandler(final Bridge bridge, ClientBuilder clientBuilder) {
super(bridge);
this.clientBuilder = clientBuilder;
}
@Override
@ -100,6 +105,8 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
/**
* Create a new SleepIQ cloud service connection. If a connection already exists, it will be lost.
*
* @param clientBuilder2
*
* @throws LoginException if there is an error while authenticating to the service
*/
private void createCloudConnection() throws LoginException {
@ -109,7 +116,7 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
logger.debug("Creating SleepIQ client");
Configuration cloudConfig = new Configuration().withUsername(bindingConfig.username)
.withPassword(bindingConfig.password).withLogging(logger.isDebugEnabled());
cloud = SleepIQ.create(cloudConfig);
cloud = SleepIQ.create(cloudConfig, clientBuilder);
logger.debug("Authenticating at the SleepIQ cloud service");
cloud.login();