/*
* Copyright (c) 2009-2010 Lockheed Martin Corporation
*
* 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.eurekastreams.server.service.opensocial.gadgets.spec;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eurekastreams.server.domain.GeneralGadgetDefinition;
import org.eurekastreams.server.domain.gadgetspec.EnumValuePairDTO;
import org.eurekastreams.server.domain.gadgetspec.GadgetMetaDataDTO;
import org.eurekastreams.server.domain.gadgetspec.UserPrefDTO;
import org.eurekastreams.server.domain.gadgetspec.UserPrefDTO.DataType;
/**
* This class provides the mechanism for retrieving the gadget metadata contained with gadget definitions that are
* supplied.
*
*/
public class GadgetMetaDataHttpFetcher implements GadgetMetaDataFetcher
{
/**
* Base url for the Current App where the metadata request will be made to.
*/
private String currentAppContextBaseUrl;
/**
* Constant byte size for characters.
*/
private static final int CHAR_BYTE = 1024;
/**
* Local logging instance.
*/
private final Log logger = LogFactory.getLog(GadgetMetaDataHttpFetcher.class);
/**
* Setter for the Current Application Context Base Url to be used for retrieving gadget metadata from the container.
*
* @param inCurrentAppContextBaseUrl
* - base url for the gadget metadata fetcher.
*/
public void setCurrentAppContextBaseUrl(final String inCurrentAppContextBaseUrl)
{
currentAppContextBaseUrl = inCurrentAppContextBaseUrl;
}
/**
* Retrieve the gadget metadata for the gadget definitions passed into the class.
*
* @param gadgetDefs
* Map of gadget definitions with the string key as the gadget def url.
* @return List of GadgetMetaData objects that contain the metadata for the Gadget definitions passed in.
* @throws Exception
* Error occurs on retrieving gadget metadata.
*/
public List<GadgetMetaDataDTO> getGadgetsMetaData(final Map<String, GeneralGadgetDefinition> gadgetDefs)
throws Exception
{
if (gadgetDefs.isEmpty())
{
return Collections.EMPTY_LIST;
}
try
{
// Make the http request here.
StringWriter output = new StringWriter();
URL endpoint = new URL(currentAppContextBaseUrl + "/gadgets/metadata");
logger.debug("Target url for metadata request " + endpoint.toString());
HttpURLConnection urlConnection = null;
try
{
// Open the HttpConnection and make the POST.
urlConnection = (HttpURLConnection) endpoint.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setAllowUserInteraction(false);
urlConnection.setRequestProperty("Content-type", "application/json");
OutputStream out = urlConnection.getOutputStream();
try
{
Writer writer = new OutputStreamWriter(out, "UTF-8");
pipe(new StringReader(assembleMetaDataRequestContext(gadgetDefs)), writer);
writer.close();
}
catch (IOException e)
{
String ioErrorMsg = "IOException occurred writing params to outputstream.";
logger.error(ioErrorMsg, e);
throw new Exception(ioErrorMsg);
}
finally
{
if (out != null)
{
out.close();
}
}
// Retrieve the response.
InputStream in = urlConnection.getInputStream();
try
{
Reader reader = new InputStreamReader(in);
pipe(reader, output);
reader.close();
}
catch (IOException iox)
{
String ioErrorMsg = "IOException occurred reading response from request";
logger.error(ioErrorMsg, iox);
throw iox;
}
finally
{
if (in != null)
{
in.close();
}
}
logger.debug("This is the output of the MetaData request: " + output.toString());
}
catch (IOException ioex)
{
throw ioex;
}
finally
{
if (urlConnection != null)
{
urlConnection.disconnect();
}
}
return mapGadgetMetaDataJSONToObject(output.toString(), gadgetDefs);
}
catch (Exception ex)
{
String msg = "Error occurred retrieving gadget metadata " + ex;
logger.error(msg, ex);
throw ex;
}
}
/**
* This method converts the MetaData JSON into a List of GadgetMetaData objects.
*
* @param inGadgetsJSON
* raw results from the metadata request.
* @param gadgetDefs
* Map of gadget definitions with the string key as the gadget def url.
* @return List of GadgetMetaData objects from the request.
*/
private List<GadgetMetaDataDTO> mapGadgetMetaDataJSONToObject(final String inGadgetsJSON,
final Map<String, GeneralGadgetDefinition> gadgetDefs)
{
List<GadgetMetaDataDTO> currentGadgetMetaData = new ArrayList<GadgetMetaDataDTO>();
JSONObject gadgetsMetaData = JSONObject.fromObject(inGadgetsJSON.toString());
JSONArray gadgetsJSON = gadgetsMetaData.getJSONArray("gadgets");
GadgetMetaDataDTO currentGadget;
for (int index = 0; index < gadgetsJSON.size(); index++)
{
JSONObject gadgetJSON = gadgetsJSON.getJSONObject(index);
currentGadget = new GadgetMetaDataDTO(gadgetDefs.get(gadgetJSON.get("url")));
// set the user prefs here as a list.
currentGadget.setTitle(gadgetJSON.containsKey("title") ? gadgetJSON.getString("title") : "");
currentGadget.setAuthor(gadgetJSON.containsKey("author") ? gadgetJSON.getString("author") : "");
currentGadget.setDescription(gadgetJSON.containsKey("description") ? gadgetJSON.getString("description")
: "");
JSONObject gadgetUserPrefsJSON = gadgetJSON.getJSONObject("userPrefs");
List<UserPrefDTO> userPrefs = new ArrayList<UserPrefDTO>();
UserPrefDTO currentUserPref;
JSONObject currentUserPrefJSON;
String currentUserPrefKey;
for (Object userPrefKey : gadgetUserPrefsJSON.keySet())
{
currentUserPrefKey = (String) userPrefKey;
currentUserPrefJSON = gadgetUserPrefsJSON.getJSONObject(currentUserPrefKey);
currentUserPref = new UserPrefDTO();
currentUserPref.setName(currentUserPrefKey);
currentUserPref.setDisplayName(currentUserPrefJSON.getString("displayName"));
currentUserPref.setDataType(currentUserPrefJSON.getString("type"));
currentUserPref.setDefaultValue(currentUserPrefJSON.getString("default"));
if (currentUserPref.getDataType().name() == DataType.ENUM.name())
{
// Unordered easy access map of enum values.
Map<String, String> enumValues = new HashMap<String, String>();
String enumKey;
String userPrefEnumValue;
// parse enum values here
for (Object enumKeyObject : currentUserPrefJSON.getJSONObject("enumValues").keySet())
{
enumKey = (String) enumKeyObject;
userPrefEnumValue = (String) currentUserPrefJSON.getJSONObject("enumValues").get(enumKey);
enumValues.put(enumKey, userPrefEnumValue);
}
currentUserPref.setEnumValues(enumValues);
// Ordered enum values for creating preferences.
List<EnumValuePairDTO> orderedEnumValues = new LinkedList<EnumValuePairDTO>();
JSONArray orderedEnumValuesJson = currentUserPrefJSON.getJSONArray("orderedEnumValues");
JSONObject currentOrderedEnumValueJson;
for (int arrayIndex = 0; arrayIndex < orderedEnumValuesJson.size(); arrayIndex++)
{
currentOrderedEnumValueJson = orderedEnumValuesJson.getJSONObject(arrayIndex);
orderedEnumValues.add(new EnumValuePairDTO(currentOrderedEnumValueJson.getString("value"),
currentOrderedEnumValueJson.getString("displayValue")));
}
currentUserPref.setOrderedEnumValues(orderedEnumValues);
}
if (currentUserPrefJSON.containsKey("required"))
{
currentUserPref.setRequired(currentUserPrefJSON.getString("required"));
}
userPrefs.add(currentUserPref);
}
currentGadget.setUserPrefs(userPrefs);
currentGadgetMetaData.add(currentGadget);
}
return currentGadgetMetaData;
}
/**
* This method assembles the context for the metadata request to the OpenSocial container.
*
* @param gadgetDefs
* Map of gadget definitions with the string key as the gadget def url.
* @return String representation of the JSON Context.
*/
private String assembleMetaDataRequestContext(final Map<String, GeneralGadgetDefinition> gadgetDefs)
{
// Assemble the json for requesting the metadata for a gadget.
JSONObject metaDataParams = new JSONObject();
JSONObject contextParams = new JSONObject();
contextParams.put("country", "default");
contextParams.put("language", "default");
contextParams.put("view", "preview");
contextParams.put("container", "eureka");
metaDataParams.put("context", contextParams);
JSONArray gadgetParams = new JSONArray();
JSONObject gadgetDefParam;
for (Entry<String, GeneralGadgetDefinition> currentGadgetDef : gadgetDefs.entrySet())
{
gadgetDefParam = new JSONObject();
gadgetDefParam.put("url", currentGadgetDef.getKey());
gadgetDefParam.put("moduleId", currentGadgetDef.getValue().getId());
gadgetParams.add(gadgetDefParam);
}
metaDataParams.put("gadgets", gadgetParams);
logger.debug("Created JSON for metadata request: " + metaDataParams.toString());
return metaDataParams.toString();
}
/**
* Helper method for copying a Reader into a writer.
*
* @param reader
* - containing the reader content to be written.
* @param writer
* - target writer.
* @throws IOException
* on stream errors.
*/
private static void pipe(final Reader reader, final Writer writer) throws IOException
{
char[] buf = new char[CHAR_BYTE];
int read = 0;
while ((read = reader.read(buf)) >= 0)
{
writer.write(buf, 0, read);
}
writer.flush();
}
}