/*
 *  GeoServer-Manager - Simple Manager Library for GeoServer
 *  
 *  Copyright (C) 2007,2011 GeoSolutions S.A.S.
 *  http://www.geo-solutions.it
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package it.geosolutions.geoserver.rest;

import it.geosolutions.geoserver.rest.decoder.RESTCoverageList;
import it.geosolutions.geoserver.rest.decoder.RESTCoverageStore;
import it.geosolutions.geoserver.rest.decoder.utils.NameLinkElem;
import it.geosolutions.geoserver.rest.encoder.GSBackupEncoder;
import it.geosolutions.geoserver.rest.encoder.GSLayerEncoder;
import it.geosolutions.geoserver.rest.encoder.GSLayerGroupEncoder;
import it.geosolutions.geoserver.rest.encoder.GSNamespaceEncoder;
import it.geosolutions.geoserver.rest.encoder.GSResourceEncoder;
import it.geosolutions.geoserver.rest.encoder.GSResourceEncoder.ProjectionPolicy;
import it.geosolutions.geoserver.rest.encoder.GSWorkspaceEncoder;
import it.geosolutions.geoserver.rest.encoder.coverage.GSCoverageEncoder;
import it.geosolutions.geoserver.rest.encoder.datastore.GSPostGISDatastoreEncoder;
import it.geosolutions.geoserver.rest.encoder.feature.GSFeatureTypeEncoder;
import it.geosolutions.geoserver.rest.manager.GeoServerRESTStoreManager;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Iterator;

import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Connect to a GeoServer instance to publish or modify its contents via REST API.
 * <P>
 * There are no modifiable instance fields, so all the calls are thread-safe.
 * 
 * @author ETj (etj at geo-solutions.it)
 * @author Carlo Cancellieri - carlo.cancellieri@geo-solutions.it
 */
public class GeoServerRESTPublisher {

    public static final String DEFAULT_CRS = "EPSG:4326";

    /** The logger for this class */
    private static final Logger LOGGER = LoggerFactory.getLogger(GeoServerRESTPublisher.class);

    /**
     * GeoServer instance base URL. E.g.: <TT>http://localhost:8080/geoserver</TT>.
     */
    private final String restURL;

    /**
     * GeoServer instance privileged username, with read & write permission on REST API
     */
    private final String gsuser;

    /**
     * GeoServer instance password for privileged username with r&w permission on REST API
     */
    private final String gspass;

    /**
     * Creates a <TT>GeoServerRESTPublisher</TT> to connect against a GeoServer instance with the given URL and user credentials.
     * 
     * @param restURL the base GeoServer URL (e.g.: <TT>http://localhost:8080/geoserver</TT>)
     * @param username auth credential
     * @param password auth credential
     */
    public GeoServerRESTPublisher(String restURL, String username, String password) {
        this.restURL = HTTPUtils.decurtSlash(restURL);
        this.gsuser = username;
        this.gspass = password;
    }

    // ==========================================================================
    // === BACKUP and RESTORE
    // ==========================================================================

    /**
     * Issues a GeoServer BACKUP.
     * <P>
     * Won't include data, cached tiles, or logs. Use {@link #backup(String, boolean, boolean, boolean)} to control these parameters.
     * 
     * @param backupDir the target Backup Dir String.
     * 
     * @return <TT>id</TT> of the backup.
     * @throws IllegalArgumentException if the backupDir is null or empty
     */
    public String backup(final String backupDir) throws IllegalArgumentException {
        /*
         * This is the equivalent call with cUrl:
         * 
         * {@code curl -u admin:geoserver -XPOST \ -H 'Content-type: text/xml' \ --data
         * "&lt;task&gt;&lt;path&gt;${BACKUP_DATADIR}&lt;/path&gt;&lt;/task&gt;" \ ${restURL}/rest/bkprst/backup}
         */
        return backup(backupDir, false, false, false);
    }

    /**
     * Issues a GeoServer BACKUP.
     * 
     * @param backupDir the target Backup Dir String.
     * @param includedata whether or not include the data dir Boolean.
     * @param includegwc whether or not include the geowebcache dir Boolean.
     * @param includelog whether or not include the log dir Boolean.
     * 
     * @return <TT>id</TT> of the backup.
     * @throws IllegalArgumentException if the backupDir is null or empty.
     */
    public String backup(final String backupDir, final boolean includedata,
            final boolean includegwc, final boolean includelog) throws IllegalArgumentException {
        /*
         * This is the equivalent call with cUrl:
         * 
         * {@code curl -u admin:geoserver -XPOST \ -H 'Content-type: text/xml' \ --data
         * "&lt;task&gt;&lt;path&gt;${BACKUP_DATADIR}&lt;/path&gt;&lt;includedata&gt;${includedata}&lt;/includedata&gt;&lt;includegwc&gt;${includegwc}&lt;/includegwc&gt;&lt;includelog&gt;${includelog}&lt;/includelog&gt;&lt;/task&gt;"
         * \ ${restURL}/rest/bkprst/backup}
         */
        if ((backupDir == null) || backupDir.isEmpty()) {
            throw new IllegalArgumentException("The backup_dir must not be null or empty");
        }

        StringBuilder bkpUrl = new StringBuilder(restURL);
        bkpUrl.append("/rest/bkprst/backup");

        final GSBackupEncoder bkpenc = new GSBackupEncoder(backupDir);
        bkpenc.setIncludeData(includedata);
        bkpenc.setIncludeGwc(includegwc);
        bkpenc.setIncludeLog(includelog);
        final String result = HTTPUtils.post(bkpUrl.toString(), bkpenc.toString(), "text/xml",
                gsuser, gspass);

        return result;
    }

    /**
     * Issues a GeoServer RESTORE.
     * 
     * @param backupDir the source backup dir.
     * 
     * @return <TT>id</TT> of the backup.
     * @throws IllegalArgumentException if the backupDir is null or empty
     */
    public String restore(final String backupDir) throws IllegalArgumentException {
        /*
         * This is the equivalent call with cUrl:
         * 
         * {@code curl -u admin:geoserver -XPOST \ -H 'Content-type: text/xml' \ --data
         * "&lt;task&gt;&lt;path&gt;${BACKUP_DATADIR}&lt;/path&gt;&lt;/task&gt;" \ ${restURL}/rest/bkprst/restore}
         */
        if ((backupDir == null) || backupDir.isEmpty()) {
            throw new IllegalArgumentException("The backup_dir must not be null or empty");
        }

        StringBuilder bkpUrl = new StringBuilder(restURL);
        bkpUrl.append("/rest/bkprst/restore");

        final GSBackupEncoder bkpenc = new GSBackupEncoder(backupDir);

        final String result = HTTPUtils.post(bkpUrl.toString(), bkpenc.toString(), "text/xml",
                gsuser, gspass);

        return result;
    }

    // ==========================================================================
    // === WORKSPACES
    // ==========================================================================

    /**
     * Create a new Workspace.
     * <P>
     * GeoServer will automatically create an associated Namespace with the URI being "http://{workspaceName}". To specify a custom Namespace URI, use
     * {@link #createWorkspace(String, URI)}.
     * 
     * @param workspace The name of the new workspace.
     * 
     * @return <TT>true</TT> if the workspace was created.
     */
    public boolean createWorkspace(final String workspace) {
        /*
         * This is the equivalent call with cUrl:
         * 
         * {@code curl -u admin:geoserver -XPOST \ -H 'Content-type: text/xml' \ -d "<workspace><name>$WORKSPACE</name></workspace>" \
         * http://$GSIP:$GSPORT/$SERVLET/rest/workspaces }
         */
        final String sUrl = restURL + "/rest/workspaces";
        final GSWorkspaceEncoder wsenc = new GSWorkspaceEncoder(workspace);
        final String wsxml = wsenc.toString();
        final String result = HTTPUtils.postXml(sUrl, wsxml, gsuser, gspass);
        return result != null;
    }

    /**
     * Create both a workspace and its associated namespace.
     * <P>
     * Note that this method is equivalent to {@link #createNamespace}.
     * 
     * @param name Name for the new workspace, which will be also its associated namespace prefix.
     * @param uri Namespace URI. Cannot be empty.
     * @return <TT>true</TT> if the Workspace and its associated namespace were successfully created.
     */
    public boolean createWorkspace(final String name, final URI uri) {
        // This is really an alias to createNamespace, as GeoServer
        // will automatically create the associated workspace as well.
        return createNamespace(name, uri);
    }

    // ==========================================================================
    // === NAMESPACES
    // ==========================================================================

    /**
     * Create a new Namespace. GeoServer will automatically create the corresponding Workspace.
     * 
     * Prefix and URI are mandatory and cannot be empty. If a Namespace with the given prefix already exists, it won't be created.
     * 
     * @param prefix The name of the new Namespace.
     * @param uri The URI of the new Namespace.
     * 
     * @return <TT>true</TT> if the Namespace was successfully created.
     * @see <a href="http://docs.geoserver.org/stable/en/user/restconfig/rest-config-api.html#namespaces"> GeoServer Documentation</a>
     */
    public boolean createNamespace(final String prefix, final URI uri) {
        final String sUrl = restURL + "/rest/namespaces";
        final GSNamespaceEncoder nsenc = new GSNamespaceEncoder(prefix, uri);
        final String nsxml = nsenc.toString();
        final String result = HTTPUtils.postXml(sUrl, nsxml, gsuser, gspass);
        return result != null;
    }

    /**
     * Update a Namespace URI.
     * 
     * Prefix and URI are mandatory and cannot be empty. A Namespace with the given prefix should exist.
     * 
     * @param prefix The prefix of an existing Namespace.
     * @param uri The new URI.
     * 
     * @return <TT>true</TT> if the Namespace was successfully updated.
     */
    public boolean updateNamespace(final String prefix, final URI uri) {
        final String sUrl = restURL + "/rest/namespaces/" + encode(prefix);
        final GSNamespaceEncoder nsenc = new GSNamespaceEncoder(prefix, uri);
        final String nsxml = nsenc.toString();
        final String result = HTTPUtils.put(sUrl, nsxml, "application/xml", gsuser, gspass);
        return result != null;
    }

    /**
     * Remove a given Namespace. It will remove the associated Workspace as well.
     * 
     * @param prefix The Namespace prefix
     * @param recurse The recurse parameter is used to recursively delete all resources contained in the workspace associated with this Namespace.
     *        This includes data stores, coverage stores, feature types, etc... Allowable values for this parameter are <i>true</i> or <i>false</i>.
     *        The default (safer) value is <i>false</i>.
     * 
     * @return <TT>true</TT> if the Namespace was successfully removed.
     */
    public boolean removeNamespace(final String prefix, boolean recurse) {
        // Hack: We are instead calling removeWorkspace, as DELETE on
        // a namespace will leave associated workspace in an inconsistent
        // state. See https://jira.codehaus.org/browse/GEOS-5075
        // TODO switch to namespace when GEOS-5075 is solved
        return removeWorkspace(prefix, recurse);
    }

    // ==========================================================================
    // === STYLES
    // ==========================================================================

