/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.esri.gpt.control.publication; import com.esri.gpt.catalog.publication.ProcessedRecord; import com.esri.gpt.catalog.publication.ProcessingContext; import com.esri.gpt.catalog.publication.ProcessorFactory; import com.esri.gpt.catalog.publication.ResourceProcessor; import com.esri.gpt.catalog.publication.UploadRequest; import com.esri.gpt.catalog.publication.ValidationRequest; import com.esri.gpt.catalog.schema.Schema; import com.esri.gpt.catalog.schema.ValidationException; import com.esri.gpt.control.view.SelectablePublishers; import com.esri.gpt.framework.context.RequestContext; import com.esri.gpt.framework.http.HttpClientRequest; import com.esri.gpt.framework.jsf.BaseActionListener; import com.esri.gpt.framework.jsf.MessageBroker; import com.esri.gpt.framework.security.principal.Publisher; import com.esri.gpt.framework.util.UuidUtil; import com.esri.gpt.framework.util.Val; import de.tudresden.gis.db.McpPublish; import de.tudresden.gis.manage.xml.Constant; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.text.MessageFormat; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.event.AbortProcessingException; import javax.faces.event.ActionEvent; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItem; import org.n52.movingcode.runtime.codepackage.MovingCodePackage; /** * Handles a metadata file upload action. */ public class UploadMetadataController extends BaseActionListener { /** * class variables ========================================================= */ public static final String SPECIFICATIONMETHOD_BROWSE = "browse"; public static final String SPECIFICATIONMETHOD_EXPLICIT = "explicit"; /** * instance variables ====================================================== */ private String explicitPath = ""; private UploadOptions options; private SelectablePublishers selectablePublishers; /** * constructors ============================================================ */ /** Default constructor. */ public UploadMetadataController() { selectablePublishers = new SelectablePublishers(); } /** * properties ============================================================== */ /** * Gets the flag indicating if the user can publish on behalf of another. * * @return truw id proxy publishing is enabled */ public boolean getCanPublishAsProxy() { return true; } /** * Gets the explicit path of the file to upload. <br/> * The explicit path can be a URL or a path recognized on the server. * * @return the explicit path */ public String getExplicitPath() { return this.explicitPath; } /** * Sets the explicit path of the file to upload. <br/> * The explicit path can be a URL or a path recognized on the server. * * @param path the explicit path */ public void setExplicitPath(String path) { this.explicitPath = Val.chkStr(path); } /** * Gets the upload options. * * @return the upload options */ public UploadOptions getOptions() { return this.options; } /** * Sets the upload options. * * @param options the upload options */ public void setOptions(UploadOptions options) { this.options = options; } /** * Gets list of selectable publishers. * * @return the list of selectable publishers */ public SelectablePublishers getSelectablePublishers() { return selectablePublishers; } /** * Gets the file specification method. * * @return the file specification method */ public String getSpecificationMethod() { return this.getOptions().getSpecificationMethod(); } /** * Sets the file specification method. * * @param method the file specification method */ public void setSpecificationMethod(String method) { this.getOptions().setSpecificationMethod(method); } /** * Gets the style attribute for "browse" specification panel. * * @return the style (display none or block) */ public String getStyleForBrowseMethod() { if (getSpecificationMethod().equals(UploadMetadataController.SPECIFICATIONMETHOD_BROWSE)) { return "display: block;"; } else { return "display: none;"; } } /** * Gets the style attribute for "explicit" specification panel. * * @return the style (display none or block) */ public String getStyleForExplicitMethod() { if (getSpecificationMethod().equals(UploadMetadataController.SPECIFICATIONMETHOD_EXPLICIT)) { return "display: block;"; } else { return "display: none;"; } } /** * methods ================================================================= */ /** * Adds a summary message and a list of errors for each processed record * that failed. * * @param context the processing context * @param msgBroker the message broker * @param resourceKey the resource key associated with the status type * @param statusType the status type * @param count the count associated with the status type */ private void addErrorMessages(ProcessingContext context, MessageBroker msgBroker, String resourceKey, ProcessedRecord.StatusType statusType, int count) { Object[] parameters = new Integer[] { count }; String msg = msgBroker.retrieveMessage(resourceKey); msg = MessageFormat.format(msg, parameters); FacesMessage fm = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, null); msgBroker.addMessage(fm); for (ProcessedRecord processedRecord : context.getProcessedRecords()) { if (processedRecord.getStatusType().equals(statusType) && (processedRecord.getExceptions() != null)) { StringBuilder sb = new StringBuilder(); sb.append(processedRecord.getSourceUri()); if (processedRecord.getExceptions() != null) { for (String error : processedRecord.getExceptions()) { sb.append("<br />").append(error); } } fm = new FacesMessage(FacesMessage.SEVERITY_ERROR, sb.toString(), null); msgBroker.addMessage(fm); } } } /** * Adds a summary message and a list processed records for a processd status * type. * * @param context the processing context * @param msgBroker the message broker * @param resourceKey the resource key associated with the status type * @param statusType the status type * @param count the count associated with the status type */ private void addSummaryMessage(ProcessingContext context, MessageBroker msgBroker, String resourceKey, ProcessedRecord.StatusType statusType, int count) { Object[] parameters = new Integer[] { count }; String msg = msgBroker.retrieveMessage(resourceKey); msg = MessageFormat.format(msg, parameters); StringBuilder sb = new StringBuilder(msg); for (ProcessedRecord processedRecord : context.getProcessedRecords()) { if (processedRecord.getStatusType().equals(statusType)) { sb.append("<br />").append(processedRecord.getSourceUri()); } } if (sb.length() > 0) { FacesMessage fm = new FacesMessage(FacesMessage.SEVERITY_INFO, sb.toString(), null); msgBroker.addMessage(fm); } } /** * Extracts the file item placed in the HTTP servlet request by the * MultipartFilter. * * @return the uploaded file item (null if none) */ private FileItem extractFileItem() { FileItem item = null; HttpServletRequest httpReq = getContextBroker().extractHttpServletRequest(); if (httpReq != null) { Object oFile = httpReq.getAttribute("upload:uploadXml"); if ((oFile != null) && (oFile instanceof FileItem)) { item = (FileItem) oFile; } } return item; } /** * Extracts the XML string associated with an uploaded multipath file item. * * @return the XML string (null if none) * @throws UnsupportedEncodingException * (should never be thrown) */ private String extractItemXml(FileItem item) throws UnsupportedEncodingException { String xml = null; if (item != null) { xml = Val.chkStr(Val.removeBOM(item.getString("UTF-8"))); } return xml; } /** * Fired when the getPrepareView() property is accessed. <br/> * This event is triggered from the page during the render response phase of * the JSF cycle. * * @param context * the context associated with the active request * @throws Exception * if an exception occurs */ @Override protected void onPrepareView(RequestContext context) throws Exception { getSelectablePublishers().build(context, false); } /** * Handles a metadata file upload action. <br/> * This is the default entry point for a sub-class of BaseActionListener. <br/> * This BaseActionListener handles the JSF processAction method and invokes * the processSubAction method of the sub-class. * * @param event the associated JSF action event * @param context the context associated with the active request * @throws AbortProcessingException if processing should be aborted * @throws Exception if an exception occurs */ @Override protected void processSubAction(ActionEvent event, RequestContext context) throws AbortProcessingException, Exception { // initialize MessageBroker msgBroker = extractMessageBroker(); String sFileName = ""; String sXml = ""; UIComponent component = event.getComponent(); String sCommand = Val.chkStr((String) component.getAttributes().get("command")); boolean bValidateOnly = sCommand.equalsIgnoreCase("validate"); boolean bIsBrowse = this.getSpecificationMethod().equals(UploadMetadataController.SPECIFICATIONMETHOD_BROWSE); String sExplicitPath = this.getExplicitPath(); try { // upload a single file from disk if (bIsBrowse) { FileItem item = extractFileItem(); /* mcp Catch */ // check file type (zip) if (McpPublish.validateFileType(item)) { // convert to mcp File uploadedFile = new File(item.getName()); // TODO:temp folder needed - its getting moved if valid, but if not we collect trash item.write(uploadedFile); MovingCodePackage mcp = new MovingCodePackage(uploadedFile); // validate only if (bValidateOnly) { // Get PackageDescription XML as String and use ESRI Geoportal validation sXml = mcp.getDescriptionString(); ValidationRequest request = new ValidationRequest(context, sFileName, sXml); // in case of schema problems verify method would throw exception request.verify(); msgBroker.addSuccessMessage("catalog.publication.success.validated"); } // upload else { // Get PackageDescription XML as String and use ESRI Geoportal validation sXml = mcp.getDescriptionString(); ValidationRequest request = new ValidationRequest(context, sFileName, sXml); // in case of schema problems verify method would throw exception request.verify(); msgBroker.addSuccessMessage("catalog.publication.success.validated"); // save file with hashKey //RandomHashString hashFactory = new RandomHashString(); // changed upload folder to absolute String path = Constant.UPLOAD_FOLDER, outLoc =UuidUtil.makeUuid(true); // regenerate until we find a non-existing folder name while (new File(path + File.separator + outLoc).exists()) { outLoc =UuidUtil.makeUuid(true); } outLoc = UuidUtil.removeCurlies(outLoc); // create folder new File(path + File.separator + outLoc).mkdirs(); // move file String newPath = path + File.separator + outLoc + File.separator + Constant.XML_OUTPUT_ZIP_NAME; uploadedFile.renameTo(new File(newPath)); // TODO: extract xml out of zip package ? // save db information McpPublish mcpp = new McpPublish(mcp, newPath, outLoc, context); if (mcpp.publish(false)) { msgBroker.addSuccessMessage("publication.success.created"); } else { msgBroker.addErrorMessage("catalog.publication.uploadMetadata.summary.invalid"); System.out.println("UploadMetadataController processSubAction: not a valid mcp package"); } } return; // don't run xml upload code if its a mcp } else { System.out.println("UploadMetadataController processSubAction: not a mcp package"); } // normal xml catch if (item != null) { sFileName = Val.chkStr(item.getName()); if (sFileName.length() > 0) { File file = new File(sFileName); sFileName = file.getName(); } sXml = extractItemXml(item); } if (sFileName.length() > 0) { FacesMessage fm = new FacesMessage( FacesMessage.SEVERITY_WARN, sFileName, null); msgBroker.addMessage(fm); } if (sFileName.length() == 0) { msgBroker.addErrorMessage("publication.uploadMetadata.err.file.required"); } else if (sXml.length() == 0) { msgBroker.addErrorMessage("publication.uploadMetadata.err.file.empty"); } else if (bValidateOnly) { ValidationRequest request = new ValidationRequest(context, sFileName, sXml); request.verify(); msgBroker.addSuccessMessage("catalog.publication.success.validated"); } else { Publisher publisher = getSelectablePublishers().selectedAsPublisher(context, false); UploadRequest request = new UploadRequest(context, publisher, sFileName, sXml); request.publish(); if (request.getPublicationRecord().getWasDocumentUnchanged()) { msgBroker.addSuccessMessage("publication.success.unchanged"); } else if (request.getPublicationRecord().getWasDocumentReplaced()) { msgBroker.addSuccessMessage("publication.success.replaced"); } else { msgBroker.addSuccessMessage("publication.success.created"); } } // handle an empty explicit url or network path } else if (sExplicitPath.length() == 0) { msgBroker.addErrorMessage("publication.uploadMetadata.err.file.required"); // process an explicit url or network path } else { FacesMessage fm = new FacesMessage(FacesMessage.SEVERITY_WARN, sExplicitPath, null); msgBroker.addMessage(fm); sFileName = sExplicitPath; Publisher publisher = getSelectablePublishers().selectedAsPublisher(context, false); HttpClientRequest httpClient = HttpClientRequest.newRequest(); ProcessingContext pContext = new ProcessingContext(context, publisher, httpClient, null, bValidateOnly); pContext.setMessageBroker(msgBroker); ProcessorFactory factory = new ProcessorFactory(); ResourceProcessor processor = factory.interrogate(pContext, sExplicitPath); if (processor == null) { throw new IOException("Unable to process resource."); } processor.process(); boolean wasSingleSource = pContext.getWasSingleSource(); // summary messages if (bValidateOnly) { if (wasSingleSource && (pContext.getNumberValidated() == 1)) { msgBroker.addSuccessMessage("catalog.publication.success.validated"); } else if (pContext.getNumberValidated() > 0) { addSummaryMessage(pContext, msgBroker, "catalog.publication.uploadMetadata.summary.valid", ProcessedRecord.StatusType.VALIDATED, pContext.getNumberValidated()); } if (wasSingleSource && (pContext.getNumberFailed() == 1)) { Exception lastException = pContext.getLastException(); if (pContext.getLastException() != null) { throw lastException; } else { // TODO message here ?? } } else if (pContext.getNumberFailed() > 0) { addErrorMessages(pContext, msgBroker, "catalog.publication.uploadMetadata.summary.invalid", ProcessedRecord.StatusType.FAILED, pContext.getNumberFailed()); } if ((pContext.getNumberValidated() == 0) && (pContext.getNumberFailed() == 0)) { msgBroker.addErrorMessage( "catalog.publication.uploadMetadata.summary.valid", new Integer[] { 0 }); } // publication related messages } else { if (wasSingleSource && (pContext.getNumberCreated() == 1)) { msgBroker.addSuccessMessage("publication.success.created"); } else if (pContext.getNumberCreated() > 0) { addSummaryMessage(pContext, msgBroker, "catalog.publication.uploadMetadata.summary.created", ProcessedRecord.StatusType.CREATED, pContext.getNumberCreated()); } if (wasSingleSource && (pContext.getNumberReplaced() == 1)) { msgBroker.addSuccessMessage("publication.success.replaced"); } else if (pContext.getNumberReplaced() > 0) { addSummaryMessage(pContext,msgBroker, "catalog.publication.uploadMetadata.summary.replaced", ProcessedRecord.StatusType.REPLACED, pContext.getNumberReplaced()); } if (wasSingleSource && (pContext.getNumberUnchanged() == 1)) { msgBroker .addSuccessMessage("publication.success.unchanged"); } else if (pContext.getNumberUnchanged() > 0) { addSummaryMessage(pContext, msgBroker, "catalog.publication.uploadMetadata.summary.unchanged", ProcessedRecord.StatusType.UNCHNAGED, pContext.getNumberUnchanged()); } if (pContext.getNumberDeleted() > 0) { addSummaryMessage(pContext, msgBroker, "catalog.publication.uploadMetadata.summary.deleted", ProcessedRecord.StatusType.DELETED, pContext.getNumberDeleted()); } if (wasSingleSource && (pContext.getNumberFailed() == 1)) { Exception lastException = pContext.getLastException(); if (pContext.getLastException() != null) { throw lastException; } else { // TODO message here ?? } } else if (pContext.getNumberFailed() > 0) { addErrorMessages(pContext, msgBroker, "catalog.publication.uploadMetadata.summary.failed", ProcessedRecord.StatusType.FAILED, pContext.getNumberFailed()); } } } // handle a validation exception } catch (ValidationException e) { String sKey = e.getKey(); if (sKey.length() > 0) { String sMsg = sKey; Schema schema = context.getCatalogConfiguration().getConfiguredSchemas().get(sKey); if (schema != null) { if (schema.getLabel() != null) { String sResKey = schema.getLabel().getResourceKey(); if (sResKey.length() > 0) { sMsg = extractMessageBroker().retrieveMessage(sResKey) + " (" + sKey + ")"; } } } FacesMessage fm = new FacesMessage(FacesMessage.SEVERITY_WARN, " - " + sMsg, null); extractMessageBroker().addMessage(fm); } e.getValidationErrors().buildMessages(msgBroker, true); // handle remaining exceptions } catch (Exception e) { // there seems to be no good exception related to a file that is // simply // not an XML file, a message containing // "content is not allowed in prolog" // seems to be the best guess at the moment String sMsg = e.toString().toLowerCase(); if (sMsg.indexOf("content is not allowed in prolog") != -1) { msgBroker.addErrorMessage("publication.uploadMetadata.err.file.prolog"); } else { throw e; } } } /** * inner classes =========================================================== */ /** * Stores session based upload options for the controller. */ public static class UploadOptions { private String specificationMethod = UploadMetadataController.SPECIFICATIONMETHOD_BROWSE; /** * Gets the file specification method. * * @return the file specification method */ public String getSpecificationMethod() { return this.specificationMethod; } /** * Sets the file specification method. * * @param method * the file specification method */ public void setSpecificationMethod(String method) { this.specificationMethod = Val.chkStr(method); if (!this.specificationMethod .equals(UploadMetadataController.SPECIFICATIONMETHOD_EXPLICIT)) { this.specificationMethod = UploadMetadataController.SPECIFICATIONMETHOD_BROWSE; } } } }