/*
###############################################################################
# #
# Copyright (C) 2011-2016 OpenMEAP, Inc. #
# Credits to Jonathan Schang & Rob Thacher #
# #
# Released under the LGPLv3 #
# #
# OpenMEAP is free software: you can redistribute it and/or modify #
# it under the terms of the GNU Lesser General Public License as published #
# by the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# OpenMEAP is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public License #
# along with OpenMEAP. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
*/
package com.openmeap.model.dto;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.Map;
import java.util.HashMap;
import javax.persistence.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.openmeap.constants.FormConstants;
import com.openmeap.constants.ServletNameConstants;
import com.openmeap.constants.UrlParamConstants;
import com.openmeap.digest.DigestException;
import com.openmeap.model.event.AbstractModelEntity;
import com.openmeap.util.AuthTokenProvider;
import com.openmeap.util.GenericRuntimeException;
import com.openmeap.web.form.Parameter;
@Entity @Table(name="application_archive",uniqueConstraints=@UniqueConstraint(columnNames={"application_id","hash","hash_algorithm"}))
public class ApplicationArchive extends AbstractModelEntity {
private Logger logger = LoggerFactory.getLogger(ApplicationArchive.class);
private Long id;
private String fileDataUrl;
private String hash;
private String hashAlgorithm;
private Application application;
private Integer bytesLength;
private Integer bytesLengthUncompressed;
private String newFileUploaded;
final static public String HASH_BASED_URL_TEMPLATE = "${globalSettings.externalServiceUrlPrefix}/"+ServletNameConstants.APPLICATION_MANAGEMENT
+"/?"+UrlParamConstants.ACTION+"=archiveDownload"
+"&"+UrlParamConstants.AUTH_TOKEN+"=${newAuthToken}"
+"&"+UrlParamConstants.APPARCH_HASH+"=${hash}"
+"&"+UrlParamConstants.APPARCH_HASH_ALG+"=${hashAlgorithm}";
final static public String URL_TEMPLATE = HASH_BASED_URL_TEMPLATE;
@Id @GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Transient public Long getPk() { return getId(); }
public void setPk( Object pkValue ) { setId((Long)pkValue); }
@Transient public File getFile(String pathPrefix) {
return getFile(pathPrefix,getHashAlgorithm(),getHash());
}
@Transient static public File getFile(String pathPrefix, String hashAlg, String hash) {
if( pathPrefix!=null && hashAlg!=null && hash!=null ) {
return new File(pathPrefix, hash+".zip");
} else return null;
}
@Transient public File getExplodedPath(String pathPrefix) {
if( pathPrefix!=null && getHash()!=null && getHashAlgorithm()!=null ) {
return new File(pathPrefix, getHash());
} else return null;
}
@Transient public String getDownloadUrl(GlobalSettings settings) {
return substituteArchiveVariables(settings,getUrl());
}
@Transient
public String getNewFileUploaded() {
return newFileUploaded;
}
public void setNewFileUploaded(String newFileUploaded) {
this.newFileUploaded = newFileUploaded;
}
/**
* Creates a url for downloading the zip file directly.
*
* Introduced so that the specific archive of a deployment
* can be retrieved, freeing up the version archive to be modified.
*
* That a version archive may be modified is intended to support development,
* more than anything else.
*
* @param settings
*
* @return If the url matches the URL_TEMPLATE, then a HASH_BASED_URL_TEMPLATE run through the substitution method. Else the url.
*/
@Transient public String getDirectDownloadUrl(GlobalSettings settings) {
if( URL_TEMPLATE.equals(this.getUrl()) ) {
return substituteArchiveVariables(settings,HASH_BASED_URL_TEMPLATE);
}
return this.getUrl();
}
@Transient public String getViewUrl(GlobalSettings settings) {
return substituteArchiveVariables(settings,"/openmeap-admin-web/web-view/${appName}/${hash}/${newAuthToken}/index.html");
}
@Transient private String substituteArchiveVariables(GlobalSettings settings,String url) {
String externalServiceUrlPrefix = settings.getExternalServiceUrlPrefix();
String authSalt = this.getApplication().getProxyAuthSalt();
String newAuthToken;
try {
newAuthToken = AuthTokenProvider.newAuthToken(authSalt!=null?authSalt:"");
} catch (DigestException e) {
throw new GenericRuntimeException(e);
}
String replUrl = url;
try {
// TODO: replace these with constants
replUrl = replUrl.replace("${globalSettings.externalServiceUrlPrefix}", externalServiceUrlPrefix!=null?externalServiceUrlPrefix:"");
replUrl = replUrl.replace("${appName}", URLEncoder.encode(getApplication().getName(),FormConstants.CHAR_ENC_DEFAULT));
replUrl = replUrl.replace("${newAuthToken}", URLEncoder.encode(newAuthToken,FormConstants.CHAR_ENC_DEFAULT));
replUrl = replUrl.replace("${hash}", URLEncoder.encode(hash,FormConstants.CHAR_ENC_DEFAULT));
replUrl = replUrl.replace("${hashAlgorithm}", URLEncoder.encode(hashAlgorithm,FormConstants.CHAR_ENC_DEFAULT));
} catch(UnsupportedEncodingException uee) {
logger.error("Exception thrown encoding url parameters for url: {}",uee);
}
return replUrl;
}
/**
* @return Either an http, https, or app schemed url to the actual version archive.
*/
@Column(name="url")
@Parameter("url")
public String getUrl() {
/*
* Justification:
* This could have been a LOB, but:
* - the device will be able to download the archive from anywhere
* - it's simpler to pull a regular http url than to facilitate this via SOAP
* - storing this as a reference reduces load on the database
*/
return fileDataUrl;
}
public void setUrl(String url) {
this.fileDataUrl = url;
}
/**
* @return A hash the artifact residing at the url can be verified with.
*/
@Column(name="hash")
@Parameter("hash")
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
/**
* @return The algorithm ran against the archive to generate the hash.
*/
@Column(name="hash_algorithm")
@Parameter("hashType")
public String getHashAlgorithm() {
return hashAlgorithm;
}
public void setHashAlgorithm(String hashAlgorithm) {
this.hashAlgorithm = hashAlgorithm;
}
/**
* @return The version of the application the archive is associated to.
*/
@ManyToOne(fetch=FetchType.EAGER,cascade={},targetEntity=Application.class)
@JoinColumn(name="application_id")
public Application getApplication() {
return application;
}
public void setApplication(Application application) {
this.application = application;
}
@Column(name="bytes_length")
@Parameter("bytesLength")
public Integer getBytesLength() {
return bytesLength;
}
public void setBytesLength(Integer bytesLength) {
this.bytesLength=bytesLength;
}
@Column(name="bytes_length_uncompressed")
@Parameter("bytesLengthUncompressed")
public Integer getBytesLengthUncompressed() {
return bytesLengthUncompressed;
}
public void setBytesLengthUncompressed(Integer bytesLength) {
bytesLengthUncompressed = bytesLength;
}
public ApplicationArchive clone() {
ApplicationArchive clone = new ApplicationArchive();
clone.setApplication(application);
clone.setBytesLength(bytesLength);
clone.setBytesLengthUncompressed(bytesLengthUncompressed);
clone.setHash(hash);
clone.setHashAlgorithm(hashAlgorithm);
clone.setNewFileUploaded(newFileUploaded);
clone.setUrl(fileDataUrl);
clone.setId(id);
return clone;
}
public Map<Method,String> validate() {
try {
Map<Method,String> errors = new HashMap<Method,String>();
if( this.getHash()==null ) {
errors.put( this.getClass().getMethod("getHash"), "The archive must have a hash");
}
if( this.getHashAlgorithm()==null ) {
errors.put( this.getClass().getMethod("getHashAlgorithm"), "The archive must have a valid hash algorithm. The valid hash algorithms are enumerated in the openmeap protocol xsd.");
} else {
try {
com.openmeap.protocol.dto.HashAlgorithm.fromValue(this.getHashAlgorithm());
} catch( IllegalArgumentException iae ) {
errors.put( this.getClass().getMethod("getHashAlgorithm"), "The archive must have a valid hash algorithm. The valid hash algorithms are enumerated in the openmeap protocol xsd.");
}
}
if( this.getUrl()==null ) {
errors.put( this.getClass().getMethod("getUrl"), "The archive must have a url.");
}
if( this.getBytesLength()==null || this.getBytesLength()<1 ) {
errors.put( this.getClass().getMethod("getBytesLength"), "The archive must be a number of bytes greater than 1.");
}
if( this.getBytesLengthUncompressed()==null || this.getBytesLengthUncompressed()<1 ) {
errors.put( this.getClass().getMethod("getBytesLengthUncompressed"), "The uncompressed archive must be a number of bytes greater than 1.");
}
if( errors.size()>0 ) {
return errors;
}
return null;
} catch( NoSuchMethodException nsme ) {
throw new RuntimeException(nsme);
}
}
}