    /**
     * Store and publish a Style.
     * 
     * @param sldBody the full SLD document as a String.
     * 
     * @return <TT>true</TT> if the operation completed successfully.
     */
    public boolean publishStyle(String sldBody) {
        /*
         * This is the equivalent call with cUrl:
         * 
         * {@code curl -u admin:geoserver -XPOST \ -H 'Content-type: application/vnd.ogc.sld+xml' \ -d @$FULLSLD \
         * http://$GSIP:$GSPORT/$SERVLET/rest/styles}
         */
        try {
            return publishStyle(sldBody, null);
        } catch (IllegalArgumentException e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error(e.getLocalizedMessage(), e);
            }
        }
        return false;
    }

    /**
     * Store and publish a Style, assigning it a name.
     * 
     * @param sldBody the full SLD document as a String.
     * @param name the Style name.
     * 
     * @return <TT>true</TT> if the operation completed successfully.
     * @throws IllegalArgumentException if the style body is null or empty.
     */
    public boolean publishStyle(final String sldBody, final String name)
            throws IllegalArgumentException {
        /*
         * This is the equivalent call with cUrl:
         * 
         * {@code curl -u admin:geoserver -XPOST \ -H 'Content-type: application/vnd.ogc.sld+xml' \ -d @$FULLSLD \
         * http://$GSIP:$GSPORT/$SERVLET/rest/styles?name=name}
         */
        if (sldBody == null || sldBody.isEmpty()) {
            throw new IllegalArgumentException("The style body may not be null or empty");
        }
        StringBuilder sUrl = new StringBuilder(restURL);
        sUrl.append("/rest/styles");
        if (name != null && !name.isEmpty()) {
            sUrl.append("?name=").append(name);
        }
        final String result = HTTPUtils.post(sUrl.toString(), sldBody,
                "application/vnd.ogc.sld+xml", gsuser, gspass);
        return result != null;
    }

    /**
     * Store and publish a Style.
     * 
     * @param sldFile the File containing the SLD document.
     * 
     * @return <TT>true</TT> if the operation completed successfully.
     */
    public boolean publishStyle(File sldFile) {
        return publishStyle(sldFile, null);
    }

    /**
     * Store and publish a Style, assigning it a name.
     * 
     * @param sldFile the File containing the SLD document.
     * @param name the Style name.
     * 
     * @return <TT>true</TT> if the operation completed successfully.
     */
    public boolean publishStyle(File sldFile, String name) {
        String sUrl = restURL + "/rest/styles";
        if (name != null && !name.isEmpty()) {
            sUrl += "?name=" + encode(name);
        }
        LOGGER.debug("POSTing new style " + name + " to " + sUrl);
        String result = HTTPUtils
                .post(sUrl, sldFile, Format.SLD.getContentType(), gsuser, gspass);
        return result != null;
    }

    /**
     * Update a Style.
     * 
     * @param sldBody the new SLD document as a String.
     * @param name the Style name to update.
     * 
     * @return <TT>true</TT> if the operation completed successfully.
     * @throws IllegalArgumentException if the style body or name are null or empty.
     */
    public boolean updateStyle(final String sldBody, final String name)
            throws IllegalArgumentException {
        /*
         * This is the equivalent call with cUrl:
         * 
         * {@code curl -u admin:geoserver -XPUT \ -H 'Content-type: application/vnd.ogc.sld+xml' \ -d @$FULLSLD \
         * http://$GSIP:$GSPORT/$SERVLET/rest/styles/$NAME}
         */
        if (sldBody == null || sldBody.isEmpty()) {
            throw new IllegalArgumentException("The style body may not be null or empty");
        } else if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("The style name may not be null or empty");
        }

        final StringBuilder sUrl = new StringBuilder(restURL);
        sUrl.append("/rest/styles/").append(encode(name));

        final String result = HTTPUtils.put(sUrl.toString(), sldBody,
                "application/vnd.ogc.sld+xml", gsuser, gspass);
        return result != null;
    }

    /**
     * Update a Style.
     * 
     * @param sldFile the File containing the SLD document.
     * @param name the Style name.
     * 
     * @return <TT>true</TT> if the operation completed successfully.
     * @throws IllegalArgumentException if the sldFile file or name are null or name is empty.
     */
    public boolean updateStyle(final File sldFile, final String name)
            throws IllegalArgumentException {

        if (sldFile == null) {
            throw new IllegalArgumentException("Unable to updateStyle using a null parameter file");
        } else if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("The style name may not be null or empty");
        }

        final StringBuilder sUrl = new StringBuilder(restURL);
        sUrl.append("/rest/styles/").append(encode(name));

        final String result = HTTPUtils.put(sUrl.toString(), sldFile,
                "application/vnd.ogc.sld+xml", gsuser, gspass);
        return result != null;

    }

    /**
     * Remove a Style.
     * <P>
     * The Style will be unpublished, and (optionally) the SLD file will be removed.
     * 
     * @param styleName the name of the Style to remove.
     * @param purge remove the related SLD file from disk.
     * 
     * @return <TT>true</TT> if the operation completed successfully.
     * @throws IllegalArgumentException if styleName is null or empty.
     */
    public boolean removeStyle(String styleName, final boolean purge)
            throws IllegalArgumentException {
        if (styleName == null || styleName.isEmpty())
            throw new IllegalArgumentException(
                    "Check styleName parameter, it may never be null or empty");

        final StringBuffer sUrl = new StringBuffer(restURL);

        // check style name
        // TODO may we whant to throw an exception instead of
        // change style name?
        styleName = styleName.replaceAll(":", "_");
        styleName = encode(styleName);

        sUrl.append("/rest/styles/").append(styleName);
        if (purge) {
            sUrl.append("?purge=true");
        }

        return HTTPUtils.delete(sUrl.toString(), gsuser, gspass);
    }

    /**
     * Remove a Style.
     * <P>
     * The Style will be unpublished and the related SLD file will be removed.
     * 
     * @param styleName the name of the Style to remove.
     * 
     * @return <TT>true</TT> if the operation completed successfully.
     */
    public boolean removeStyle(String styleName) {
        try {
            return removeStyle(styleName, true);
        } catch (IllegalArgumentException e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error(e.getLocalizedMessage(), e);
            }
        }
        return false;
    }

    // ==========================================================================
    // === DATASTORE PUBLISHING
    // ==========================================================================

    /**
     * 
     * @author cancellieri
     * @deprecated use {@link StoreType}
     */
    public enum DataStoreType {
        /**
         * Raster based data sources.
         */
        COVERAGESTORES,
        /**
         * Vector based data sources. Can be a file in the case of a Shapefile, a database connection in the case of PostGIS, or a server in the case
         * of a remote Web Feature Service.
         */
        DATASTORES;

        /**
         * @deprecated use {@link StoreType#getTypeNameWithFormat(StoreType, Format)}
         * @param type
         * @return
         */
        public static String getTypeName(StoreType type) {
            return StoreType.getTypeNameWithFormat(type,Format.XML);
        }

        /**
         * @deprecated use {@link StoreType#toString()}
         */
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    /**
     * DataStoreType definitions.
     * <UL>
     * <LI>{@link #DATASTORES} vector based data sources.
     * <LI>{@link #COVERAGESTORES} raster based data sources.
     * </UL>
     * 
     * @author Carlo Cancellieri - carlo.cancellieri@geo-solutions.it
     */
    public enum StoreType {
        /**
         * Raster based data sources.
         */
        COVERAGESTORES,
        /**
         * Vector based data sources. Can be a file in the case of a Shapefile, a database connection in the case of PostGIS, or a server in the case
         * of a remote Web Feature Service.
         */
        DATASTORES;

        /**
         * Get the type name of a StoreType with the specified format.
         * 
         * @param type the StoreType.
         * @param format see {@link Format}
         * @return "featureTypes.{xml|html|...}" for DATASTORES, "coverages.{xml|html|...}" otherwise.
         */
        public static String getTypeNameWithFormat(StoreType type, Format format) {
            return getTypeName(type)+"."+format;
        }
        
        /**
         * Get the type name of a StoreType with the specified format.
         * 
         * @param type the StoreType.
         * @param format see {@link Format}
         * @return "featuretypes.{xml|html|...}" for DATASTORES, "coverages.{xml|html|...}" otherwise.
         */
        public String getTypeNameWithFormat(Format format) {
            return getTypeName(this).toLowerCase()+"."+format;
        }
        
        /**
         * Get the type name of a StoreType.
         * 
         * @param type the StoreType.
         * @return "featureTypes" for DATASTORES, "coverages" otherwise.
         */
        public static String getTypeName(StoreType type) {
            switch (type) {
            case COVERAGESTORES:
                return "coverages"; // Format
            case DATASTORES:
                return "featureTypes";
            default:
                return "coverages";
            }
        }
        
        /**
         * Get the type name of a StoreType.
         * 
         * @param type the StoreType.
         * @return "dataStore" for DATASTORES, "coverageStore" otherwise.
         */
        public static String getType(StoreType type) {
            switch (type) {
            case COVERAGESTORES:
                return "coverageStore"; // Format
            case DATASTORES:
                return "dataStore";
            default:
                return "coverageStore";
            }
        }
        
        /**
         * Get the type name of a StoreType.
         * 
         * @return "featuretypes" for DATASTORES, "coverages" otherwise.
         */
        public String getTypeName() {
            return getTypeName(this);
        }
        
        /**
         * Get the type of a StoreType.
         * 
         * @param type the StoreType.
         * @return "dataStore" for DATASTORES, "coverageStore" otherwise.
         */
        public String getType() {
            return getType(this);
        }

        /**
         * Returns a lowercase representation of the parameter value, suitable to construct the rest call.
         */
        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    /**
     * Specifies the method used to publish a resource.
     * <ul>
     * <li>{@link #FILE} upload a file from a local source.
     * <li>{@link #URL} indirectly upload a file from a remote source.
     * <li>{@link #EXTERNAL} don't upload; use an existing file on the server.
     * </ul>
     * 
     * @author Carlo Cancellieri - carlo.cancellieri@geo-solutions.it
     */
    public enum UploadMethod {
        /**
         * directly upload a file from a local source. The body of the request is the file itself.
         */
        FILE, @Deprecated
        file,
        /**
         * indirectly upload a file from a remote source. The body of the request is the URL where the data is published. This url must be visible
         * from the server.
         */
        URL, @Deprecated
        url,
        /**
         * forgo upload, and use an existing file on the server. The body of the request is the absolute local path to the existing file.
         */
        EXTERNAL, @Deprecated
        external;

        /**
         * Returns a lowercase representation of the parameter value, suitable to construct the rest call.
         */
        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    /**
     * Vector data format being uploaded. Following extensions are supported:
     * <ul>
     * <li>{@link #SHP} Shapefile
     * <li>{@link #PROPERTIES} Property file
     * <li>{@link #H2} H2 Database
     * <li>{@link #SPATIALITE} SpatiaLite Database
     * </ul>
     * 
     * @author Carlo Cancellieri - carlo.cancellieri@geo-solutions.it
     */
    public enum DataStoreExtension {
        /** Shapefile */
        SHP,
        /** Properties file */
        PROPERTIES,
        /** H2 Database */
        H2,
        /** SpatiaLite Database */
        SPATIALITE;

        /**
         * Returns a lowercase representation of the parameter value, suitable to construct the rest call.
         */
        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    /**
     * Raster data format being uploaded. Following extensions are supported:
     * <ul>
     * <li>{@link #GEOTIFF} GeoTiff coverage
     * <li>{@link #IMAGEMOSAIC} ImageMosaic
     * <li>{@link #WORLDIMAGE} Geo referenced image (JPEG,PNG,TIF)
     * </ul>
     * 
     * @author Carlo Cancellieri - carlo.cancellieri@geo-solutions.it
     */
    public enum CoverageStoreExtension {
        /** GeoTiff coverage */
        GEOTIFF,
        /** ImageMosaic */
        IMAGEMOSAIC,
        /** Geo referenced image (JPEG,PNG,TIF) */
        WORLDIMAGE;

        /**
         * Returns a lowercase representation of the parameter value, suitable to construct the rest call.
         */
        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    /**
     * Control if feature types are to be automatically configured upon file upload. It can take one of the three values:
     * <ul>
     * <li>{@link #FIRST} Configure first feature type only (default).
     * <li>{@link #NONE} Don't configure any feature types.
     * <li>{@link #ALL} Configure all feature types.
     * </ul>
     */
    public static enum ParameterConfigure {
        /** Configure first feature type only (default). */
        FIRST,
        /** Don't configure any feature types. */
        NONE,
        /** Configure all feature types. */
        ALL;

        /**
         * Returns a lowercase representation of the parameter value, suitable to construct the rest call.
         */
        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    /**
     * Upload and publish data (vector or raster), and automatically create the associated Store if needed.
     * 
     * This is a generic method; use {@link #createDataStore} and {@link #createCoverageStore} for vector and raster publishing respectively.
     * 
     * @param workspace workspace where data will be published.
     * @param dsType Store type. See {@link DataStoreType}.
     * @param storeName Store name. Will be created if doesn't exist.
     * @param method Publication method. See {@link UploadMethod}.
     * @param extension Dataset format. One of {@link DataStoreExtension} for vector data, or one of {@link CoverageStoreExtension} for raster data.
     * @param mimeType Dataset MIME type.
     * @param uri The data location. A local path for {@link UploadMethod#FILE}, a server path for {@link UploadMethod#EXTERNAL}, or a URL for
     *        {@link UploadMethod#URL}.
     * @param configure Indicate if associated feature types will be automatically configured. See {@link ParameterConfigure}.
     * @param params Other parameters to be included in request, such as "update", "charset" or "coverageName". See GeoServer REST documentation for
     *        details: <a href= "http://docs.geoserver.org/latest/en/user/restconfig/rest-config-api.html#id2" >dataStore</a> and <a href=
     *        "http://docs.geoserver.org/latest/en/user/restconfig/rest-config-api.html#id4" >coverageStore</a> operations.
     * @return {@code true} if the store was successfully created.
     * @throws FileNotFoundException if local file does not exist when using the {@link UploadMethod#FILE} method.
     * @throws IllegalArgumentException if any of the mandatory {@code workspace}, {@code dsType}, {@code storeName}, {@code method},
     *         {@code extension}, {@code mimeType} or {@code uri} parameters are {@code null}.
     */
    private boolean createStore(String workspace, StoreType dsType, String storeName,
            UploadMethod method, Enum extension, String mimeType, URI uri,
            ParameterConfigure configure, NameValuePair... params) throws FileNotFoundException,
            IllegalArgumentException {
        if (workspace == null || dsType == null || storeName == null || method == null
                | extension == null || mimeType == null || uri == null) {
            throw new IllegalArgumentException("Null argument");
        }
        StringBuilder sbUrl = new StringBuilder(restURL).append("/rest/workspaces/")
                .append(workspace).append("/").append(dsType).append("/").append(storeName)
                .append("/").append(method).append(".").append(extension);

        if (configure != null) {
            sbUrl.append("?configure=").append(configure);
            if (params != (NameValuePair[]) null) {
                final String paramString = appendParameters(params);
                if (!paramString.isEmpty()) {
                    sbUrl.append("&").append(paramString);
                }
            }
        }

        String sentResult = null;

        if (method.equals(UploadMethod.FILE)) {
            final File file = new File(uri);
            if (!file.exists())
                throw new FileNotFoundException("unable to locate file: " + file);
            sentResult = HTTPUtils.put(sbUrl.toString(), file, mimeType, gsuser, gspass);
        } else if (method.equals(UploadMethod.EXTERNAL)) {
            sentResult = HTTPUtils.put(sbUrl.toString(), uri.toString(), mimeType, gsuser, gspass);
        } else if (method.equals(UploadMethod.URL)) {
            // TODO check
            sentResult = HTTPUtils.put(sbUrl.toString(), uri.toString(), mimeType, gsuser, gspass);
        }

        if (sentResult != null) {
            if (LOGGER.isInfoEnabled())
                LOGGER.info("Store successfully created using ( " + uri + " )");
            return true;
        } else {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Error in creating store using: " + uri);
            return false;
        }

    }

    /**
     * Upload and publish vector data, and automatically create the associated DataStore if needed.
     * 
     * Note that this is same as {@link #createStore} but specific for vector data (that is, {@code dsType} being {@link DataStoreType#DATASTORES}).
     * 
     * @param workspace workspace where data will be published.
     * @param storeName DataStore name. Will be created if doesn't exist.
     * @param method Publication method. See {@link UploadMethod}.
     * @param extension Dataset format. One of {@link DataStoreExtension}.
     * @param mimeType Dataset MIME type.
     * @param uri The data location. A local path for {@link UploadMethod#FILE}, a server path for {@link UploadMethod#EXTERNAL}, or a URL for
     *        {@link UploadMethod#URL}.
     * @param configure Indicate if associated feature types will be automatically configured. See {@link ParameterConfigure}.
     * @param params Other parameters to be included in request, such as "update" or "charset". See GeoServer REST documentation for details: <a href=
     *        "http://docs.geoserver.org/latest/en/user/restconfig/rest-config-api.html#id2" >dataStore operations</a>.
     * @return {@code true} if the store was successfully created.
     * @throws FileNotFoundException if local file does not exist when using the {@link UploadMethod#FILE} method.
     * @throws IllegalArgumentException if any of the mandatory {@code workspace}, {@code storeName}, {@code method}, {@code extension},
     *         {@code mimeType} or {@code uri} parameters are {@code null}.
     */
    private boolean createDataStore(String workspace, String storeName, UploadMethod method,
            DataStoreExtension extension, String mimeType, URI uri, ParameterConfigure configure,
            NameValuePair... params) throws FileNotFoundException, IllegalArgumentException {
        return createStore(workspace, StoreType.DATASTORES, storeName, method, extension, mimeType,
                uri, configure, params);
    }

    /**
     * Upload and publish raster data, and automatically create the associated CoverageStore if needed.
     * 
     * Note that this is same as {@link #createStore} but specific for raster data (that is, {@code dsType} being {@link DataStoreType#COVERAGESTORES}
     * ).
     * 
     * @param workspace workspace where data will be published.
     * @param storeName CoverageStore name. Will be created if doesn't exist.
     * @param method Publication method. See {@link UploadMethod}.
     * @param extension Dataset format. One of {@link CoverageStoreExtension}.
     * @param mimeType Dataset MIME type.
     * @param uri The data location. A local path for {@link UploadMethod#FILE}, a server path for {@link UploadMethod#EXTERNAL}, or a URL for
     *        {@link UploadMethod#URL}.
     * @param configure Indicate if associated feature types will be automatically configured. See {@link ParameterConfigure}.
     * @param params Other parameters to be included in request, such as "coverageName". See GeoServer REST documentation for details: <a href=
     *        "http://docs.geoserver.org/latest/en/user/restconfig/rest-config-api.html#id4" >coverageStore operations</a>.
     * @return {@code true} if the store was successfully created.
     * @throws FileNotFoundException if local file does not exist when using the {@link UploadMethod#FILE} method.
     * @throws IllegalArgumentException if any of the mandatory {@code workspace}, {@code storeName}, {@code method}, {@code extension},
     *         {@code mimeType} or {@code uri} parameters are {@code null}.
     */
    private boolean createCoverageStore(String workspace, String storeName, UploadMethod method,
            CoverageStoreExtension extension, String mimeType, URI uri,
            ParameterConfigure configure, NameValuePair... params) throws FileNotFoundException,
            IllegalArgumentException {
        return createStore(workspace, StoreType.COVERAGESTORES, storeName, method, extension,
                mimeType, uri, configure, params);
    }

    /**
     * Create a PostGIS datastore.
     * 
     * @deprecated Will be deleted in next version 1.6.x, use {@link GeoServerRESTStoreManager} instead.
     * 
     * @param workspace Name of the workspace to contain the database. This will also be the prefix of any layer names created from tables in the
     *        database.
     * @param datastoreEncoder the set of parameters to be set to the datastore (including connection params).
     * 
     * @return <TT>true</TT> if the PostGIS datastore has been successfully created, <TT>false</TT> otherwise
     */
    public boolean createPostGISDatastore(String workspace,
            it.geosolutions.geoserver.rest.encoder.GSPostGISDatastoreEncoder datastoreEncoder) {
        String sUrl = restURL + "/rest/workspaces/" + workspace + "/datastores/";
        String xml = datastoreEncoder.toString();
        String result = HTTPUtils.postXml(sUrl, xml, gsuser, gspass);
        return result != null;
    }

    /**
     * @deprecated Will be removed in the next release.
     */
    public boolean publishDBLayer(String workspace, String storename, String layername, String srs,
            String defaultStyle) {

        final GSFeatureTypeEncoder fte = new GSFeatureTypeEncoder();

        fte.setProjectionPolicy(ProjectionPolicy.REPROJECT_TO_DECLARED);
        fte.addKeyword("KEYWORD");
        fte.setTitle(layername);
        fte.setName(layername);
        fte.setSRS(srs); // srs=null?"EPSG:4326":srs);
        final GSLayerEncoder layerEncoder = new GSLayerEncoder();
        layerEncoder.setDefaultStyle(defaultStyle);
        return publishDBLayer(workspace, storename, fte, layerEncoder);
    }

    /**
     * Publish and configure a new layer from an existing DataStore (v. gr. a layer from a DB table).
     * 
     * @param workspace Workspace name where DataStore is.
     * @param storename DataStore name.
     * @param fte FeatureType configuration details using a {@link GSFeatureTypeEncoder}.
     * @return {@code true} if layer is successfully created.
     */
    public boolean publishDBLayer(final String workspace, final String storename,
            final GSFeatureTypeEncoder fte, final GSLayerEncoder layerEncoder) {
        /*
         * This is the equivalent call with cUrl:
         * 
         * {@code curl -u admin:geoserver -XPOST -H 'Content-type: text/xml' \ -d
         * "<featureType><name>easia_gaul_1_aggr</name><nativeCRS>EPSG:4326</nativeCRS><enabled>true</enabled></featureType>" \
         * http://localhost:8080/geoserver/rest/workspaces/it.geosolutions/ datastores/pg_kids/featuretypes }
         * 
         * and a PUT to <BR> restURL + "/rest/layers/" workspace + : + layerName
         */
        String ftypeXml = fte.toString();
        StringBuilder postUrl = new StringBuilder(restURL).append("/rest/workspaces/")
                .append(workspace).append("/datastores/").append(storename).append("/featuretypes");

        final String layername = fte.getName();
        if (layername == null || layername.isEmpty()) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("GSFeatureTypeEncoder has no valid name associated, try using GSFeatureTypeEncoder.setName(String)");
            return false;
        }

        String configuredResult = HTTPUtils.postXml(postUrl.toString(), ftypeXml, this.gsuser,
                this.gspass);
        boolean published = configuredResult != null;
        boolean configured = false;

        if (!published) {
            LOGGER.warn("Error in publishing (" + configuredResult + ") " + workspace + ":"
                    + storename + "/" + layername);
        } else {
            LOGGER.info("DB layer successfully added (layer:" + layername + ")");

            if (layerEncoder == null) {
                if (LOGGER.isErrorEnabled())
                    LOGGER.error("GSLayerEncoder is null: Unable to find the defauldStyle for this layer");
                return false;
            }

            configured = configureLayer(workspace, layername, layerEncoder);

            if (!configured) {
                LOGGER.warn("Error in configuring (" + configuredResult + ") " + workspace + ":"
                        + storename + "/" + layername);
            } else {
                LOGGER.info("DB layer successfully configured (layer:" + layername + ")");
            }
        }

        return published && configured;
    }

    // ==========================================================================
    // === SHAPEFILES
    // ==========================================================================

    /**
     * Upload an publish a local shapefile.
     * <P>
     * The SRS will be set to EPSG:4326.
     * 
     * @see {@link #publishShp(String, String, NameValuePair[], String, UploadMethod, URI, String, ProjectionPolicy, String)}
     * 
     * @param workspace The workspace name.
     * @param storename The store name.
     * @param layername The layer name.
     * @param zipFile The zipped file to publish.
     * 
     * @return {@code true} if the operation completed successfully.
     * @throws FileNotFoundException , IllegalArgumentException
     */
    public boolean publishShp(String workspace, String storename, String datasetname, File zipFile)
            throws FileNotFoundException, IllegalArgumentException {
        return publishShp(workspace, storename, new NameValuePair[0], datasetname,
                UploadMethod.FILE, zipFile.toURI(), DEFAULT_CRS, null);
    }

    /**
     * Publish a shapefile.
     * 
     * @param workspace the name of the workspace to use
     * @param storename the name of the store to create
     * @param storeParams parameters to append to the url (can be null).<br>
     *        Accepted parameters are:<br>
     *        <ul>
     *        <li><b>charset</b> used to set the charset</li>
     *        </ul>
     * @param layername the name of the layer to configure
     * @param method {@link UploadMethod}
     * @param fileUri the uri of the file containing the shapefile.It should be:
     *        <ul>
     *        <li>A zip file if 'method' is file</li>
     *        <li>A shp file if 'method' is external</li>
     *        <li>A zip file if 'method' is uri (UNTESTED)</li>
     *        </ul>
     * @param srs the SRS for this shapefile. It must be an ESPG code or GeoServer will choke.
     * @param nativeCRS the nativeCRS for this shapefile. It can be an EPSG code (for {@link ProjectionPolicy#NONE} or a WKT for
     *        {@link ProjectionPolicy#REPROJECT_TO_DECLARED}.
     * @param policy {@link ProjectionPolicy}
     * @param defaultStyle the default style to set (can be null).
     * @return true if success false otherwise
     * 
     * @throws FileNotFoundException if file to upload is not found
     * @throws IllegalArgumentException if any of the mandatory arguments are {@code null}.
     */
    public boolean publishShp(String workspace, String storeName, NameValuePair[] storeParams,
            String datasetName, UploadMethod method, URI shapefile, String srs, String nativeCRS,
            ProjectionPolicy policy, String defaultStyle) throws FileNotFoundException,
            IllegalArgumentException {
        if (workspace == null || storeName == null || shapefile == null || datasetName == null
                || policy == null) {
            throw new IllegalArgumentException("Unable to run: null parameter");
        }

        //
        // SRS Policy Management
        //
        boolean srsNull = !(srs != null && srs.length() != 0);
        boolean nativeSrsNull = !(nativeCRS != null && nativeCRS.length() != 0);
        // if we are asking to use the reproject policy we must have the native crs
        if (policy == ProjectionPolicy.REPROJECT_TO_DECLARED && (nativeSrsNull || srsNull)) {
            throw new IllegalArgumentException(
                    "Unable to run: you can't ask GeoServer to reproject while not specifying a native CRS");
        }

        // if we are asking to use the NONE policy we must have the native crs.
        if (policy == ProjectionPolicy.NONE && nativeSrsNull) {
            throw new IllegalArgumentException(
                    "Unable to run: you can't ask GeoServer to use a native srs which is null");
        }

        // if we are asking to use the reproject policy we must have the native crs
        if (policy == ProjectionPolicy.FORCE_DECLARED && srsNull) {
            throw new IllegalArgumentException(
                    "Unable to run: you can't force GeoServer to use an srs which is null");
        }

        //
        final String mimeType;
        switch (method) {
        case EXTERNAL:
        case external:
            mimeType = "text/plain";
            break;
        case URL: // TODO check which mime-type should be used
        case FILE:
        case file:
        case url:
            mimeType = "application/zip";
            break;
        default:
            mimeType = null;
        }
        if (!createDataStore(workspace,
                (storeName != null) ? storeName : FilenameUtils.getBaseName(shapefile.toString()),
                method, DataStoreExtension.SHP, mimeType, shapefile, ParameterConfigure.NONE,
                storeParams)) {
            LOGGER.error("Unable to create data store for shapefile: " + shapefile);
            return false;
        }

        // config coverage props (srs)
        final GSFeatureTypeEncoder featureTypeEncoder = new GSFeatureTypeEncoder();
        featureTypeEncoder.setName(datasetName);
        featureTypeEncoder.setTitle(datasetName);
        // set destination srs
        if (!srsNull) {
            featureTypeEncoder.setSRS(srs);
        } else {
            // this under the assumption that when the destination srs is null the nativeCRS has an EPSG one so we force them to be the same
            featureTypeEncoder.setSRS(nativeCRS);
        }
        // set native srs
        if (!nativeSrsNull) {
            featureTypeEncoder.setNativeCRS(nativeCRS);
        }
        featureTypeEncoder.setProjectionPolicy(policy);

        if (!createResource(workspace, StoreType.DATASTORES, storeName, featureTypeEncoder)) {
            LOGGER.error("Unable to create a coverage store for coverage: " + shapefile);
            return false;
        }

        // config layer props (style, ...)
        final GSLayerEncoder layerEncoder = new GSLayerEncoder();
        if (defaultStyle != null && !defaultStyle.isEmpty())
            layerEncoder.setDefaultStyle(defaultStyle);

        return configureLayer(workspace, datasetName, layerEncoder);
    }

    /**
     * Publish a shapefile.
     * 
     * @param workspace the name of the workspace to use
     * @param storename the name of the store to create
     * @param storeParams parameters to append to the url (can be null).<br>
     *        Accepted parameters are:<br>
     *        <ul>
     *        <li><b>charset</b> used to set the charset</li>
     *        </ul>
     * @param layername the name of the layer to configure
     * @param method {@link UploadMethod}
     * @param fileUri the uri of the file containing the shapefile.It should be:
     *        <ul>
     *        <li>A zip file if 'method' is file</li>
     *        <li>A shp file if 'method' is external</li>
     *        <li>A zip file if 'method' is uri (UNTESTED)</li>
     *        </ul>
     * @param srs the SRS for this shapefile. It must be an ESPG code or GeoServer will choke. Notice that we can only use
     *        {@link ProjectionPolicy#FORCE_DECLARED}.
     * @param policy {@link ProjectionPolicy}
     * @param defaultStyle the default style to set (can be null).
     * @return true if success false otherwise
     * 
     * @throws FileNotFoundException if file to upload is not found
     * @throws IllegalArgumentException if any of the mandatory arguments are {@code null}.
     * @deprecated use {@link #publishShp(String, String, NameValuePair[], String, UploadMethod, URI, String, String)} instead as the behaviour of
     *             this method is misleading as it allows you to use wrong ProjectionPolicy values.
     */
    public boolean publishShp(String workspace, String storeName, NameValuePair[] storeParams,
            String datasetName, UploadMethod method, URI shapefile, String srs,
            ProjectionPolicy policy, String defaultStyle) throws FileNotFoundException,
            IllegalArgumentException {
        return publishShp(workspace, storeName, storeParams, datasetName, method, shapefile, srs,
                null, policy, defaultStyle);
    }

    /**
     * Publish a shapefile.
     * 
     * @param workspace the name of the workspace to use
     * @param storename the name of the store to create
     * @param storeParams parameters to append to the url (can be null).<br>
     *        Accepted parameters are:<br>
     *        <ul>
     *        <li><b>charset</b> used to set the charset</li>
     *        </ul>
     * @param layername the name of the layer to configure
     * @param method {@link UploadMethod}
     * @param fileUri the uri of the file containing the shapefile.It should be:
     *        <ul>
     *        <li>A zip file if 'method' is file</li>
     *        <li>A shp file if 'method' is external</li>
     *        <li>A zip file if 'method' is uri (UNTESTED)</li>
     *        </ul>
     * @param srs the SRS for this shapefile. It must be an ESPG code or GeoServer will choke.
     * @param defaultStyle the default style to set (can be null).
     * @return true if success false otherwise
     * 
     * @throws FileNotFoundException if file to upload is not found
     * @throws IllegalArgumentException if any of the mandatory arguments are {@code null}.
     */
    public boolean publishShp(String workspace, String storeName, NameValuePair[] storeParams,
            String datasetName, UploadMethod method, URI shapefile, String srs, String defaultStyle)
            throws FileNotFoundException, IllegalArgumentException {
        return publishShp(workspace, storeName, storeParams, datasetName, method, shapefile, srs,
                null, ProjectionPolicy.FORCE_DECLARED, defaultStyle);
    }

    /**
     * Publish a zipped shapefile.
     * 
     * @see {@link #publishShp(String, String, NameValuePair[], String, UploadMethod, URI, String, ProjectionPolicy, String)}
     * 
     * @param workspace the name of the workspace to use
     * @param storename the name of the store to create
     * @param layerName the name of the layer to configure
     * @param zipFile The zipped file to publish
     * @param srs the srs for this shapefile. It will be forced to use this one in GeoServer using {@link ProjectionPolicy#FORCE_DECLARED}.
     * @param defaultStyle the default style to set (can be null).
     * 
     * @return {@code true} if the operation completed successfully
     * @throws FileNotFoundException if file to upload is not found
     * @throws IllegalArgumentException if any of the mandatory arguments are {@code null}.
     */
    public boolean publishShp(String workspace, String storename, String layerName, File zipFile,
            String srs, String defaultStyle) throws FileNotFoundException, IllegalArgumentException {

        return publishShp(workspace, storename, (NameValuePair[]) null, layerName,
                UploadMethod.FILE, zipFile.toURI(), srs, defaultStyle);
    }

    /**
     * Publish a zipped shapefile forcing the srs to the one provided.
     * 
     * @see {@link #publishShp(String, String, NameValuePair[], String, UploadMethod, URI, String, ProjectionPolicy, String)}
     * 
     * @param workspace the name of the workspace to use
     * @param storename the name of the store to create
     * @param layername the name of the layer to configure
     * @param zipFile The zipped file to publish
     * @param srs the CRS for this shapefile. It must be an EPSG CODE !
     * 
     * @return {@code true} if the operation completed successfully.
     * @throws FileNotFoundException if file to upload is not found
     * @throws IllegalArgumentException if any of the mandatory arguments are {@code null}.
     */
    public boolean publishShp(String workspace, String storename, String layername, File zipFile,
            String srs) throws FileNotFoundException {
        return publishShp(workspace, storename, (NameValuePair[]) null, layername,
                UploadMethod.FILE, zipFile.toURI(), srs, null);
    }

    /**
     * Publish a zipped shapefile.
     * 
     * @see {@link #publishShp(String, String, NameValuePair[], String, UploadMethod, URI, String, ProjectionPolicy, String)}
     * 
     * @param workspace the name of the workspace to use
     * @param storename the name of the store to create
     * @param layername the name of the layer to configure
     * @param zipFile the zip file containing the shapefile
     * @param srs the shapefile srs. This must be an EPSG Codefor this code to work!
     * @param params parameters to append to the url (can be null).<br>
     *        Accepted parameters are:<br>
     *        <ul>
     *        <li><b>charset</b> used to set the charset</li>
     *        </ul>
     * @return {@code true} if the operation completed successfully.
     * @throws FileNotFoundException if file to upload is not found
     * @throws IllegalArgumentException if any of the mandatory arguments are {@code null}.
     */
    public boolean publishShp(String workspace, String storename, String layername, File zipFile,
            String srs, NameValuePair... params) throws FileNotFoundException,
            IllegalArgumentException {

        return publishShp(workspace, storename, params, layername, UploadMethod.FILE,
                zipFile.toURI(), srs, null);
    }

    /**
     * Publish a collection of shapefiles.
     * <P>
     * Will automatically create the store and publish each shapefile as a layer.
     * 
     * @param workspace the name of the workspace to use
     * @param storeName the name of the store to create
     * @param resource the shapefile collection. It can be:
     *        <ul>
     *        <li>A path to a directory containing shapefiles in the server. <li>A local zip file containing shapefiles that will be uploaded. <li>A
     *        URL pointing to a shapefile collection in the wild web (not tested).
     *        </ul>
     * @return {@code true} if publication successful.
     * @throws FileNotFoundException if the specified zip file does not exist.
     */
    public boolean publishShpCollection(String workspace, String storeName, URI resource)
            throws FileNotFoundException {

        // Deduce upload method & mime type from resource syntax.
        UploadMethod method = null;
        String mime = null;
        if (resource.getScheme().equals("file") || resource.isAbsolute() == false) {
            File f = new File(resource);
            if (f.exists() && f.isFile() && f.toString().endsWith(".zip")) {
                method = UploadMethod.FILE;
                mime = "application/zip";
            } else if (f.isDirectory()) {
                method = UploadMethod.EXTERNAL;
                mime = "text/plain";
            }
        } else {
            try {
                if (resource.toURL() != null) {
                    method = UploadMethod.URL;
                    mime = "text/plain";
                }
            } catch (MalformedURLException e) {
                throw new IllegalArgumentException(
                        "Resource is not recognized as a zip file, or a directory, or a valid URL",
                        e);
            }
        }

        // Create store, upload data, and publish layers
        return createStore(workspace, StoreType.DATASTORES, storeName, method,
                DataStoreExtension.SHP, mime, resource, ParameterConfigure.ALL,
                new NameValuePair[0]);
    }

    // ==========================================================================
    // === COVERAGES
    // ==========================================================================

    /**
     * Controls how existing data is handled when the file is PUT into a datastore that (a) already exists and (b) already contains a schema that
     * matches the content of the file. It can take one of the two values:
     * <ul>
     * <li>{@link #APPEND} Data being uploaded is appended to the existing data. This is the default.
     * <li>{@link #OVERWRITE} Data being uploaded replaces any existing data.
     * </ul>
     * 
     * @author Carlo Cancellieri - carlo.cancellieri@geo-solutions.it
     */
    public static enum ParameterUpdate {
        /** Data being uploaded is appended to the existing data. */
        APPEND,
        /** Data being uploaded replaces any existing data. */
        OVERWRITE;

        /**
         * Returns a lowercase representation of the parameter. Useful when constructing the REST request.
         */
        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    /**
     * Represents the format used to GET, PUT or POST information via REST. For example consider the resource "foo". To request a representation of
     * foo as XML the request uri would end with "foo.xml". To request as JSON the request uri would end with "foo.json". When no format is specified
     * the server will use its own internal format, usually html.
     * <P>
     * In a POST or PUT operation the format specifies 1) the representatin of the content being sent to the server, and 2) the representation of the
     * response to be sent back. The former is specified with the Content-type header. To send a representation in XML, the content type "text/xml" or
     * "application/xml" would be used. The latter is specified with the Accepts header as specified in the above paragraph describing a GET
     * operation.
     * <P>
     * The following table defines the Content-type values for each format:
     * <ul>
     * <li>XML (application/xml)</li>
     * <li>JSON (application/json)</li>
     * <li>HTML (application/html)</li>
     * <li>SLD (application/vnd.ogc.sld+xml)</li>
     * </ul>
     */
    public enum Format {
        XML, JSON, HTML, SLD;

        /**
         * Gets the mime type from a format.
         * 
         * @param f the format key.
         * @return The content-type (mime), or {@code null} if not in the enum.
         */
        public static String getContentType(Format f) {
            switch (f) {
            case XML:
                return "application/xml";
            case HTML:
                return "application/html";
            case JSON:
                return "application/json";
            case SLD:
                return "application/vnd.ogc.sld+xml";
            default:
                return null;
            }
        }
        
        /**
         * Gets the mime type from a format.
         * 
         * @param f the format key.
         * @return The content-type (mime), or {@code null} if not in the enum.
         */
        public String getContentType() {
            return getContentType(this);
        }
        
        /**
         * Returns a lowercase representation of the parameter. Useful when constructing the REST request.
         */
        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    /**
     * Upload and publish a raster file.
     * 
     * @param workspace Workspace to use
     * @param coveragestore Name of the coveragestore
     * @param file file to upload
     * @param configure Configure parameter. See {@link ParameterConfigure}. It can be null.
     * @param params parameters to append to the url (can be null).<br>
     *        Accepted parameters are:
     *        <ul>
     *        <li><b>coverageName=name</b> coverageName parameter to append. Only works if configure is not set to ParameterConfigure.NONE.</li>
     *        </ul>
     * @return true if the operation completed successfully.
     */
    private boolean publishCoverage(String workspace, String coveragestore,
            CoverageStoreExtension extension, String mimeType, File file,
            ParameterConfigure configure, NameValuePair... params) throws FileNotFoundException {
        /*
         * This is an example with cUrl:
         * 
         * {@code curl -u admin:geoserver -XPUT -H 'Content-type: application/zip' \ --data-binary @$ZIPFILE \ http://$GSIP:$GSPORT/$SERVLET
         * /rest/workspaces/$WORKSPACE/coveragestores /$COVERAGESTORE/file.worldimage
         */
        return createCoverageStore(workspace, coveragestore, UploadMethod.FILE, extension,
                mimeType, file.toURI(), configure, params);
    }

    /**
     * Publish a raster file local to the server.
     * 
     * @param workspace Workspace to use
     * @param coveragestore Name of the coveragestore
     * @param file absolute path to the file location in the server
     * @param configure Configure parameter. It may be null.
     * @param update Accepted parameters are:
     *        <ul>
     *        <li>See <b>{@link #ParameterUpdate}</b></li>
     *        </ul>
     * @see #{@link ParameterConfigure}
     * @return true if the operation completed successfully.
     * @throws IllegalArgumentException
     * @throws FileNotFoundException
     */
    private boolean publishExternalCoverage(String workspace, String coveragestore,
            CoverageStoreExtension extension, String mimeType, File file,
            ParameterConfigure configure, ParameterUpdate update) throws FileNotFoundException,
            IllegalArgumentException {
        /*
         * Curl example:
         * 
         * {@code curl -u admin:geoserver -XPUT -H 'Content-type: application/zip' \
         * 
         * --data-binary @$ZIPFILE \
         * 
         * http://$GSIP:$GSPORT/$SERVLET/rest/workspaces/$WORKSPACE/coveragestores /$COVERAGESTORE/file.worldimage
         */
        return createCoverageStore(
                workspace,
                coveragestore,
                UploadMethod.EXTERNAL,
                extension,
                mimeType,
                file.toURI(),
                configure,
                (update != null) ? new NameValuePair[] { new NameValuePair("update", update
                        .toString()) } : (NameValuePair[]) null);
    }

    // ==========================================================================
    // === GEOTIFF
    // ==========================================================================

    /**
     * Upload and publish a GeoTIFF image.
     * 
     * @param workspace Workspace to use
     * @param storeName The store name to be used or created.
     * @param geotiff The GeoTIFF file.
     * @return true if success.
     * @throws FileNotFoundException if GeoTIFF file does not exist.
     */
    public boolean publishGeoTIFF(String workspace, String storeName, File geotiff)
            throws FileNotFoundException {
        return publishCoverage(workspace, storeName, CoverageStoreExtension.GEOTIFF,
                "image/geotiff", geotiff, ParameterConfigure.FIRST, (NameValuePair[]) null);
    }

    /**
     * Upload and publish a GeoTIFF image.
     * 
     * @param workspace Workspace to use
     * @param storeName Name of the coveragestore (if null the file name will be used)
     * @param coverageName the name of the coverage (if null the file name will be used)
     * @param geotiff file to upload
     * @return true if the operation completed successfully.
     * @throws FileNotFoundException if file does not exists
     * @throws IllegalArgumentException if workspace or geotiff are null
     */
    public boolean publishGeoTIFF(final String workspace, final String storeName,
            final String coverageName, final File geotiff) throws FileNotFoundException,
            IllegalArgumentException {
        if (workspace == null || geotiff == null)
            throw new IllegalArgumentException("Unable to proceed, some arguments are null");

        return publishCoverage(
                workspace,
                (storeName != null) ? storeName : FilenameUtils.getBaseName(geotiff
                        .getAbsolutePath()), CoverageStoreExtension.GEOTIFF, "image/geotiff",
                geotiff, ParameterConfigure.FIRST,
                (coverageName != null) ? new NameValuePair[] { new NameValuePair("coverageName",
                        coverageName) } : (NameValuePair[]) null);
    }

    /**
     * Same as {@link #publishGeoTIFF(String, String, String, File, String, ProjectionPolicy, String, double[])} but without the last parameter
     * (bbox). Kept here for backwards compatibility.
     * 
     * @deprecated use the former method with bbox set to null.
     */
    public boolean publishGeoTIFF(String workspace, String storeName, String resourceName,
            File geotiff, String srs, ProjectionPolicy policy, String defaultStyle)
            throws FileNotFoundException, IllegalArgumentException {
        return publishGeoTIFF(workspace, storeName, resourceName, geotiff, srs, policy,
                defaultStyle, null);
    }

    /**
     * Upload and publish a GeoTIFF image.
     * 
     * @param workspace Workspace to use
     * @param storeName Name of the coveragestore (if null the file name will be used)
     * @param coverageName the name of the coverage (if null the file name will be used)
     * @param geotiff file to upload
     * @param srs the native CRS
     * @param policy projection policy. See {@link ProjectionPolicy}.
     * @param defaultStyle the default style to apply.
     * @param bbox An array of 4 doubles indicating envelope in EPSG:4326. Order is [Xmin, Ymin, Xmax, Ymax].
     * @return true if the operation completed successfully.
     * @throws FileNotFoundException if file does not exists
     * @throws IllegalArgumentException if workspace or geotiff are null
     * 
     */
    public boolean publishGeoTIFF(String workspace, String storeName, String coverageName,
            File geotiff, String srs, ProjectionPolicy policy, String defaultStyle, double[] bbox)
            throws FileNotFoundException, IllegalArgumentException {
        if (workspace == null || storeName == null || geotiff == null || coverageName == null
                || srs == null || policy == null || defaultStyle == null)
            throw new IllegalArgumentException("Unable to run: null parameter");

        if (!createCoverageStore(
                workspace,
                (storeName != null) ? storeName : FilenameUtils.getBaseName(geotiff
                        .getAbsolutePath()), UploadMethod.FILE, CoverageStoreExtension.GEOTIFF,
                "image/geotiff", geotiff.toURI(), ParameterConfigure.NONE, (NameValuePair[]) null)) {
            LOGGER.error("Unable to create coverage store for coverage: " + geotiff);
            return false;
        }

        // config coverage props (srs)
        final GSCoverageEncoder coverageEncoder = new GSCoverageEncoder();
        coverageEncoder.setName(coverageName);
        coverageEncoder.setTitle(coverageName);
        coverageEncoder.setSRS(srs);
        coverageEncoder.setProjectionPolicy(policy);
        if (bbox != null && bbox.length == 4) {
            coverageEncoder.setLatLonBoundingBox(bbox[0], bbox[1], bbox[2], bbox[3], DEFAULT_CRS);
        }

        if (!createCoverage(workspace, storeName, coverageEncoder)) {
            LOGGER.error("Unable to create a coverage store for coverage: " + geotiff);
            return false;
        }

        // config layer props (style, ...)
        final GSLayerEncoder layerEncoder = new GSLayerEncoder();
        layerEncoder.setDefaultStyle(defaultStyle);

        return configureLayer(workspace, coverageName, layerEncoder);
    }

    /**
     * Publish a GeoTiff already in a filesystem readable by GeoServer.
     * 
     * @param workspace an existing workspace
     * @param storeName the coverageStore to be created
     * @param geotiff the geoTiff to be published
     * @param srs the native CRS
     * @param policy projection policy. See {@link ProjectionPolicy}.
     * @param defaultStyle the default style to apply.
     * 
     * @return true if the operation completed successfully.
     * @throws FileNotFoundException if file does not exists
     * @throws IllegalArgumentException if any of the mandatory parameters are null.
     */
    public boolean publishExternalGeoTIFF(String workspace, String storeName, File geotiff,
            String coverageName, String srs, ProjectionPolicy policy, String defaultStyle)
            throws FileNotFoundException, IllegalArgumentException {
        if (workspace == null || storeName == null || geotiff == null || coverageName == null
                || srs == null || policy == null || defaultStyle == null)
            throw new IllegalArgumentException("Unable to run: null parameter");

        // config coverage props (srs)
        final GSCoverageEncoder coverageEncoder = new GSCoverageEncoder();
        coverageEncoder.setName(coverageName);
        coverageEncoder.setTitle(coverageName);
        coverageEncoder.setSRS(srs);
        coverageEncoder.setProjectionPolicy(policy);

        // config layer props (style, ...)
        final GSLayerEncoder layerEncoder = new GSLayerEncoder();
        layerEncoder.setDefaultStyle(defaultStyle);

        return publishExternalGeoTIFF(workspace, storeName, geotiff, coverageEncoder, layerEncoder) != null ? true
                : false;
    }

    /**
     * Publish a GeoTiff already in a filesystem readable by GeoServer.
     * 
     * @param workspace an existing workspace
     * @param storeName the coverageStore to be created
     * @param geotiff the geoTiff to be published
     * @param coverageEncoder coverage details. See {@link GSCoverageEncoder}.
     * @param layerEncoder layer details, See {@link GSLayerEncoder}.
     * 
     * @return true if the operation completed successfully.
     * @throws FileNotFoundException if file does not exists
     * @throws IllegalArgumentException if any of the mandatory parameters are null.
     */
    public RESTCoverageStore publishExternalGeoTIFF(final String workspace, final String storeName,
            final File geotiff, final GSCoverageEncoder coverageEncoder,
            final GSLayerEncoder layerEncoder) throws IllegalArgumentException,
            FileNotFoundException {

        if (workspace == null || geotiff == null || storeName == null || layerEncoder == null
                || coverageEncoder == null)
            throw new IllegalArgumentException("Unable to run: null parameter");

        final String coverageName = coverageEncoder.getName();
        if (coverageName.isEmpty()) {
            throw new IllegalArgumentException("Unable to run: empty coverage store name");
        }

        // create store
        final boolean store = publishExternalCoverage(workspace, storeName,
                CoverageStoreExtension.GEOTIFF, "text/plain", geotiff, ParameterConfigure.NONE,
                ParameterUpdate.OVERWRITE);
        if (!store) {
            return null;
        }

        // create Coverage Store
        if (!createCoverage(workspace, storeName, coverageEncoder)) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Unable to create a coverage for the store:" + coverageName);
            return null;
        }

        // create Layer
        if (configureLayer(workspace, coverageName, layerEncoder)) {
            GeoServerRESTReader reader;
            try {
                reader = new GeoServerRESTReader(this.restURL, this.gsuser, this.gspass);
                return reader.getCoverageStore(workspace, storeName);
            } catch (MalformedURLException e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
        return null;
    }

    // ==========================================================================
    // === WORLDIMAGE
    // ==========================================================================

    /**
     * Publish a zipped worldimage file. It is assumed that the the zip-file contain the *.prj to set the srs.
     * 
     * @param workspace Workspace to use
     * @param coveragestore Name of the coveragestore
     * @param zipFile zip file to upload
     * 
     * @return true if the operation completed successfully.
     */
    public boolean publishWorldImage(String workspace, String coveragestore, File zipFile)
            throws FileNotFoundException {
        return publishWorldImage(workspace, coveragestore, zipFile, ParameterConfigure.FIRST,
                (NameValuePair) null);
    }

    /**
     * Publish a zipped worldimage file. It is assumed that the the zip-file contain the *.prj to set the srs.
     * 
     * @param workspace Workspace to use
     * @param coveragestore Name of the coveragestore
     * @param zipFile zip file to upload
     * @param configure Configure parameter. See {@link ParameterConfigure}. It can be null.
     * @param params parameters to append to the url (can be null).<br>
     *        Accepted parameters are:
     *        <ul>
     *        <li><b>coverageName=name</b> coverageName parameter to append. Only works if configure is not set to {@link ParameterConfigure#NONE}.</li>
     *        </ul>
     * @return true if the operation completed successfully.
     */
    public boolean publishWorldImage(String workspace, String coveragestore, File zipFile,
            ParameterConfigure configure, NameValuePair... params) throws FileNotFoundException {
        return publishCoverage(workspace, coveragestore, CoverageStoreExtension.WORLDIMAGE,
                "application/zip", zipFile, configure, params);
    }

    // ==========================================================================
    // === MOSAIC
    // ==========================================================================

    /**
     * Publish imagemosaic as zip file.
     * 
     * @param workspace Workspace to use
     * @param storeName Name of the coveragestore
     * @param zipFile file to upload
     * 
     * @return true if the operation completed successfully.
     */
    public boolean publishImageMosaic(String workspace, String storeName, File zipFile)
            throws FileNotFoundException {
        return publishCoverage(workspace, storeName, CoverageStoreExtension.IMAGEMOSAIC,
                "application/zip", zipFile, ParameterConfigure.FIRST, (NameValuePair[]) null);
    }

    /**
     * Publish imagemosaic as zip file.
     * 
     * @param workspace Workspace to use
     * @param storeName Name of the coveragestore
     * @param zipFile file to upload
     * @param configure Configure parameter. See {@link ParameterConfigure}. It can be null.
     * @param params parameters to append to the url (can be null).<br>
     *        Accepted parameters are:
     *        <ul>
     *        <li><b>coverageName=name</b> coverageName parameter to append. Only works if configure is not set to ParameterConfigure.NONE.</li>
     *        </ul>
     * 
     * @return true if the operation completed successfully.
     */
    public boolean publishImageMosaic(String workspace, String storeName, File zipFile,
            ParameterConfigure configure, NameValuePair... params) throws FileNotFoundException {
        return publishCoverage(workspace, storeName, CoverageStoreExtension.IMAGEMOSAIC,
                "application/zip", zipFile, configure, params);
    }

    /**
     * Publish a Mosaic from a filesystem currently readable by GeoServer.
     * 
     * @param workspace an existing workspace
     * @param storeName the name of the coverageStore to be created
     * @param mosaicDir the directory where the raster images are located
     * @param configure a specify if a coverage should be configured
     * 
     * @return true if the operation completed successfully.
     * @throws FileNotFoundException
     */
    public RESTCoverageStore createExternaMosaicDatastore(String workspace, String storeName,
            File mosaicDir, ParameterConfigure configure, ParameterUpdate update)
            throws FileNotFoundException {

        /*
         * Carlo (23 Nov 2011): commented out since this directory should be readable by target GeoServer not the calling client!
         */
        if (!mosaicDir.isDirectory()) {
            if (LOGGER.isWarnEnabled())
                LOGGER.warn("Directory '"
                        + mosaicDir
                        + "' not exists locally. Continue: please check existance on the remote server.");
        }

        String sUrl = restURL + "/rest/workspaces/" + workspace + "/coveragestores/" + storeName
                + "/external.imagemosaic?configure=" + configure.toString() + "&update="
                + update.toString();
        String sendResult = HTTPUtils.put(sUrl, mosaicDir.toURI().toString(), "text/plain", gsuser,
                gspass);
        return RESTCoverageStore.build(sendResult);
    }

    /**
     * Publish a Mosaic already in a filesystem readable by GeoServer.
     * 
     * @param workspace an existing workspace
     * @param storeName the name of the coverageStore to be created
     * @param mosaicDir the directory where the raster images are located
     * @param srs the coverage declared SRS
     * @param defaultStyle may be null
     * 
     * @return true if the operation completed successfully.
     * 
     * @throws FileNotFoundException
     */
    public boolean publishExternalMosaic(String workspace, String storeName, File mosaicDir,
            String srs, String defaultStyle) throws FileNotFoundException {

        final GSCoverageEncoder coverageEncoder = new GSCoverageEncoder();
        coverageEncoder.setSRS(srs);
        final String name = FilenameUtils.getBaseName(mosaicDir.getName());
        coverageEncoder.setName(name);

        final GSLayerEncoder layerEncoder = new GSLayerEncoder();
        layerEncoder.setDefaultStyle(defaultStyle);

        return publishExternalMosaic(workspace, storeName, mosaicDir, coverageEncoder, layerEncoder);
    }

    /**
     * @deprecated use {@link #publishExternalMosaic(String, String, File, GSCoverageEncoder, GSLayerEncoder)}
     */
    public boolean createExternalMosaic(String workspace, String storeName, File mosaicDir,
            GSCoverageEncoder coverageEncoder, GSLayerEncoder layerEncoder)
            throws FileNotFoundException {
        return publishExternalMosaic(workspace, storeName, mosaicDir, coverageEncoder, layerEncoder);
    }

    /**
     * Publish a Mosaic already in a filesystem readable by GeoServer.
     * 
     * @param workspace an existing workspace
     * @param storeName the name of the coverageStore to be created
     * @param mosaicDir the directory where the raster images are located
     * @param coverageEncoder the set of parameters to be set to the coverage (bbox, srs, ...)
     * @param layerEncoder the set of parameters to be set to the layer (defaultstyle, wmspath, ...)
     * 
     * @return true if the operation completed successfully.
     * @throws FileNotFoundException
     */
    public boolean publishExternalMosaic(String workspace, final String storeName, File mosaicDir,
            GSCoverageEncoder coverageEncoder, GSLayerEncoder layerEncoder)
            throws FileNotFoundException, IllegalArgumentException {

        if (coverageEncoder == null) {
            throw new IllegalArgumentException("no coverageEncoder provided for mosaic "
                    + mosaicDir);
        }
        // override name to match the FIRST configured coverage
        String coverageName = coverageEncoder.getName();

        if (layerEncoder == null) {
            throw new IllegalArgumentException("no layerEncoder provided for " + workspace + ":"
                    + coverageName);
        }

        RESTCoverageStore store = createExternaMosaicDatastore(workspace, storeName, mosaicDir,
                ParameterConfigure.NONE, ParameterUpdate.OVERWRITE);

        if (store == null) {
            return false;
        }
        if (!createCoverage(workspace, storeName, coverageEncoder)) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Unable to create a coverage for the store:" + coverageName);
            return false;
        }
        if (!configureLayer(workspace, coverageName, layerEncoder)) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Unable to configure the Layer for the coverage:" + coverageName);
            return false;
        }
        return true;
    }

    // ==========================================================================
    // === REMOVING THINGS
    // ==========================================================================

    /**
     * Remove the Coverage configuration from GeoServer.
     * <P>
     * First, the associated layer is removed, then the Coverage configuration itself.
     * <P>
     * <B>CHECKME</B> Maybe the coveragestore has to be removed as well.
     * 
     * @return true if the operation completed successfully.
     */
    public boolean unpublishCoverage(String workspace, String storename, String layerName) {
        try {
            final String fqLayerName;

            // this null check is here only for backward compatibility.
            // workspace
            // shall be mandatory.
            if (workspace == null) {

                fqLayerName = layerName;

                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Null workspace while configuring layer : " + layerName
                            + " -- This behavior is deprecated.");
                }
            } else {
                fqLayerName = workspace + ":" + layerName;
            }
            // delete related layer
            URL deleteLayerUrl = new URL(restURL + "/rest/layers/" + fqLayerName);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Going to delete " + "/rest/layers/" + fqLayerName);
            }
            boolean layerDeleted = HTTPUtils
                    .delete(deleteLayerUrl.toExternalForm(), gsuser, gspass);
            if (!layerDeleted) {
                LOGGER.warn("Could not delete layer '" + fqLayerName + "'");
                return false;
            }
            // delete the coverage
            URL deleteCovUrl = new URL(restURL + "/rest/workspaces/" + workspace
                    + "/coveragestores/" + storename + "/coverages/" + layerName);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Going to delete " + "/rest/workspaces/" + workspace
                        + "/coveragestores/" + storename + "/coverages/" + layerName);
            }
            boolean covDeleted = HTTPUtils.delete(deleteCovUrl.toExternalForm(), gsuser, gspass);
            if (!covDeleted) {
                LOGGER.warn("Could not delete coverage " + workspace + ":" + storename + "/"
                        + layerName + ", but layer was deleted.");
            } else {
                LOGGER.info("Coverage successfully deleted " + workspace + ":" + storename + "/"
                        + layerName);
            }
            return covDeleted;

            // the covstore is still there: should we delete it?

        } catch (MalformedURLException ex) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error(ex.getLocalizedMessage(), ex);
            return false;
        }
    }

    /**
     * Removes the featuretype and the associated layer.
     * <P>
     * You may also want to {@link #removeDatastore(String, String) remove the datastore}.
     * 
     * @return true if the operation completed successfully.
     */
    public boolean unpublishFeatureType(String workspace, String storename, String layerName) {
        try {

            final String fqLayerName;
            // this null check is here only for backward compatibility.
            // workspace
            // shall be mandatory.
            if (workspace == null) {

                fqLayerName = layerName;

                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Null workspace while configuring layer : " + layerName
                            + " -- This behavior is deprecated.");
                }
            } else {
                fqLayerName = workspace + ":" + layerName;
            }
            // delete related layer
            URL deleteLayerUrl = new URL(restURL + "/rest/layers/" + fqLayerName);
            boolean layerDeleted = HTTPUtils
                    .delete(deleteLayerUrl.toExternalForm(), gsuser, gspass);
            if (!layerDeleted) {
                LOGGER.warn("Could not delete layer '" + fqLayerName + "'");
                return false;
            }
            // delete the coverage
            URL deleteFtUrl = new URL(restURL + "/rest/workspaces/" + workspace + "/datastores/"
                    + storename + "/featuretypes/" + layerName);
            boolean ftDeleted = HTTPUtils.delete(deleteFtUrl.toExternalForm(), gsuser, gspass);
            if (!ftDeleted) {
                LOGGER.warn("Could not delete featuretype " + workspace + ":" + storename + "/"
                        + layerName + ", but layer was deleted.");
            } else {
                LOGGER.info("FeatureType successfully deleted " + workspace + ":" + storename + "/"
                        + layerName);
            }

            return ftDeleted;

            // the store is still there: should we delete it?

        } catch (MalformedURLException ex) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error(ex.getLocalizedMessage(), ex);
            return false;
        }
    }

    /**
     * Remove recursively a given Datastore in a given Workspace.
     * 
     * @param workspace The name of the workspace
     * @param storename The name of the Datastore to remove.
     * @return <TT>true</TT> if the datastore was successfully removed.
     * 
     * @deprecated will be removed in next release use {@link GeoServerRESTPublisher#removeDatastore(String, String, boolean)}
     */
    public boolean removeDatastore(String workspace, String storename) {
        try {
            return removeDatastore(workspace, storename, true);
        } catch (IllegalArgumentException e) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Arguments may not be null or empty!", e);
        }
        return false;
    }

    /**
     * Remove a given Datastore in a given Workspace.
     * 
     * @param workspace The name of the workspace
     * @param storename The name of the Datastore to remove.
     * @param recurse if remove should be performed recursively
     * @throws IllegalArgumentException if workspace or storename are null or empty
     * @return <TT>true</TT> if the datastore was successfully removed.
     */
    public boolean removeDatastore(String workspace, String storename, final boolean recurse)
            throws IllegalArgumentException {
        try {
            if (workspace == null || storename == null)
                throw new IllegalArgumentException("Arguments may not be null!");
            if (workspace.isEmpty() || storename.isEmpty())
                throw new IllegalArgumentException("Arguments may not be empty!");

            final StringBuilder url = new StringBuilder(restURL);
            url.append("/rest/workspaces/").append(workspace).append("/datastores/")
                    .append(storename);
            if (recurse)
                url.append("?recurse=true");
            final URL deleteStore = new URL(url.toString());

            boolean deleted = HTTPUtils.delete(deleteStore.toExternalForm(), gsuser, gspass);
            if (!deleted) {
                LOGGER.warn("Could not delete datastore " + workspace + ":" + storename);
            } else {
                LOGGER.info("Datastore successfully deleted " + workspace + ":" + storename);
            }

            return deleted;
        } catch (MalformedURLException ex) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error(ex.getLocalizedMessage(), ex);
            return false;
        }
    }

    /**
     * Remove recursively a given CoverageStore in a given Workspace.
     * 
     * @param workspace The name of the workspace
     * @param storename The name of the CoverageStore to remove.
     * @return <TT>true</TT> if the CoverageStore was successfully removed.
     * @deprecated use {@link #removeCoverageStore(String, String, boolean)}
     */
    public boolean removeCoverageStore(String workspace, String storename) {
        try {
            return removeCoverageStore(workspace, storename, true);
        } catch (IllegalArgumentException e) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Arguments may not be null or empty!", e);
        }
        return false;
    }

    /**
     * Remove a given CoverageStore in a given Workspace.
     * 
     * @param workspace The name of the workspace
     * @param storename The name of the CoverageStore to remove.
     * @param recurse if remove should be performed recursively
     * @return <TT>true</TT> if the CoverageStore was successfully removed.
     */
    public boolean removeCoverageStore(final String workspace, final String storename,
            final boolean recurse) throws IllegalArgumentException {
        try {
            if (workspace == null || storename == null)
                throw new IllegalArgumentException("Arguments may not be null!");
            if (workspace.isEmpty() || storename.isEmpty())
                throw new IllegalArgumentException("Arguments may not be empty!");

            final StringBuilder url = new StringBuilder(restURL);
            url.append("/rest/workspaces/").append(workspace).append("/coveragestores/")
                    .append(storename);
            if (recurse)
                url.append("?recurse=true");
            final URL deleteStore = new URL(url.toString());

            boolean deleted = HTTPUtils.delete(deleteStore.toExternalForm(), gsuser, gspass);
            if (!deleted) {
                LOGGER.warn("Could not delete CoverageStore " + workspace + ":" + storename);
            } else {
                LOGGER.info("CoverageStore successfully deleted " + workspace + ":" + storename);
            }
            return deleted;

        } catch (MalformedURLException ex) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error(ex.getLocalizedMessage(), ex);
            return false;
        }
    }

    /**
     * Remove the workspace given Workspace using default parameters
     * 
     * @see {@link GeoServerRESTPublisher#removeWorkspace(String, boolean)}
     * @param workspace the workspace to remove
     * @return true if success, false otherwise
     * @deprecated {@link #removeWorkspace(String, boolean)}
     */
    public boolean removeWorkspace(String workspace) {
        try {
            return removeWorkspace(workspace, false);
        } catch (IllegalArgumentException e) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Arguments may not be null or empty!", e);
        }
        return false;
    }

    /**
     * Remove a given Workspace.
     * 
     * @param workspace The name of the workspace
     * @param recurse The recurse parameter is used to recursively delete all resources contained by the specified workspace. This includes data
     *        stores, coverage stores, feature types, etc... Allowable values for this parameter are <i>true</i> or <i>false</i>. The default value is
     *        <i>false</i>.
     * @return <TT>true</TT> if the WorkSpace was successfully removed.
     */
    public boolean removeWorkspace(String workspace, boolean recurse)
            throws IllegalArgumentException {
        workspace = sanitize(workspace);
        try {
            if (workspace == null)
                throw new IllegalArgumentException("Arguments may not be null!");
            if (workspace.isEmpty())
                throw new IllegalArgumentException("Arguments may not be empty!");

            StringBuffer url = new StringBuffer(restURL).append("/rest/workspaces/").append(
                    workspace);
            if (recurse)
                url.append("?recurse=true");
            final URL deleteUrl = new URL(url.toString());
            boolean deleted = HTTPUtils.delete(deleteUrl.toExternalForm(), gsuser, gspass);
            if (!deleted) {
                LOGGER.warn("Could not delete Workspace " + workspace);
            } else {
                LOGGER.info("Workspace successfully deleted " + workspace);
            }

            return deleted;
        } catch (MalformedURLException ex) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error(ex.getLocalizedMessage(), ex);
            return false;
        }
    }

    /**
     * Remove a layer group.
     * 
     * @param workspace the layer group workspace.
     * @param name the layer group name.
     * @return true if succeeded.
     */    
    public boolean removeLayerGroup(String workspace, String name) {
        String url = restURL + "/rest";
        if (workspace == null) {
            url += "/layergroups/" + name;
        } else {
            url += "/workspaces/" + workspace + "/layergroups/" + name;
        }  
    
        try {
            URL deleteUrl = new URL(url);
            boolean deleted = HTTPUtils.delete(deleteUrl.toExternalForm(), gsuser, gspass);
            if (!deleted) {
                if (LOGGER.isWarnEnabled())
                    LOGGER.warn("Could not delete layergroup " + name);
            } else {
                if (LOGGER.isInfoEnabled())
                    LOGGER.info("Layergroup successfully deleted: " + name);
            }

            return deleted;
        } catch (MalformedURLException ex) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error(ex.getLocalizedMessage(), ex);
            return false;
        }        
    }
    
    /**
     * Remove a layer group.
     * 
     * @param name the layer group name.
     * @return true if succeeded.
     */
    public boolean removeLayerGroup(String name) {
        return removeLayerGroup(null, name);
    }

    /**
     * remove a generic given layer from a given workspace
     * 
     * @param workspace
     * @param layerName
     * @return true if success
     */
    public boolean removeLayer(final String workspace, final String layerName) {

        final String fqLayerName;

        // this null check is here only for backward compatibility. workspace
        // shall be mandatory.
        if (workspace == null) {

            fqLayerName = layerName;

            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn("Null workspace while removing layer : " + layerName
                        + " -- This behavior is deprecated.");
            }
        } else {
            fqLayerName = workspace + ":" + layerName;
        }
        if (layerName == null) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("Null layerName : " + layerName);
            }
            return false;
        }

        final String url = restURL + "/rest/layers/" + fqLayerName;

        boolean result = HTTPUtils.delete(url, gsuser, gspass);
        if (result) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Layer successfully removed: " + fqLayerName);
            }
        } else {
            if (LOGGER.isWarnEnabled())
                LOGGER.warn("Error removing layer " + fqLayerName);
        }

        return result;
    }

    // ==========================================================================
    // === CATALOG REFRESHING
    // ==========================================================================

    /**
     * 
     * /workspaces/<ws>/datastores/<ds>.xml /workspaces/<ws>/coveragestores/<ds>.xml
     * 
     * @param workspace
     * @param storeName
     * @param mosaicDir
     * @return true if successfully reloaded
     * @throws FileNotFoundException
     * @throws IllegalArgumentException
     * @throws MalformedURLException
     */
    public boolean reloadStore(String workspace, final String storeName, StoreType storeType)
            throws IllegalArgumentException, MalformedURLException {
        final String url = HTTPUtils.append(this.restURL, "/rest/workspaces/", workspace, "/",
                storeType.toString(), "/", storeName, ".xml").toString();
        final String store = HTTPUtils.get(url, this.gsuser, this.gspass);

        if (store != null) {
            String storeTag=storeType.getTypeName();
//            switch (storeType) {
//            case COVERAGESTORES:
//                storeTag = storeType.toString().replaceAll("store", "");
//                break;
//            case DATASTORES:
//                storeTag = "featureTypes";
//                break;
//            default:
//                throw new IllegalArgumentException("Unrecognized type");
//            }

            String startTag = "<" + storeTag + ">";
            int start = store.indexOf(startTag);
            String endTag = "</" + storeTag + ">";
            int stop = store.indexOf(endTag) + endTag.length();
            return HTTPUtils.putXml(url, store.subSequence(0, start) + store.substring(stop),
                    this.gsuser, this.gspass) != null ? true : false;
        } else
            return false;
    }

    /**
     * Reload the target geoserver configuration
     * 
     * @return true if success
     * 
     * @see <a href="http://docs.geoserver.org/stable/en/user/restconfig/rest-config-api.html">GeoServer REST Config API</a>
     */
    public boolean reload() {
        String sUrl = restURL + "/rest/reload";
        String result = HTTPUtils.post(sUrl, "", "text/plain", gsuser, gspass);
        return result != null;
    }

    /**
     * Reset the target geoserver configuration
     * 
     * @return true if success
     * 
     * @see <a href="http://docs.geoserver.org/stable/en/user/restconfig/rest-config-api.html">GeoServer REST Config API</a>
     */
    public boolean reset() {
        String sUrl = restURL + "/rest/reset";
        String result = HTTPUtils.post(sUrl, "", "text/plain", gsuser, gspass);
        return result != null;
    }

    // ==========================================================================
    // === MISCELLANEA
    // ==========================================================================

    /**
     * Allows to configure some layer attributes such and DefaultStyle
     * 
     * @param workspace
     * @param resourceName the name of the resource to use (featureStore or coverageStore name)
     * @param layer the layer encoder used to configure the layer
     * @return true if success
     * @throws IllegalArgumentException if some arguments are null or empty
     * 
     * @TODO WmsPath
     */
    public boolean configureLayer(final String workspace, final String resourceName,
            final GSLayerEncoder layer) throws IllegalArgumentException {

        if (workspace == null || resourceName == null || layer == null) {
            throw new IllegalArgumentException("Null argument");
        }
        // TODO: check this usecase, layer should always be defined
        if (workspace.isEmpty() || resourceName.isEmpty() || layer.isEmpty()) {
            throw new IllegalArgumentException("Empty argument");
        }

        final String fqLayerName = workspace + ":" + resourceName;

        final String url = restURL + "/rest/layers/" + fqLayerName;

        String layerXml = layer.toString();
        String sendResult = HTTPUtils.putXml(url, layerXml, gsuser, gspass);
        if (sendResult != null) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Layer successfully configured: " + fqLayerName);
            }
        } else {
            if (LOGGER.isWarnEnabled())
                LOGGER.warn("Error configuring layer " + fqLayerName + " (" + sendResult + ")");
        }

        return sendResult != null;
    }

    /**
     * Create a new LayerGroup using the specified encoder
     * 
     * @param name name of the layer group
     * @param group group encoder
     * @return true if operation was successful
     */
    public boolean createLayerGroup(String name, GSLayerGroupEncoder group) {
        return createLayerGroup(null, name, group);
    }
    
    /**
     * Create a new LayerGroup using the specified encoder
     * 
     * @param workspace name of the workspace
     * @param name name of the layer group
     * @param group group encoder
     * @return true if operation was successful
     */
    public boolean createLayerGroup(String workspace, String name, GSLayerGroupEncoder group) {
        String url = restURL + "/rest";
        if (workspace == null) {            
            url += "/layergroups/";
        } else {
            group.setWorkspace(workspace);
            url += "/workspaces/" + workspace + "/layergroups/";
        }  
    
        group.setName(name);
        
        String sendResult = HTTPUtils.postXml(url, group.toString(), gsuser, gspass);
        if (sendResult != null) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("LayerGroup successfully configured: " + name);
            }
        } else {
            if (LOGGER.isWarnEnabled())
                LOGGER.warn("Error configuring LayerGroup " + name + " (" + sendResult + ")");
        }

        return sendResult != null;  
    }
    
    /**
     * Update a LayerGroup using the specified encoder
     * 
     * @param name name of the layer group
     * @param group group encoder
     * @return true if operation was successful
     */
    public boolean configureLayerGroup(String name, GSLayerGroupEncoder group) {
        return configureLayerGroup(null, name, group);
    }
    
    /**
     * Update a LayerGroup using the specified encoder
     * 
     * @param workspace name of the workspace
     * @param name name of the layer group
     * @param group group encoder
     * @return true if operation was successful
     */    
    public boolean configureLayerGroup(String workspace, String name, GSLayerGroupEncoder group) {
        String url = restURL + "/rest";
        if (workspace == null) {
            url += "/layergroups/" + name;
        } else {
            url += "/workspaces/" + workspace + "/layergroups/" + name;
        }  
        
        String sendResult = HTTPUtils.putXml(url, group.toString(), gsuser, gspass);
        if (sendResult != null) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("LayerGroup successfully configured: " + name);
            }
        } else {
            if (LOGGER.isWarnEnabled())
                LOGGER.warn("Error configuring LayerGroup " + name + " (" + sendResult + ")");
        }

        return sendResult != null;         
    }
    
    /**
     * Configure an existent coverage in a given workspace and coverage store
     * 
     * @param ce contains the coverage name to configure and the configuration to apply
     * @param wsname the workspace to search for existent coverage
     * @param csname the coverage store to search for existent coverage
     * @return true if success
     */
    public boolean configureCoverage(final GSCoverageEncoder ce, final String wsname,
            final String csname) {
        final String cname = ce.getName();
        if (cname == null) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Unable to configure a coverage with no name try using GSCoverageEncoder.setName(String)");
            return false;
        }
        // retrieve coverage name
        GeoServerRESTReader reader;
        try {
            reader = new GeoServerRESTReader(restURL, gsuser, gspass);
        } catch (MalformedURLException e) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error(e.getLocalizedMessage(), e);
            return false;
        }
        final RESTCoverageList covList = reader.getCoverages(wsname, csname);
        if (covList.isEmpty()) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("No coverages found in new coveragestore " + csname);
            return false;
        }
        final Iterator<NameLinkElem> it = covList.iterator();
        boolean found = false;
        while (it.hasNext()) {
            NameLinkElem nameElem = it.next();
            if (nameElem.getName().equals(cname)) {
                found = true;
                break;
            }
        }
        // if no coverage to configure is found return false
        if (!found) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("No coverages found in new coveragestore " + csname + " called "
                        + cname);
            return false;
        }

        // configure the selected coverage
        final String url = restURL + "/rest/workspaces/" + wsname + "/coveragestores/" + csname
                + "/coverages/" + cname + ".xml";

        final String xmlBody = ce.toString();
        final String sendResult = HTTPUtils.putXml(url, xmlBody, gsuser, gspass);
        if (sendResult != null) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Coverage successfully configured " + wsname + ":" + csname + ":"
                        + cname);
            }
        } else {
            if (LOGGER.isWarnEnabled())
                LOGGER.warn("Error configuring coverage " + wsname + ":" + csname + ":" + cname
                        + " (" + sendResult + ")");
        }

        return sendResult != null;
    }

    /**
     * @deprecated use {@link #createCoverage(String, String, GSCoverageEncoder)}
     */
    public boolean createCoverage(final GSCoverageEncoder ce, final String wsname,
            final String csname) {
        return createCoverage(wsname, csname, ce);
    }

    /**
     * Create a new coverage in a given workspace and coverage store
     * 
     * @param wsname the workspace to search for existent coverage
     * @param storeName an existent store name to use as data source
     * @param ce contains the coverage name to create and the configuration to apply
     * @return true if success
     * @throws IllegalArgumentException if arguments are null or empty
     */
    public boolean createCoverage(final String wsname, final String storeName,
            final GSCoverageEncoder ce) throws IllegalArgumentException {
        return createResource(wsname, StoreType.COVERAGESTORES, storeName, ce);
    }

    /**
     * Create a new resource in a given workspace and store
     * 
     * @param wsname the workspace to search for existent coverage
     * @param storeName an existent store name to use as data source
     * @param re contains the coverage name to create and the configuration to apply
     * 
     * @TODO For FeatureType: The list parameter is used to control the category of feature types that are returned. It can take one of the three
     *       values configured, available, or all.
     * 
     *       configured - Only setup or configured feature types are returned. This is the default value. available - Only unconfigured feature types
     *       (not yet setup) but are available from the specified datastore will be returned. available_with_geom - Same as available but only
     *       includes feature types that have a geometry attribute. all - The union of configured and available.
     * 
     * 
     * @return true if success
     * @throws IllegalArgumentException if arguments are null or empty
     */
    private boolean createResource(String workspace, StoreType dsType, String storeName,
            GSResourceEncoder re) throws IllegalArgumentException {
        if (workspace == null || dsType == null || storeName == null || re == null) {
            throw new IllegalArgumentException("Null argument");
        }
        StringBuilder sbUrl = new StringBuilder(restURL).append("/rest/workspaces/")
                .append(workspace).append("/").append(dsType).append("/").append(storeName)
                .append("/").append(dsType.getTypeNameWithFormat(Format.XML));

        final String resourceName = re.getName();
        if (resourceName == null) {
            throw new IllegalArgumentException(
                    "Unable to configure a coverage using unnamed coverage encoder");
        }

        final String xmlBody = re.toString();
        final String sendResult = HTTPUtils.postXml(sbUrl.toString(), xmlBody, gsuser, gspass);
        if (sendResult != null) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(dsType + " successfully created " + workspace + ":" + storeName + ":"
                        + resourceName);
            }
        } else {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Error creating coverage " + workspace + ":" + storeName + ":"
                        + resourceName + " (" + sendResult + ")");
        }

        return sendResult != null;
    }

    /**
     * Appends ".DUMMY" to any string containing a dot (<i>sic</i>).
     */
    protected String sanitize(String s) {
        if (s.indexOf(".") != -1) {
            return s + ".DUMMY";
        }
        return s;
    }

    /**
     * Append params generating a string in the form:
     * <p>
     * NAME_0=VALUE_0&NAME_1=VALUE_1&....&NAME_n-1=VALUE_n-1
     * </p>
     * </br>
     * 
     * @param params an array of NameValuePair
     * @return the parameter string or empty an string
     */
    private String appendParameters(NameValuePair... params) {
        StringBuilder sbUrl = new StringBuilder();
        // append parameters
        if (params != null) {
            final int paramsSize = params.length;
            if (paramsSize > 0) {
                int i = 0;
                NameValuePair param = params[i];
                while (param != null && i++ < paramsSize) {
                    final String name = param.getName();
                    final String value = param.getValue();
                    // success
                    if (name != null && !name.isEmpty() && value != null && !value.isEmpty()) {
                        sbUrl.append(name).append("=").append(value);
                        // end cycle
                        param = null;
                    } else {
                        // next value
                        param = params[i];
                    }
                }
                for (; i < paramsSize; i++) {
                    param = params[i];
                    if (param != null) {
                        final String name = param.getName();
                        final String value = param.getValue();
                        sbUrl.append(name).append("=").append(value);
                        if (name != null && !name.isEmpty() && value != null && !value.isEmpty()) {
                            sbUrl.append("&").append(name).append("=").append(value);
                        }

                    }

                }
            }
        }
        return sbUrl.toString();
    }

    /**
     * URL-encodes a String.
     * 
     * @param s The original string.
     * @return The encoded string.
     */
    protected String encode(String s) {
        // try {
        // return URLEncoder.encode(s,"UTF-8");
        // } catch (UnsupportedEncodingException e) {
        // LOGGER.warn("Error encoding :"+s+" with UTF-8: "+e.getLocalizedMessage());
        return URLEncoder.encode(s);
        // }
    }
}
