Java tutorial
/* Copyright (C) 2011 Alasdair Mercer, http://neocotic.com/ * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.neocotic.blingaer.link; import java.io.File; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.net.URL; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.maxmind.geoip.Country; import com.maxmind.geoip.LookupService; import com.neocotic.blingaer.account.Account; import com.neocotic.blingaer.application.Application; import com.neocotic.blingaer.common.HashUtils; import com.neocotic.blingaer.common.IOUtils; import com.neocotic.blingaer.common.dao.DAOLocator; import com.neocotic.blingaer.common.validation.ValidationResult; import com.neocotic.blingaer.common.validation.ValidationRule; import com.neocotic.blingaer.common.validation.Validator; import com.neocotic.blingaer.common.validation.rule.StringValidationRule; import com.neocotic.blingaer.common.validation.rule.URLValidationRule; import com.neocotic.blingaer.link.dao.LinkDAO; /** * An implementation of {@link LinkService} that provides the promised * functionality. * * @author Alasdair Mercer */ // TODO: Test code public class LinkServiceImpl implements LinkService { /** The {@code Logger} instance for {@link LinkServiceImpl}. */ private static final Logger LOG = Logger.getLogger(LinkServiceImpl.class); /** The path to MaxMind's GeoIP API data {@code File}. */ private static final String DATA_FILE_PATH = "/WEB-INF/GeoIP.dat"; private LookupService lookup; private List<ValidationRule> urlValidationRules; /** * Provides quick and easy access to the {@link LinkDAO}. * * @return an instance of {@code LinkDAO} */ private LinkDAO dao() { return DAOLocator.getInstance().getDAO(LinkDAO.class); } /* * @see LinkService#fetchClick(Long) */ @Override public Click fetchClick(Long id) { LOG.trace("Entered method - fetchClick"); Click click = null; try { click = dao().getClickForId(id); } catch (LinkServiceException e) { LOG.debug("Swallowed exception", e); } LOG.trace("Exiting method - fetchClick"); return click; } /* * @see LinkService#fetchClicks(Link) */ @Override public List<Click> fetchClicks(Link link) { return dao().getClicksForLink(link); } /* * @see LinkService#fetchGlobalLink(Link) */ @Override public Link fetchGlobalLink(Link link) { LOG.trace("Entered method - fetchGlobalLink"); Link globalLink = null; if (link.getParent() != null) { try { globalLink = dao().getParentLink(link); } catch (LinkServiceException e) { LOG.debug("Swallowed exception", e); } } LOG.trace("Exiting method - fetchGlobalLink"); return globalLink; } /* * @see LinkService#fetchCountry(String) */ @Override public Country fetchCountry(String ipAddress) throws LinkServiceException { return lookup().getCountry(ipAddress); } /* * @see LinkService#fetchGlobalLink(String) */ @Override public Link fetchGlobalLink(String url) throws IllegalArgumentException { if (validateUrl(url).hasErrors()) { throw new IllegalArgumentException("Invalid URL"); } return dao().getGlobalLinkForUrl(url); } /* * @see LinkService#fetchLink(Long) */ @Override public Link fetchLink(Long id) { LOG.trace("Entered method - fetchLink"); Link link = null; try { link = dao().getLinkForId(id); } catch (LinkServiceException e) { LOG.debug("Swallowed exception", e); } LOG.trace("Exiting method - fetchLink"); return link; } /* * @see LinkService#fetchLinkForHash(String) */ @Override public Link fetchLinkForHash(String hash) { return LinkServiceFactory.getLinkService().fetchLink(HashUtils.toNumber(hash)); } /* * @see LinkService#fetchLinkForShortUrl(String) */ @Override public Link fetchLinkForShortUrl(String shortUrl) throws IllegalArgumentException { if (validateUrl(shortUrl).hasErrors()) { throw new IllegalArgumentException("Invalid URL"); } return LinkServiceFactory.getLinkService() .fetchLink(HashUtils.toNumber(StringUtils.substringAfterLast(shortUrl, "/"))); } /* * @see LinkService#fetchLinks(Account) */ @Override public List<Link> fetchLinks(Account account) { return dao().getLinksForAccount(account); } /* * @see LinkService#fetchLinks(Application) */ @Override public List<Link> fetchLinks(Application application) { return dao().getLinksForApplication(application); } /* * @see LinkService#fetchLinks(String) */ @Override public List<Link> fetchLinks(String url) throws IllegalArgumentException { if (validateUrl(url).hasErrors()) { throw new IllegalArgumentException("Invalid URL"); } return dao().getLinksForUrl(url); } /* * @see LinkService#fetchTitle(String) */ @Override public String fetchTitle(String url) throws IllegalArgumentException { LOG.trace("Entered method - fetchTitle"); if (validateUrl(url).hasErrors()) { throw new IllegalArgumentException("Invalid URL"); } String content = null; Reader reader = null; try { reader = new InputStreamReader(new URL(url).openStream()); content = IOUtils.toString(reader); reader.close(); } catch (Exception e) { LOG.debug("Swallowed exception", e); } finally { IOUtils.closeQuietly(reader); } // Use URL if content could not be retrieved if (content == null) { return url; } // Attempts to extract charset declared in the HTML String charSet = null; Matcher matcher = Pattern.compile("/<meta[^>]*?charset=([^>]*?)\\/?>/is").matcher(content); while (matcher.find()) { charSet = StringUtils.trimToNull(matcher.group()); break; } // Attempts to extract title declared in the HTML String title = null; matcher = Pattern.compile("/<title>(.*?)<\\/title>/is").matcher(content); while (matcher.find()) { title = StringUtils.trimToNull(matcher.group()); break; } // If title wasn't found uses an error message (if applicable) or URL if (title == null) { title = (content.indexOf("Error") == 0) ? content : url; } // Attempts charset conversion to UTF-8 try { if (charSet == null) { title = new String(title.getBytes(), "UTF-8"); } else { title = new String(title.getBytes(charSet), "UTF-8"); } } catch (UnsupportedEncodingException e) { LOG.debug("Swallowed exception", e); } // Removes HTML entities title = StringEscapeUtils.unescapeHtml4(title); // Removes all tags title = title.replaceAll("<(.|\\n)*?>", StringUtils.EMPTY); LOG.trace("Exiting method - fetchTitle"); return title; } /** * Returns the {@code LookupService} used by MaxMind's GeoIP API to query * their data {@code File}. * <p> * The result is cached internally by this {@link LikeServiceImpl} to * improve performance of subsequent calls to this method. * * @return the GeoIP {@code LookupService} * @throws LinkServiceException * If the data {@code File} could not be accessed. */ private LookupService lookup() throws LinkServiceException { LOG.trace("Entered method - lookup"); if (lookup == null) { try { // TODO: Confirm GEOIP_MEMORY_CACHE is best option for GAE lookup = new LookupService(new File(LinkServiceImpl.class.getResource(DATA_FILE_PATH).toURI()), LookupService.GEOIP_MEMORY_CACHE); } catch (Exception e) { LOG.debug("Throwing exception", e); throw new LinkServiceException(e); } } LOG.trace("Exiting method - lookup"); return lookup; } /* * @see LinkService#removeLink(Link) */ @Override public void removeLink(Link link) { dao().deleteLink(link); } /* * @see LinkService#storeLink(Link) */ @Override public Link storeLink(Link link) { return dao().updateLink(link); } /** * Validates the specified URL specification. * * @param url * the URL to be validated * @return the {@link ValidationResult result} of the validation */ private ValidationResult validateUrl(String url) { if (urlValidationRules == null) { urlValidationRules.add(new StringValidationRule()); urlValidationRules.add(new URLValidationRule()); } return Validator.validate(url, urlValidationRules); } }