/**
* *****************************************************************************
*
* Copyright (c) 2012 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Winston Prakash
*
*****************************************************************************
*/
package org.eclipse.hudson.plugins;
import hudson.ProxyConfiguration;
import hudson.model.Messages;
import hudson.util.TextFile;
import hudson.util.VersionNumber;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
/**
* Utility that manages Update Site and local copy of Update Site. Provides
* information about plugins available for installation and their updates
*
* @since 3.0.0
* @author Winston Prakash
*/
public final class UpdateSiteManager {
public static final String COMPATIBILITY = "compatibility";
public static final String FEATURED = "featured";
public static final String RECOMMENDED = "recommended";
public static final String OBSOLETE = "obsolete";
private Map<String, AvailablePluginInfo> availablePluginInfos = new TreeMap<String, AvailablePluginInfo>(String.CASE_INSENSITIVE_ORDER);
private final String updateServer = System.getProperty("updateServer", "http://hudson-ci.org/update-center3.3.2/");
private String updateSiteUrl = updateServer + "update-center.json";
private ProxyConfiguration proxyConfig;
private final String id = "default";
private File hudsonHomeDir;
private Set<String> pluginCategories = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
public UpdateSiteManager(String id, File homeDir, ProxyConfiguration proxyConfig) throws IOException {
hudsonHomeDir = homeDir;
this.proxyConfig = proxyConfig;
refresh();
}
public String getId() {
return id;
}
public Set<AvailablePluginInfo> searchPlugins(String patternStr, boolean searchDescription) {
Set<AvailablePluginInfo> availablePlugins = new TreeSet<AvailablePluginInfo>();
if ((patternStr != null) && !"".equals(patternStr)) {
Pattern pattern = Pattern.compile(patternStr, Pattern.CASE_INSENSITIVE);
Set<String> availablePluginNames = getAvailablePluginNames();
for (String pluginName : availablePluginNames) {
AvailablePluginInfo availablePlugin = getAvailablePlugin(pluginName);
Matcher matcher;
String displayName = availablePlugin.getDisplayName();
if ((displayName != null) && !"".equals(displayName)) {
matcher = pattern.matcher(displayName);
if (matcher.find() && !availablePlugin.isObsolete()) {
availablePlugins.add(availablePlugin);
}
}
String description = availablePlugin.getDescription();
if (searchDescription && (description != null) && !"".equals(description)) {
matcher = pattern.matcher(description);
if (matcher.find() && !availablePlugin.isObsolete()) {
availablePlugins.add(availablePlugin);
}
}
}
}
return availablePlugins;
}
public List<AvailablePluginInfo> getAvailablePlugins(String pluginType) {
List<AvailablePluginInfo> availablePlugins = new ArrayList<AvailablePluginInfo>();
Set<String> availablePluginNames = getAvailablePluginNames();
for (String pluginName : availablePluginNames) {
AvailablePluginInfo availablePlugin = getAvailablePlugin(pluginName);
String type = availablePlugin.getType();
if (pluginType.equals(type) && !availablePlugin.isObsolete()) {
availablePlugins.add(availablePlugin);
}
}
return availablePlugins;
}
public List<AvailablePluginInfo> getCategorizedAvailablePlugins(String pluginType, String category) {
List<AvailablePluginInfo> availablePlugins = new ArrayList<AvailablePluginInfo>();
for (AvailablePluginInfo plugin : getAvailablePlugins(pluginType)) {
if (plugin.isBelongToCategory(category) && !plugin.isObsolete()) {
availablePlugins.add(plugin);
}
}
return availablePlugins;
}
public void verifyUpdateSite(String remoteUrl) throws IOException {
InputStream inputStream;
try {
URL updateCenterRemoteUrl = new URL(remoteUrl);
inputStream = proxyConfig.openUrl(updateCenterRemoteUrl).getInputStream();
} catch (Exception exc) {
throw new IOException("Could not connect to " + remoteUrl + ". "
+ "If you are behind a firewall set HTTP proxy and try again.");
}
String jsonStr = IOUtils.toString(inputStream);
jsonStr = jsonStr.trim();
if (jsonStr.startsWith("updateCenter.post(")) {
jsonStr = jsonStr.substring("updateCenter.post(".length());
} else {
throw new IOException(remoteUrl + " does not seem to be a valid update site");
}
if (jsonStr.endsWith(");")) {
jsonStr = jsonStr.substring(0, jsonStr.lastIndexOf(");"));
}
parseJson(jsonStr);
}
public void setUpdateSiteUrl(String remoteUrl) {
updateSiteUrl = remoteUrl;
}
public Set<String> getPluginCategories() {
return pluginCategories;
}
public void refreshFromUpdateSite() throws IOException {
InputStream inputStream;
try {
URL updateCenterRemoteUrl = new URL(getUpdateSiteUrl());
inputStream = proxyConfig.openUrl(updateCenterRemoteUrl).getInputStream();
} catch (Exception exc) {
throw new IOException("Could not connect to " + getUpdateSiteUrl() + ". "
+ "If you are behind a firewall set HTTP proxy and try again.");
}
String jsonStr = IOUtils.toString(inputStream);
jsonStr = jsonStr.trim();
if (jsonStr.startsWith("updateCenter.post(")) {
jsonStr = jsonStr.substring("updateCenter.post(".length());
}
if (jsonStr.endsWith(");")) {
jsonStr = jsonStr.substring(0, jsonStr.lastIndexOf(");"));
}
availablePluginInfos = parseJson(jsonStr);
getLocalCacheFile().write(jsonStr);
}
public String getUpdateSiteUrl() throws IOException {
return updateSiteUrl;
}
public Set<String> getAvailablePluginNames() {
return availablePluginInfos.keySet();
}
public AvailablePluginInfo getAvailablePlugin(String name) {
return availablePluginInfos.get(name);
}
public String getCategoryDisplayName(String category) {
if (category == null) {
return Messages.UpdateCenter_PluginCategory_misc();
}
try {
return (String) Messages.class.getMethod(
"UpdateCenter_PluginCategory_" + category.replace('-', '_')).invoke(null);
} catch (Exception ex) {
return Messages.UpdateCenter_PluginCategory_unrecognized(category);
}
}
public void refresh() throws IOException{
if (getLocalCacheFile().exists()) {
availablePluginInfos = parseJson(getLocalCacheFile().readTrim());
}
}
private Map<String, AvailablePluginInfo> parseJson(String jsonString) throws IOException {
Map<String, AvailablePluginInfo> pluginInfos = new TreeMap<String, AvailablePluginInfo>(String.CASE_INSENSITIVE_ORDER);
try {
JSONObject jsonObject = JSONObject.fromObject(jsonString);
for (Map.Entry<String, JSONObject> e : (Set<Map.Entry<String, JSONObject>>) jsonObject.getJSONObject("plugins").entrySet()) {
AvailablePluginInfo pluginInfo = new AvailablePluginInfo(e.getValue());
if (!"disabled".equals(pluginInfo.getType())) {
pluginInfos.put(e.getKey(), pluginInfo);
}
}
} catch (Exception exc) {
System.out.println(jsonString);
throw new IOException("Incorrect Update Center JSON. " + exc.getLocalizedMessage());
}
return pluginInfos;
}
AvailablePluginInfo createAvailablePluginInfo(String name, String version, String displayName, String wikiUrl){
return new AvailablePluginInfo(name, version, displayName, wikiUrl);
}
public final class AvailablePluginInfo implements Comparable<AvailablePluginInfo> {
private String name;
private String version;
private String downloadUrl;
private String wikiUrl;
private String displayName;
private String description = "";
private String type;
private List<String> categories = new ArrayList<String>();
private String requiredCore;
private String compatibleSinceVersion;
private Map<String, String> dependencies = new HashMap<String, String>();
public AvailablePluginInfo(JSONObject jsonObject) {
parseJsonObject(jsonObject);
}
public AvailablePluginInfo(String name, String version, String displayName, String wikiUrl){
this.name = name;
this.version = version;
this.displayName = displayName;
this.wikiUrl = wikiUrl;
}
public String getDisplayName() {
if (displayName != null) {
return displayName;
}
return name;
}
public List<String> getCategories() {
return categories;
}
public Map<String, String> getDependencies() {
return dependencies;
}
public String getDescription() {
return description;
}
public String getName() {
return name;
}
public String getDownloadUrl() {
return downloadUrl;
}
public String getWikiUrl() {
return wikiUrl;
}
public String getVersion() {
return version;
}
public String getType() {
return type;
}
public boolean isObsolete(){
return OBSOLETE.equals(type);
}
public String getRequiredCore() {
return requiredCore;
}
VersionNumber getRequiredCoreVersion(){
if ((requiredCore != null) && !"".equals(requiredCore)) {
return new VersionNumber(requiredCore);
}
return null;
}
public String getCompatibleSinceVersion() {
return compatibleSinceVersion;
}
@Override
public String toString() {
return "[Plugin Name:" + name + " Display Name:" + displayName + " Version:" + version + " Wiki Url:" + wikiUrl + "Download Url:" + downloadUrl + "]";
}
private void parseJsonObject(JSONObject jsonObject) {
name = jsonObject.getString("name");
version = jsonObject.getString("version");
downloadUrl = jsonObject.getString("url");
wikiUrl = get(jsonObject, "wiki");
displayName = get(jsonObject, "title");
description = get(jsonObject, "excerpt");
requiredCore = get(jsonObject, "requiredCore");
compatibleSinceVersion = get(jsonObject, "compatibleSinceVersion");
type = get(jsonObject, "type");
if ((type == null) || "".equals(type)) {
type = "others";
}
if (jsonObject.has("labels")) {
String[] labels = (String[]) jsonObject.getJSONArray("labels").toArray(new String[0]);
for (String label : labels) {
String category = getCategoryDisplayName(label);
if (!pluginCategories.contains(category)) {
pluginCategories.add(category);
}
categories.add(category);
}
} else {
categories.add("Uncategorized");
}
for (String category : categories) {
if (!pluginCategories.contains(category)) {
pluginCategories.add(category);
}
}
for (Object jo : jsonObject.getJSONArray("dependencies")) {
JSONObject depObj = (JSONObject) jo;
if (get(depObj, "name") != null
&& get(depObj, "optional").equals("false")) {
dependencies.put(get(depObj, "name"), get(depObj, "version"));
}
}
}
private String get(JSONObject o, String prop) {
if (o.has(prop)) {
String value = o.getString(prop);
if (!"null".equals(value) && !"\"null\"".equals(value)) {
return value;
}
}
return null;
}
boolean isBelongToCategory(String category) {
if (categories != null) {
for (String cat : categories) {
if (category.equalsIgnoreCase(cat)) {
return true;
}
}
}
return false;
}
@Override
public int compareTo(AvailablePluginInfo t) {
return this.getName().compareToIgnoreCase(t.getName());
}
}
private TextFile getLocalCacheFile() {
return new TextFile(new File(hudsonHomeDir,
"updates/" + getId() + ".json"));
}
}