/* 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 com.esri.gpt.catalog.arcgis.agportal.publication;
import com.esri.gpt.catalog.schema.MetadataDocument;
import com.esri.gpt.catalog.schema.Schema;
import com.esri.gpt.framework.collection.StringAttributeMap;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.http.HttpClientRequest.MethodName;
import com.esri.gpt.framework.http.*;
import com.esri.gpt.framework.http.multipart.MultiPartContentProvider;
import com.esri.gpt.framework.util.Val;
import com.esri.gpt.framework.xml.DomUtil;
import com.esri.gpt.framework.xml.NodeListAdapter;
import com.esri.gpt.framework.xml.XsltTemplate;
import com.esri.gpt.framework.xml.XsltTemplates;
import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
/**
* Base ArcGIS Portal request.
*/
public abstract class AgpBaseRequest {
/** temporary storage file prefix */
private static final String PREFIX = "file";
/** temporary storage file suffix */
private static final String SUFFIX = ".data";
/** default sub-folder for the intermediate report files */
private static final String SUBFOLDER = "/geoportal/downloads";
protected static XsltTemplates XSLTTEMPLATES = new XsltTemplates();
protected CredentialProvider credentialProvider = null;
protected RequestContext requestContext = null;
protected EndPoint ep;
/**
* Creates instance of the request.
*
* @param requestContext request context
* @param endPoint ArcGIS Portal end point
* @param credtialProvider credential provider
*/
public AgpBaseRequest(RequestContext requestContext, CredentialProvider credtialProvider, EndPoint endPoint) {
if (credtialProvider == null) {
throw new IllegalArgumentException("Null credentials provided.");
}
if (requestContext == null) {
throw new IllegalArgumentException("Null request context provided.");
}
if (endPoint == null) {
throw new IllegalArgumentException("Null end point provided.");
}
this.credentialProvider = credtialProvider;
this.requestContext = requestContext;
this.ep = endPoint;
}
/**
* Fetch ArcGIS portal token.
* @throws IOException if communication with the server fails
* @throws JSONException if unable to parse server response
*/
protected String fetchToken() throws AgpServerException, AgpPublishException {
try {
String content = URLEncoder.encode("f", "UTF-8") + "=" + URLEncoder.encode("json", "UTF-8") + "&" + URLEncoder.encode("username", "UTF-8") + "=" + URLEncoder.encode(this.credentialProvider.getUsername(), "UTF-8") + "&" + URLEncoder.encode("password", "UTF-8") + "=" + URLEncoder.encode(this.credentialProvider.getPassword(), "UTF-8");
StringHandler handler = new StringHandler();
HttpClientRequest httpClient = new HttpClientRequest();
// send the request
content += "&expiration=525600&referer=" + ep.getReferer();
httpClient.setContentProvider(new StringProvider(content, "application/x-www-form-urlencoded"));
httpClient.setContentHandler(handler);
httpClient.setRequestHeader("Referer", ep.getReferer());
httpClient.setUrl(this.ep.getGenerateTokenUrl());
httpClient.setMethodName(MethodName.POST);
// execute
execute(httpClient);
checkError(handler.getContent());
String resp = handler.getContent();
JSONObject jResp = new JSONObject(resp);
return jResp.getString("token");
} catch (AgpServerException ex) {
throw ex;
} catch (Exception ex) {
throw new AgpPublishException("Error fetching token.", ex);
}
}
/**
* Executes HTTP request.
* @param request HTTP client request
* @throws AgpServerException server exception
* @throws AgpPublishException publish exception
*/
protected void execute(HttpClientRequest request) throws AgpServerException, AgpPublishException {
try {
request.execute();
int nHttpResponseCode = request.getResponseInfo().getResponseCode();
if ((nHttpResponseCode < 200) || (nHttpResponseCode > 299)) {
throw new AgpServerException(nHttpResponseCode, "Error accessing ArcGIS Portal.");
}
} catch (AgpServerException ex) {
throw ex;
} catch (Exception ex) {
throw new AgpPublishException("Error executing request.", ex);
}
}
/**
* Extracts credential provider for ArcGIS Portal from configuration via
* context.
*
* @param ctx request context
* @return credential provider
*/
protected static CredentialProvider extractCredentialProvider(RequestContext ctx) {
StringAttributeMap params = ctx.getCatalogConfiguration().getParameters();
return new CredentialProvider(
params.get("share.with.arcgis.username").getValue(),
params.get("share.with.arcgis.password").getValue());
}
/**
* Extracts ESRI Item Information from metadata.
*
* @param metadata metadata
* @return DOM node of ESRI Item Information
* @throws AgpPublishException if extracting ESRI Item Information fails
*/
protected Node extractItemInfo(String metadata) throws AgpPublishException {
try {
// find the right schema
MetadataDocument document = new MetadataDocument();
Schema schema = document.prepareForView(requestContext, metadata);
// get transformation file
String toEsriItemInfoXslt = schema.getToEsriItemInfoXslt();
if (toEsriItemInfoXslt.isEmpty()) {
throw new AgpPublishException("Schema: " + schema.getKey() + " has no transformation to ItemInformation.");
}
// run the validation xsl
XsltTemplate template = this.getCompiledTemplate(toEsriItemInfoXslt);
String result = template.transform(metadata);
// load the result SVRL document
Document dom = DomUtil.makeDomFromString(result, true);
// find ESRI item information
XPath xPath = XPathFactory.newInstance().newXPath();
return (Node) xPath.evaluate("ESRI_ItemInformation", dom, XPathConstants.NODE);
} catch (AgpPublishException ex) {
throw ex;
} catch (Exception ex) {
throw new AgpPublishException("Error processing metadata.", ex);
}
}
/**
* Extracts ESRI item attributes.
* @param esriItemInfo DOM node with ESRI item info
* @return map of attributes
*/
protected Map<String, List<String>> extractEsriItemAttributes(Node esriItemInfo) {
AttrMap attributes = new AttrMap();
ItemDesc itemDesc = null;
// go through each child of ESRI item info
for (Node nd : new NodeListAdapter(esriItemInfo.getChildNodes())) {
// get name and value of the attribute
String name = Val.chkStr(nd.getNodeName());
String value = Val.chkStr(nd.getTextContent());
if (!name.isEmpty() && !value.isEmpty()) {
// if the attribute is url check a type of that url
if ("url".equals(name)) {
for (ItemEntry ie : itemEntries) {
if (ie.matches(value)) {
itemDesc = ie.getItemDesc();
break;
}
}
// if the type is downloadable, put 'file' attribute instead of 'url'
if (itemDesc != null && itemDesc.getItemType() == ItemType.file) {
attributes.put("file", value);
} else {
attributes.put(name, value);
}
} else {
attributes.put(name, value);
}
}
}
// if 'type' attribute not found in the ESRI item information (which will be
// the most common case), set that attribute from the item description
if (itemDesc != null && !attributes.containsKey("type")) {
attributes.put("type", itemDesc.getType());
}
if (itemDesc != null && !attributes.containsKey("typekeywords")) {
attributes.put("typekeywords", itemDesc.getTypeKeywords());
}
return attributes;
}
/**
* Process attributes.
* @param provider
* @param attributes
* @throws AgpPublishException
*/
protected void processEsriItemAttributes(MultiPartContentProvider provider, Map<String, List<String>> attributes) throws AgpPublishException {
try {
// go through each attribute and add it to the multi-part provider
for (Map.Entry<String, List<String>> e : attributes.entrySet()) {
// for 'file' though download that file and push to the request
if (!e.getValue().isEmpty()) {
if ("file".equals(e.getKey())) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
StreamHandler handler = new StreamHandler(output);
HttpClientRequest request = new HttpClientRequest();
request.setUrl(e.getValue().get(0));
request.setContentHandler(handler);
request.execute();
// two versions
// 'in memory' version
//provider.add("file", output.toByteArray(), URLDecoder.decode(getFileName(value), "UTF-8"), null, null);
// 'on disk' version
File tempFile = createTempFile();
saveBytesToFile(tempFile, output.toByteArray());
provider.add("file", tempFile, URLDecoder.decode(getFileName(e.getValue().get(0)), "UTF-8"), null, null, true);
// possible third version operating directly on a stream
} else {
provider.add(e.getKey(), e.getValue().get(0));
}
}
}
} catch (IOException ex) {
throw new AgpPublishException("Error processing item information.", ex);
}
}
/**
* Gets file name from the URL.
* @param url URL
* @return file name
* @throws IOException if getting file name fails
*/
private String getFileName(String url) throws IOException {
URL u = new URL(url);
File f = new File(u.getPath());
return f.getName();
}
/**
* Creates temporary file to store downloaded data.
* @return file
* @throws IOException if creating file fails
*/
private File createTempFile() throws IOException {
File directory = new File(System.getProperty("java.io.tmpdir") + SUBFOLDER);
directory.mkdirs();
File storage = File.createTempFile(PREFIX, SUFFIX, directory);
return storage;
}
/**
* Saves downloaded bytes to the temporary file.
* @param file file
* @param bytes bytes
* @throws IOException if saving bytes fails
*/
private void saveBytesToFile(File file, byte[] bytes) throws IOException {
OutputStream output = null;
try {
output = new FileOutputStream(file);
output.write(bytes);
output.flush();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException ex) {
}
}
}
}
/**
* Checks response for errors.
*
* @param response response
* @throws IOException if error found in the response
*/
protected void checkError(String response) throws AgpServerException {
try {
JSONObject jo = new JSONObject(response);
if (jo.has("error")) {
JSONObject error = jo.getJSONObject("error");
String code = error.getString("code");
String message = error.getString("message");
throw new AgpServerException(Val.chkInt(code, -1), message);
}
} catch (JSONException ex) {
// no error in the response; do not throw anything
}
}
/**
* Gets newly created document response
*
* @param response response
* @return id
*/
protected String extractId(String response) throws JSONException {
JSONObject jo = new JSONObject(response);
return jo.getString("id");
}
/**
* Gets a compiled XSLT template.
*
* @param xsltPath the path to an XSLT
* @return the compiled template
* @throws IOException if an IO exception occurs
* @throws TransformerException if a transformation exception occurs
*/
protected synchronized XsltTemplate getCompiledTemplate(String xsltPath) throws TransformerException {
String sKey = xsltPath;
XsltTemplate template = XSLTTEMPLATES.get(sKey);
if (template == null) {
template = XsltTemplate.makeTemplate(xsltPath);
XSLTTEMPLATES.put(sKey, template);
}
return template;
}
/**
* Gets folder URL.
* @param folderId folder id
* @return folder URL
*/
protected String getFolderUrl(String folderId) {
folderId = Val.chkStr(folderId);
return ep.getBaseArcGISUrl() + "/content/users/" + credentialProvider.getUsername() + (!folderId.isEmpty() ? "/" + folderId : "");
}
/**
* Gets item URL.
* @param folderId folder name
* @param itemId item id
* @return item URL
*/
protected String getItemUrl(String folderId, String itemId) {
return getFolderUrl(folderId) + "/items/" + Val.chkStr(itemId);
}
/**
* Known item entries
*/
private static final ArrayList<ItemEntry> itemEntries = new ArrayList<ItemEntry>();
static {
itemEntries.add(new ItemEntry(".*mapserver.*", new ItemDesc("Map Service", ItemType.url, "Data, Service, Map Service, ArcGIS Server")));
itemEntries.add(new ItemEntry(".*imageserver.*", new ItemDesc("Image Service", ItemType.url, "Data, Service, Image Service, ArcGIS Server")));
itemEntries.add(new ItemEntry(".*globeserver.*", new ItemDesc("Globe Service", ItemType.url, "Data, Service, Globe Service, ArcGIS Server")));
itemEntries.add(new ItemEntry(".*gpserver.*", new ItemDesc("Geoprocessing Service", ItemType.url, "Tool, Service, Geoprocessing Service, ArcGIS Server")));
itemEntries.add(new ItemEntry(".*geocodeserver.*", new ItemDesc("Geocoding Service", ItemType.url, "Tool, Service, Geocoding Service, Locator Service, ArcGIS Server")));
itemEntries.add(new ItemEntry(".*geometryserver.*", new ItemDesc("Geometry Service", ItemType.url, "Tool, Service, Geometry Service, ArcGIS Server")));
itemEntries.add(new ItemEntry(".*networkserver.*", new ItemDesc("Network Analysis Service", ItemType.url, "Tool, Service, Network Analysis Service, ArcGIS Server")));
itemEntries.add(new ItemEntry(".*geodataserver.*", new ItemDesc("Geodata Service", ItemType.url, "Data, Service, Geodata Service, ArcGIS Server")));
itemEntries.add(new ItemEntry(".*service=wms.*|.*wmsserver.*", new ItemDesc("WMS", ItemType.url, "Web Map Service")));
itemEntries.add(new ItemEntry(".*service=wmts.*|.*wmtsserver.*", new ItemDesc("WMTS", ItemType.url, "Web Map Tile Service")));
itemEntries.add(new ItemEntry(".*\\.kml$|.*\\.kmz$|.*f=kml.*", new ItemDesc("KML", ItemType.url, "Keyhole Markup Language")));
itemEntries.add(new ItemEntry(".*\\.mxd$", new ItemDesc("Map Document", ItemType.file, "Map Document, Map, 2D, ArcMap, ArcGIS Server, mxd")));
itemEntries.add(new ItemEntry(".*\\.nmf$", new ItemDesc("Explorer Map", ItemType.file, "Map, Explorer Map, Explorer Document, 2D, 3D, ArcGIS Explorer, nmf")));
itemEntries.add(new ItemEntry(".*\\.3dd$", new ItemDesc("Globe Document", ItemType.file, "Map, Globe Document, 3D, ArcGlobe, ArcGIS Server, 3dd")));
itemEntries.add(new ItemEntry(".*\\.sxd$", new ItemDesc("Scene Document", ItemType.file, "Map, Scene Document, 3D, ArcScene, sxd")));
itemEntries.add(new ItemEntry(".*\\.ncfg$", new ItemDesc("Explorer Map", ItemType.file, "Map, Explorer Map, Explorer Document, 2D, 3D, ArcGIS Explorer, nmf")));
itemEntries.add(new ItemEntry(".*\\.pmf$", new ItemDesc("Published Map", ItemType.file, "Published Map,2D,ArcReader,ArcMap,ArcGIS Server,pmf")));
itemEntries.add(new ItemEntry(".*\\.mpk$", new ItemDesc("Map Package", ItemType.file, "Map, 2D, Map Package, ArcMap")));
itemEntries.add(new ItemEntry(".*\\.gpk$", new ItemDesc("Geoprocessing Package", ItemType.file, "Tool, ArcGIS Desktop, ArcMap, ArcGlobe, ArcScene, Toolbox, Geoprocessing Package, Model, Script, Sharing, Result, gpk")));
itemEntries.add(new ItemEntry(".*\\.apk$", new ItemDesc("Locator Package", ItemType.file, "Tool, ArcMap, ArcGIS Desktop, Locator Package, Geocoding, apk")));
itemEntries.add(new ItemEntry(".*\\.wmpk$", new ItemDesc("Windows Mobile Package", ItemType.file, "ArcGIS Windows Mobile Package, ArcGIS Windows Mobile Map, ArcGIS Windows Mobile, wmpk")));
itemEntries.add(new ItemEntry(".*\\.wpk$", new ItemDesc("Workflow Manager Package", ItemType.file, "Tool, ArcGIS Workflow Manager, Sharing, wpk, ArcGIS Desktop")));
itemEntries.add(new ItemEntry(".*\\.zip$", new ItemDesc("Desktop Application Template", ItemType.file, "application, template, ArcGIS desktop")));
// itemEntries.add(new ItemEntry(".*\\.zip$", new ItemDesc("Map Template", ItemType.file, "map, template, ArcMap, ArcGIS desktop")));
itemEntries.add(new ItemEntry(".*\\.c$", new ItemDesc("Code Sample", ItemType.file, "code, sample, <optional, specified by the caller: Java, C#, C++, C, Python, or Script>")));
itemEntries.add(new ItemEntry(".*\\.cpp$", new ItemDesc("Code Sample", ItemType.file, "code, sample, <optional, specified by the caller: Java, C#, C++, C, Python, or Script>")));
itemEntries.add(new ItemEntry(".*\\.py$", new ItemDesc("Code Sample", ItemType.file, "code, sample, <optional, specified by the caller: Java, C#, C++, C, Python, or Script>")));
itemEntries.add(new ItemEntry(".*\\.java$", new ItemDesc("Code Sample", ItemType.file, "code, sample, <optional, specified by the caller: Java, C#, C++, C, Python, or Script>")));
itemEntries.add(new ItemEntry(".*\\.cs$", new ItemDesc("Code Sample", ItemType.file, "code, sample, <optional, specified by the caller: Java, C#, C++, C, Python, or Script>")));
itemEntries.add(new ItemEntry(".*\\.js$", new ItemDesc("Code Sample", ItemType.file, "code, sample, <optional, specified by the caller: Java, C#, C++, C, Python, or Script>")));
itemEntries.add(new ItemEntry(".*\\.lyr$", new ItemDesc("Layer", ItemType.file, "Data, Layer, ArcMap,ArcGlobe,ArcGIS Explorer, lyr")));
itemEntries.add(new ItemEntry(".*\\.lpk$", new ItemDesc("Layer Package", ItemType.file, "Data, Layer Package,ArcMap, ArcGlobe, ArcGIS Explorer, lpk")));
itemEntries.add(new ItemEntry(".*\\.nmc$", new ItemDesc("Explorer Layer", ItemType.file, "Data, Layer, Explorer Layer, ArcGIS Explorer,nmc")));
itemEntries.add(new ItemEntry(".*\\.esriaddin$", new ItemDesc("Desktop Add In", ItemType.file, "Tool, Add In, Desktop Add In, ArcGIS Desktop, ArcMap, ArcGlobe, ArcScene, esriaddin")));
itemEntries.add(new ItemEntry(".*\\.eaz$", new ItemDesc("Explorer Add In", ItemType.file, "Tool, Add In,Explorer Add In, ArcGIS Explorer, eaz")));
}
/**
* Item entry
*/
public static final class ItemEntry {
private Pattern pattern;
private ItemDesc itemDesc;
public ItemEntry(String pattern, ItemDesc itemDesc) {
this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
this.itemDesc = itemDesc;
}
public ItemEntry(Pattern pattern, ItemDesc itemDesc) {
this.pattern = pattern;
this.itemDesc = itemDesc;
}
public ItemDesc getItemDesc() {
return itemDesc;
}
public boolean matches(String url) {
return pattern.matcher(url).matches();
}
}
/**
* Item description.
*/
public static final class ItemDesc {
private String type;
private ItemType itemType;
private String typeKeywords;
public ItemDesc(String type, ItemType itemType, String typeKeywords) {
this.type = Val.chkStr(type);
this.itemType = itemType;
this.typeKeywords = Val.chkStr(typeKeywords);
}
public String getType() {
return type;
}
public ItemType getItemType() {
return itemType;
}
public String getTypeKeywords() {
return typeKeywords;
}
}
/**
* Item type.
*/
public static enum ItemType {
file,
url,
text
}
/**
* Attributes map.
*/
protected static class AttrMap extends HashMap<String, List<String>> {
public void put(String name, String value) {
name = Val.chkStr(name);
value = Val.chkStr(value);
if (!name.isEmpty() && !value.isEmpty()) {
List<String> node = get(name);
if (node==null) {
node = new ArrayList<String>();
put(name, node);
}
node.add(value);
}
}
}
}