Java tutorial
/* * Copyright (c) 2015 by the author(s). * * 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.thevortex.lighting.jinks.icons; import java.io.IOException; import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.thevortex.lighting.jinks.client.WinkClient; import org.thevortex.lighting.jinks.client.WinkService; /** * Get icons, because that's all that can be done. This service has a built-in cache that refreshes in a background * thread with a configurable period of time (default 12 hours). The updates start after the first request for any * icon(s). * <p> * If a cache type is specified, the image bytes will be fetched and cached as necessary/where possible. * </p> * * @author E. A. Graham Jr. */ public class WinkIconService implements WinkService<WinkIcon> { public static final String GET_URI = "/icons"; private final Logger logger = LoggerFactory.getLogger(this.getClass()); // the cache private final Map<String, WinkIcon> iconCache = new ConcurrentHashMap<>(); private final WinkClient client; private ScheduledExecutorService scheduler = Executors .newSingleThreadScheduledExecutor(r -> new Thread(r, "Wink Icon Cache Refresh")); private Duration timeout = Duration.ofHours(12); private ImageCacheType cacheType; public WinkIconService(WinkClient client) { this.client = client; } @Override public void enableNotifications(boolean enableNotify) { // ignored } /** * Set the timeout. If set to {@code null}, the cache is disabled. * * @param timeout time to wait before a cache checkCache is required */ public void setCacheTimeout(Duration timeout) { this.timeout = timeout; } /** * Sets the cacheType of image to cache. If not set, images are not cached. * * @param type the cacheType */ public void setImageCacheType(ImageCacheType type) { this.cacheType = type; } @Override public void close() { } /** * {@inheritDoc} * <p> * Reads from cache, but refreshes the cache on startup and starts the background scheduled refresh. * </p> */ @Override public synchronized SortedSet<WinkIcon> getAll() throws IOException { checkCache(); return new TreeSet<>(iconCache.values()); } /** * {@inheritDoc} * <p> * Reads from cache, but refreshes the cache on startup and starts the background scheduled refresh. * </p> */ @Override public WinkIcon get(String id) throws IOException { checkCache(); WinkIcon icon = iconCache.get(id); if (icon != null && icon.getImageBytes() == null && cacheType != null) { String url = cacheType.getSelector(icon); icon.setImageBytes(getBytes(url)); } return icon; } @Override public void addServiceChangeListener(ServiceChangeListener<WinkIcon> listener) { // does nothing } @Override public void removeServiceChangeListener(ServiceChangeListener<WinkIcon> listener) { // does nothing } /** * Check to see if the cache needs refreshing. * * @throws IOException can't get it */ private synchronized void checkCache() throws IOException { // if the cache exists, we're done if (!iconCache.isEmpty()) return; // get it and start the background updates updateCache(); Runnable command = () -> { try { WinkIconService.this.updateCache(); // get bytes logger.info("Fetching bytes for empty values."); for (WinkIcon icon : iconCache.values()) { if (!hasBytes(icon.getImageBytes())) { String url = cacheType.getSelector(icon); icon.setImageBytes(WinkIconService.this.getBytes(url)); } } } catch (IOException e) { logger.error("Error updating icon cache", e); } }; scheduler.scheduleAtFixedRate(command, 1, timeout.toMinutes(), TimeUnit.MINUTES); } /** * All the data. * * @return {@code true} if the cache updated * @throws IOException barf */ private boolean updateCache() throws IOException { logger.info("Cache refreshing"); Map<String, WinkIcon> temp = new HashMap<>(); JsonNode node = client.doGet(GET_URI); if (!node.has("data")) return false; ArrayNode jsonList = (ArrayNode) node.get("data"); for (JsonNode jsonNode : jsonList) { WinkIcon icon = WinkClient.MAPPER.convertValue(jsonNode, WinkIcon.class); String id = icon.getId(); temp.put(id, icon); // if the old one exists and the images haven't changed, keep bytes if (iconCache.containsKey(id)) { WinkIcon original = iconCache.get(id); if (!original.stateChanged(icon)) { byte[] imageBytes = original.getImageBytes(); if (hasBytes(imageBytes)) icon.setImageBytes(imageBytes); } } } iconCache.clear(); iconCache.putAll(temp); return true; } private boolean hasBytes(byte[] imageBytes) { return imageBytes != null && imageBytes.length > 0; } /** * Fetch the bytes of the requested image type. * * @param url the location of the image * @return the fetched bytes or a 0-length array as a place-holder */ private synchronized byte[] getBytes(String url) { CloseableHttpClient httpClient = client.getClient(); try { HttpGet get = new HttpGet(url); try (CloseableHttpResponse response = httpClient.execute(get)) { StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); if (statusCode == HttpStatus.SC_OK) { return EntityUtils.toByteArray(response.getEntity()); } logger.error("Response for Icon fetch was {}: {}", statusCode, statusLine.getReasonPhrase()); } } catch (IOException e) { logger.error("Cannot fetch Icon at {}", url, e); } return new byte[0]; } }