/*
* Copyright 2013 Esri.
*
* 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.agp.ags;
import com.esri.arcgisws.EnvelopeN;
import com.esri.gpt.agp.client.AgpClient;
import com.esri.gpt.agp.client.AgpConnection;
import com.esri.gpt.agp.client.AgpItem;
import com.esri.gpt.agp.client.AgpItemListener;
import com.esri.gpt.agp.client.AgpProperties;
import com.esri.gpt.agp.client.AgpProperty;
import com.esri.gpt.agp.client.AgpSearchCriteria;
import com.esri.gpt.agp.client.AgpSearchRequest;
import com.esri.gpt.agp.client.AgpUtil;
import com.esri.gpt.agp.multipart2.MultipartProvider;
import com.esri.gpt.agp.sync.AgpDestination;
import com.esri.gpt.agp.sync.AgpItemHelper;
import com.esri.gpt.agp.sync.AgpPartHelper;
import com.esri.gpt.catalog.arcgis.metadata.IServiceInfoProvider;
import com.esri.gpt.catalog.arcgis.metadata.ServiceInfo;
import com.esri.gpt.catalog.arcgis.metadata.ServiceInfoProviderAdapter;
import com.esri.gpt.control.georss.GeometryService;
import com.esri.gpt.control.webharvest.IterationContext;
import com.esri.gpt.control.webharvest.client.arcgis.ArcGISInfo;
import com.esri.gpt.control.webharvest.client.arcgis.ArcGISQueryBuilder;
import com.esri.gpt.control.webharvest.common.CommonCriteria;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.geometry.Envelope;
import com.esri.gpt.framework.resource.adapters.FlatResourcesAdapter;
import com.esri.gpt.framework.resource.query.Query;
import com.esri.gpt.framework.resource.query.Result;
import com.esri.gpt.framework.util.Val;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONObject;
/**
* Copies from ArcGIS Server to Portal for ArcGIS.
*/
public class Ags2AgpCopy {
private AgpItemHelper itemHelper = new AgpItemHelper();
private AgpPartHelper partHelper = new AgpPartHelper();
private GeometryService gs = GeometryService.createDefaultInstance();
private static final Logger LOGGER = Logger.getLogger(Ags2AgpCopy.class.getCanonicalName());
private ArcGISInfo source;
private AgpDestination destination;
private int numItemsConsidered;
private int numWithNullType;
private static final HashMap<String,String> agsToAgpType = new HashMap<String, String>();
static {
agsToAgpType.put("MapServer", "Map Service");
agsToAgpType.put("ImageServer", "Image Service");
agsToAgpType.put("FeatureServer", "Feature Service");
agsToAgpType.put("WFSServer", "Feature Service");
agsToAgpType.put("WMSServer", "WMS");
//agsToAgpType.put("GPServer", "Geoprocessing Service");
//agsToAgpType.put("NASServer", "Network Analysis Service");
//agsToAgpType.put("GeometryServer", "Geometry Service");
}
/**
* Creates instance of the class.
* @param source source
* @param destination destination
*/
public Ags2AgpCopy(ArcGISInfo source, AgpDestination destination) {
this.source = source;
this.destination = destination;
}
/**
* Executes copy action.
* @throws Exception if anything fails
*/
public void copy() throws Exception {
LOGGER.log(Level.INFO, "Starting synchronization from ArcGIS Server "+source.getRestUrl()+" into Portal for ArcGIS "+destination.getConnection().getHost());
RequestContext requestContext = RequestContext.extract(null);
try {
ArcGISQueryBuilder qb = new ArcGISQueryBuilder(new IterationContext() {
@Override
public void onIterationException(Exception ex) {
LOGGER.log(Level.SEVERE, "Error iterating through AGS resources.", ex);
}
}, source);
Query newQuery = qb.newQuery(new CommonCriteria());
Result result = newQuery.execute();
this.destination.getConnection().generateToken();
Iterable<IServiceInfoProvider> records = new ServiceInfoProviderAdapter(new FlatResourcesAdapter(result.getResources()));
for (IServiceInfoProvider r: records) {
if (!doContinue()) {
break;
}
ServiceInfo serviceInfo = r.getServiceInfo();
AgpItem agpItem = createAgpItem(serviceInfo);
if (agpItem!=null) {
syncItem(agpItem);
}
}
} finally {
requestContext.onExecutionPhaseCompleted();
LOGGER.log(Level.INFO, "Completed synchronization from ArcGIS Server "+source.getRestUrl()+" into Portal for ArcGIS "+destination.getConnection().getHost());
}
}
/**
* Creates a string definition of the envelope.
* @param E envelope
* @return string definition
*/
private String envelopeToString(com.esri.arcgisws.Envelope E) {
if (E instanceof EnvelopeN) {
EnvelopeN e = (EnvelopeN) E;
// project envelope
Envelope gptEnvelope = new Envelope(e.getXMin(), e.getYMin(), e.getXMax(), e.getYMax());
if (e.getSpatialReference()!=null && e.getSpatialReference().getWKID()!=null) {
gptEnvelope.setWkid(e.getSpatialReference().getWKID().toString());
List<Envelope> envelopes = Arrays.asList(new Envelope[]{gptEnvelope});
try {
List<Envelope> projectedEnvelopes = gs.project(envelopes, "4326");
if (projectedEnvelopes!=null && !projectedEnvelopes.isEmpty()) {
Envelope pe = projectedEnvelopes.get(0);
String envelope = ""+pe.getMinX()+","+pe.getMinY()+","+pe.getMaxX()+","+pe.getMaxY();
return envelope;
}
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Error projecting envelope.", ex);
}
} else {
LOGGER.log(Level.WARNING, "Error projecting envelope: no WKID.");
}
}
return "";
}
/**
* Creates AGP item from service info.
* @param serviceInfo service info.
* @return AGP item
*/
private AgpItem createAgpItem(ServiceInfo serviceInfo) {
if (serviceInfo!=null) {
AgpItem agpItem = new AgpItem();
AgpProperties props = agpItem.getProperties();
String type = serviceInfo.getType();
props.add(new AgpProperty("title", Val.chkStr(serviceInfo.getName())));
props.add(new AgpProperty("name", Val.chkStr(serviceInfo.getName())));
props.add(new AgpProperty("url", Val.chkStr(serviceInfo.getRestUrl())));
props.add(new AgpProperty("description", Val.chkStr(serviceInfo.getDescription())));
String thumbnailUrl = Val.chkStr(serviceInfo.getThumbnailUrl());
if (!thumbnailUrl.isEmpty()) {
props.add(new AgpProperty("thumbnailurl", thumbnailUrl));
}
String textInfo = Val.chkStr(serviceInfo.getText());
if (!textInfo.isEmpty()) {
props.add(new AgpProperty("text", textInfo));
}
String envelope = Val.chkStr(envelopeToString(serviceInfo.getEnvelope()));
if (!envelope.isEmpty()) {
props.add(new AgpProperty("extent", envelope));
}
String agpType = agsToAgpType.get(type);
if (agpType!=null) {
props.add(new AgpProperty("type", agpType));
return agpItem;
}
}
return null;
}
/**
* Synchronizes a single item.
* @param sourceItem item
* @return <code>true</code> if item has been synchronized
* @throws Exception if any exception occurs during the process
*/
protected boolean syncItem(AgpItem sourceItem) throws Exception {
this.numItemsConsidered++;
String sType = sourceItem.getProperties().getValue("type");
String sTitle = sourceItem.getProperties().getValue("title");
String sMsg = "Processing item ("+this.numItemsConsidered+")";
sMsg += ", type:"+sType+", title:"+sTitle;
LOGGER.info(sMsg);
if (sType == null) {
this.numWithNullType++;
LOGGER.finer("Ignoring item with null type: "+sTitle);
return false;
}
AgpItem destinationItem = queryDestinationItem(sourceItem);
execPublishItem(sourceItem,destinationItem);
return true;
}
private void execPublishItem(AgpItem sourceItem,AgpItem destItem) throws Exception {
AgpDestination dest = this.destination;
String sDestId = destItem!=null? destItem.getProperties().getValue("id"): null;
String sTitle = sourceItem.getProperties().getValue("title");
LOGGER.finer("Publishing item: "+sTitle);
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest(sourceItem.getProperties().toString());
}
// make the URL
boolean bInsert = true;
String sUrl = dest.getConnection().makeSharingUrl();
sUrl += "/content/users";
sUrl += "/"+AgpUtil.encodeURIComponent(dest.getDestinationOwner());
sUrl += "/"+AgpUtil.encodeURIComponent(dest.getDestinationFolderID());
if (sDestId == null) {
sUrl += "/addItem";
} else {
bInsert = false;
sUrl += "/items";
sUrl += "/"+AgpUtil.encodeURIComponent(sDestId);
sUrl += "/update";
}
// make the content provider, add thumb-nail and data parts
MultipartProvider provider = new MultipartProvider();
provider.add("f","json");
provider.add("token",destination.getConnection().getToken().getTokenString());
provider.add("overwrite","true");
for (AgpProperty destProp: sourceItem.getProperties().values()) {
provider.add(destProp.getName(),destProp.getValue());
}
//this.partHelper.addThumbnailPart(provider,src,sourceItem,dest,destItem);
//this.partHelper.addDataPart(provider,src,sourceItem,dest,destItem);
// execute
AgpProperties hdr = dest.getConnection().makeRequestHeaderProperties();
AgpClient client = dest.getConnection().ensureClient();
JSONObject jso = client.executeJsonRequest(sUrl,hdr,provider);
if (jso.has("id") && jso.has("success") && jso.getString("success").equals("true")) {
if (sDestId == null) {
sDestId = jso.getString("id");
sourceItem.getProperties().add(new AgpProperty("id",sDestId));
}
/*
if (bInsert) {
this.numItemsInserted++;
LOGGER.finer("Item inserted: "+sUrl);
} else {
this.numItemsUpdated++;
LOGGER.finer("Item updated: "+sUrl);
}
*/
} else {
LOGGER.finer("Publish item FAILED for: "+sUrl);
// TODO: throw exception here??
}
}
/**
* Checks if the item already exists.
* @param sourceItem source item
* @return destination item or <code>null</code> if destination item doesn't exist.
* @throws Exception if accessing destination fails
*/
private AgpItem queryDestinationItem(AgpItem sourceItem) throws Exception {
final AgpItem destinationItem = new AgpItem();
destinationItem.setProperties(null);
final String url = sourceItem.getProperties().getValue("url");
String sQuery = "url:\""+url+"\"";
AgpSearchRequest request = new AgpSearchRequest();
AgpSearchCriteria criteria = new AgpSearchCriteria();
criteria.setQ(sQuery);
criteria.setNum(1);
request.search(destination.getConnection(),criteria,
new AgpItemListener() {
@Override
public void onItemLoaded(AgpConnection connection, AgpItem item)
throws Exception {
if (url.equals(item.getProperties().get("url").getValue()) && Ags2AgpCopy.this.destination.getDestinationOwner().equals(item.getProperties().get("owner").getValue())) {
destinationItem.setProperties(item.getProperties());
}
}
}
);
if (destinationItem.getProperties() != null) {
return destinationItem;
}
return null;
}
/**
* Check if continue copying.
* @return <code>true</code> if continue copying
*/
protected boolean doContinue() {
return true;
}
}