/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2012 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.extension;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.commons.io.FileUtils;
import org.json.JSONArray;
import com.servoy.extension.parser.ParseDependencyMetadata;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.Pair;
import com.servoy.j2db.util.Utils;
/**
* This class provides extension info & data taken from the Servoy Marketplace
* @author gboros
*/
public class MarketPlaceExtensionProvider extends CachingExtensionProvider
{
public static final String MARKETPLACE_HOST;
static
{
MARKETPLACE_HOST = System.getProperty("market_place_host_url", "https://crm.servoy.com"); //$NON-NLS-1$ //$NON-NLS-2$
}
public static final String MARKETPLACE_WS = MARKETPLACE_HOST + "/servoy-service/rest_ws/marketplace/ws_extensions/"; //$NON-NLS-1$
private static final String WS_ACTION_VERSIONS = "versions"; //$NON-NLS-1$
private static final String WS_ACTION_EXP = "exp"; //$NON-NLS-1$
private static final String WS_ACTION_PACKAGE_XML = "xml"; //$NON-NLS-1$
private static final String TO_BE_INSTALLED_FOLDER = ".ws"; //$NON-NLS-1$
private final File destinationDir;
private final HashMap<String, String[]> availableVersionsMap = new HashMap<String, String[]>();
private final HashMap<Pair<String, String>, File> expFileMap = new HashMap<Pair<String, String>, File>();
private final MessageKeeper messages = new MessageKeeper();
public MarketPlaceExtensionProvider(File installDir)
{
destinationDir = new File(new File(installDir, ExtensionUtils.EXPFILES_FOLDER), TO_BE_INSTALLED_FOLDER);
}
public String[] getAvailableVersions(String extensionID)
{
if (!availableVersionsMap.containsKey(extensionID)) availableVersionsMap.put(extensionID, ws_getVersions(extensionID));
return availableVersionsMap.get(extensionID);
}
public File getEXPFile(String extensionId, String version, IProgress progressMonitor)
{
Pair<String, String> extVersion = new Pair<String, String>(extensionId, version);
if (!expFileMap.containsKey(extVersion))
{
expFileMap.put(extVersion, ws_getEXP(extensionId, version, progressMonitor));
if (progressMonitor != null && progressMonitor.shouldCancelOperation())
{
expFileMap.remove(extVersion); // don't cache it then
}
}
return expFileMap.get(extVersion);
}
@Override
protected DependencyMetadata[] getDependencyMetadataImpl(ExtensionDependencyDeclaration extensionDependency)
{
ArrayList<DependencyMetadata> dmA = new ArrayList<DependencyMetadata>();
for (String version : getAvailableVersions(extensionDependency.id))
{
if (VersionStringUtils.belongsToInterval(version, extensionDependency.minVersion, extensionDependency.maxVersion))
{
BufferedInputStream bis = null;
try
{
URLConnection ws_connection = ws_getConnection(WS_ACTION_PACKAGE_XML, "application/binary", extensionDependency.id, version); //$NON-NLS-1$
bis = new BufferedInputStream(ws_connection.getInputStream());
ParseDependencyMetadata parseDM = new ParseDependencyMetadata(extensionDependency.id, messages);
dmA.add(parseDM.runOnEntryInputStream(bis));
}
catch (Exception ex)
{
String msg = "Cannot get extension definition from marketplace. Error is : " + ex.getMessage(); //$NON-NLS-1$
Debug.error(ex);
messages.addError(msg);
}
finally
{
Utils.closeInputStream(bis);
}
}
}
return dmA.toArray(new DependencyMetadata[dmA.size()]);
}
public void dispose()
{
messages.clearMessages();
availableVersionsMap.clear();
flushCache();
FileUtils.deleteQuietly(destinationDir);
expFileMap.clear();
}
public Message[] getMessages()
{
return messages.getMessages();
}
public void clearMessages()
{
messages.clearMessages();
}
private String[] ws_getVersions(String extensionId)
{
ArrayList<String> versions = new ArrayList<String>();
ByteArrayOutputStream bos = null;
BufferedInputStream bis = null;
try
{
URLConnection ws_connection = ws_getConnection(WS_ACTION_VERSIONS, "text/json", extensionId, null); //$NON-NLS-1$
bos = new ByteArrayOutputStream();
bis = new BufferedInputStream(ws_connection.getInputStream());
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1)
bos.write(buffer, 0, len);
String encoding = getCharset(ws_connection.getContentType());
if (encoding == null) encoding = "UTF-8"; //$NON-NLS-1$
JSONArray jsonVersions = new JSONArray(new String(bos.toByteArray(), encoding));
for (int i = 0; i < jsonVersions.length(); i++)
versions.add(jsonVersions.getString(i));
}
catch (Exception ex)
{
String msg = "Cannot get extension versions from marketplace. Error is : " + ex.getMessage(); //$NON-NLS-1$
Debug.error(ex);
messages.addError(msg);
}
finally
{
Utils.closeInputStream(bis);
Utils.closeOutputStream(bos);
}
return versions.toArray(new String[versions.size()]);
}
private File ws_getEXP(String extensionId, String version, IProgress progressMonitor)
{
BufferedInputStream bis = null;
FileOutputStream fos = null;
File outputFile = new File(destinationDir, extensionId + "_" + version + ".exp"); //$NON-NLS-1$ //$NON-NLS-2$
try
{
destinationDir.mkdirs();
URLConnection ws_connection = ws_getConnection(WS_ACTION_EXP, "application/binary", extensionId, version); //$NON-NLS-1$
fos = new FileOutputStream(outputFile);
bis = new BufferedInputStream(ws_connection.getInputStream());
int total = ws_connection.getContentLength();
if (progressMonitor != null)
{
progressMonitor.setStatusMessage("0 KB of " + getSizeString(total) + "..."); //$NON-NLS-1$//$NON-NLS-2$
progressMonitor.start(total);
}
byte[] buffer = new byte[1091];
int len;
int downloaded = 0;
long lastLabelUpdateTime = System.currentTimeMillis();
while ((len = bis.read(buffer)) != -1)
{
fos.write(buffer, 0, len);
if (progressMonitor != null)
{
downloaded += len;
progressMonitor.worked(len);
if (System.currentTimeMillis() - lastLabelUpdateTime > 500)
{
// do not update label faster then 0.5 sec - it looks strange
progressMonitor.setStatusMessage(getSizeString(downloaded) + " of " + getSizeString(total) + "..."); //$NON-NLS-1$//$NON-NLS-2$
lastLabelUpdateTime = System.currentTimeMillis();
}
if (progressMonitor.shouldCancelOperation()) break;
}
}
}
catch (Exception ex)
{
String msg = "Cannot get extension file from marketplace. Error is : " + ex.getMessage(); //$NON-NLS-1$
Debug.error(ex);
messages.addError(msg);
}
finally
{
Utils.closeInputStream(bis);
Utils.closeOutputStream(fos);
}
if (progressMonitor != null && progressMonitor.shouldCancelOperation())
{
FileUtils.deleteQuietly(outputFile);
outputFile = null;
}
return outputFile;
}
private String getCharset(String contentType)
{
String charset = null;
if (contentType != null)
{
String[] split = contentType.split("; *"); //$NON-NLS-1$
for (String element : split)
{
if (element.toLowerCase().startsWith("charset=")) //$NON-NLS-1$
{
charset = element.substring("charset=".length()); //$NON-NLS-1$
if (charset.length() > 1 && charset.charAt(0) == '"' && charset.charAt(charset.length() - 1) == '"')
{
charset = charset.substring(1, charset.length() - 1);
}
return charset;
}
}
}
return charset;
}
private String getSizeString(int size)
{
String unit;
double value;
if (size > 1024 * 1024)
{
unit = " MB"; //$NON-NLS-1$
value = ((double)size) / 1024 / 1024;
}
else
{
unit = " KB"; //$NON-NLS-1$
value = ((double)size) / 1024;
}
BigDecimal bd = new BigDecimal(value);
bd = bd.setScale(2, BigDecimal.ROUND_HALF_UP);
return bd.toPlainString() + unit;
}
private URLConnection ws_getConnection(String action, String acceptContentType, String extensionId, String version) throws Exception
{
String unescapedURL = MARKETPLACE_WS + action + "/" + extensionId + (version != null ? "/" + version : "")/* + "?nodebug=true" */; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
URL mpURL = new URI(null, null, unescapedURL, null).toURL(); // the URI should escape it correctly
URLConnection urlConnection = mpURL.openConnection();
urlConnection.addRequestProperty("accept", acceptContentType); //$NON-NLS-1$
urlConnection.setConnectTimeout(30 * 1000); // don't make an unstoppable job if the network connection is down
urlConnection.setReadTimeout(30 * 1000); // don't make an unstoppable job if the network connection is down or server doesn't want to respond
return urlConnection;
}
}