Implementation Guidelines
Refer to the following implementation of filter to customize the content download in your Windchill system:
* 
The following implementation is provided only for reference to assist in creating the implementation for filter.All customizations should follow recommended coding best practices and other customer specific considerations.
package wt.content.filter;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* Sample content encryption filter that can be configured to encrypt content upon download.
* If the HTTP request is for content download, the original content file is held in GenericResponseWrapper's data field.
* Original file is encrypted and written into actual ServletResponse.
*/
public class ContentEncryptionFilter implements Filter {

private static final Logger logger = LogManager.getLogger(ContentEncryptionFilter.class.getName());

@Override
public void destroy() {
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
GenericResponseWrapper wrapper = new
GenericResponseWrapper((HttpServletResponse) servletResponse);
filterChain.doFilter(servletRequest, wrapper);
if(logger.isDebugEnabled()) {
logger.debug("ContentEncryptionFilter.doFilter: isDownloadRequest = "+wrapper.isDownloadRequest());
}
if(!wrapper.isDownloadRequest()) {
return;
}
OutputStream os = servletResponse.getOutputStream();
InputStream inputStream = wrapper.getData();
try {
byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
IvParameterSpec ivspec = new IvParameterSpec(iv);
String SECRET_KEY = "my_super_secret_key_ho_ho_ho";
String SALT = "ssshhhhhhhhhhh!!!!";
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(SECRET_KEY.toCharArray(), SALT.getBytes(), 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
byte[] inputBytes = new byte[8192];
for (int n = inputStream.read(inputBytes); n > 0; n = inputStream.read(inputBytes)) {
byte[] outputBytes = cipher.update(inputBytes, 0, n);
os.write(outputBytes);
}
byte[] outputBytes = cipher.doFinal();
os.write(outputBytes);
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
finally {
inputStream.close();
File tempfile = wrapper.getTempFile();
if(tempfile != null) {
tempfile.delete();
}
}
}

@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
package wt.content.filter;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Wrapper class for HttpServletResponseWrapper.
* The class determines if the response is for content file download.
*/
public class GenericResponseWrapper extends HttpServletResponseWrapper {
private int contentLength;
private String contentType;
private boolean downloadRequest = false;
private static final Logger logger = LogManager.getLogger(GenericResponseWrapper.class.getName());
private FilterServletOutputStream fsos = null;

public GenericResponseWrapper(HttpServletResponse response) {
super(response);
}

/**
* returns original content file.
* @return
*/
public InputStream getData() {
return fsos.getInputStream();
}

public File getTempFile() {
return fsos.getTempFile();
}

public ServletOutputStream getOutputStream() throws IOException {
if(downloadRequest) {
if(fsos == null) {
fsos = new FilterServletOutputStream();
}
return fsos;
}
return super.getOutputStream();
}

public PrintWriter getWriter() throws IOException {
if(downloadRequest) {
return new PrintWriter(getOutputStream(),true);
}
return super.getWriter();
}

public void setContentLength(int length) {
this.contentLength = length;
super.setContentLength(length);
}

public int getContentLength() {
return contentLength;
}

public void setContentType(String type) {
this.contentType = type;
super.setContentType(type);
}

public String getContentType() {
return contentType;
}

/**
* This method determines if the HTTP request is for content file download.
* Customize it as per requirements. For example you can look for specific content-types like
* application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint etc.
*/
public void setHeader(String name, String value) {
//if content-disposition header set, its a content file download request
if(name.equalsIgnoreCase("content-disposition")) {
if(contentType != null &&
(contentType.equalsIgnoreCase("application/json")
|| contentType.equalsIgnoreCase("application/json; charset=utf-8")
|| contentType.equalsIgnoreCase("application/ld+json")
|| contentType.equalsIgnoreCase("application/wcdti")
|| contentType.equalsIgnoreCase("text/html"))) {
downloadRequest = false;
logger.debug("GenericResponseWrapper.setHeader: content-disposition, contentType="+contentType+". downloadRequest = "+downloadRequest);
}
else {
downloadRequest = true;
logger.debug("GenericResponseWrapper.setHeader: content-disposition. downloadRequest = "+downloadRequest);
}
}
else if(name.equalsIgnoreCase("content-type")) {
value = value.trim();
contentType = value;
//if content-type value is one of OOTB configured mime types, its a content file download request
if(value.equalsIgnoreCase("application/cals-1840")
|| value.equalsIgnoreCase("application/ed")
|| value.equalsIgnoreCase("application/edz")
|| value.equalsIgnoreCase("application/msaccess")
|| value.equalsIgnoreCase("application/msonenote")
|| value.equalsIgnoreCase("application/msoutlook")
|| value.equalsIgnoreCase("application/msproject")
|| value.equalsIgnoreCase("application/msword")
|| value.equalsIgnoreCase("application/octet-stream")
|| value.equalsIgnoreCase("application/pdf")
|| value.equalsIgnoreCase("application/postscript")
|| value.equalsIgnoreCase("application/pvz")
|| value.equalsIgnoreCase("application/rtf")
|| value.equalsIgnoreCase("application/visio")
|| value.equalsIgnoreCase("application/vnd.groove-injector")
|| value.equalsIgnoreCase("application/vnd.ms-excel")
|| value.equalsIgnoreCase("application/vnd.ms-excel.addin.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-excel.sheet.binary.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-excel.sheet.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-excel.template.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-officetheme")
|| value.equalsIgnoreCase("application/vnd.ms-powerpoint")
|| value.equalsIgnoreCase("application/vnd.ms-powerpoint.addin.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-powerpoint.presentation.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-powerpoint.slide.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-powerpoint.slideshow.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-powerpoint.template.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-project")
|| value.equalsIgnoreCase("application/vnd.ms-publisher")
|| value.equalsIgnoreCase("application/vnd.ms-word.document.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.ms-word.template.macroEnabled.12")
|| value.equalsIgnoreCase("application/vnd.openxmlformats-officedocument.presentationml.presentation")
|| value.equalsIgnoreCase("application/vnd.openxmlformats-officedocument.presentationml.slide")
|| value.equalsIgnoreCase("application/vnd.openxmlformats-officedocument.presentationml.slideshow")
|| value.equalsIgnoreCase("application/vnd.openxmlformats-officedocument.presentationml.template")
|| value.equalsIgnoreCase("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|| value.equalsIgnoreCase("application/vnd.openxmlformats-officedocument.spreadsheetml.template")
|| value.equalsIgnoreCase("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
|| value.equalsIgnoreCase("application/vnd.openxmlformats-officedocument.wordprocessingml.template")
|| value.equalsIgnoreCase("application/x-camcad")
|| value.equalsIgnoreCase("application/x-chm")
|| value.equalsIgnoreCase("application/x-gzip")
|| value.equalsIgnoreCase("application/zip")
|| value.equalsIgnoreCase("audio/basic")
|| value.equalsIgnoreCase("audio/wav")
|| value.equalsIgnoreCase("image/bmp")
|| value.equalsIgnoreCase("image/cgm")
|| value.equalsIgnoreCase("image/gif")
|| value.equalsIgnoreCase("image/jpeg")
|| value.equalsIgnoreCase("image/png")
|| value.equalsIgnoreCase("image/slp")
|| value.equalsIgnoreCase("image/stl")
|| value.equalsIgnoreCase("image/tiff")
|| value.equalsIgnoreCase("image/vnd.dwg")
|| value.equalsIgnoreCase("image/vnd.dxf")
|| value.equalsIgnoreCase("model/vrml")
|| value.equalsIgnoreCase("text/plain")
|| value.equalsIgnoreCase("text/sgml")
|| value.equalsIgnoreCase("text/xml")
|| value.equalsIgnoreCase("video/mpeg")
|| value.equalsIgnoreCase("x-ptc/x-asm")
|| value.equalsIgnoreCase("x-ptc/x-dgm")
|| value.equalsIgnoreCase("x-ptc/x-drw")
|| value.equalsIgnoreCase("x-ptc/x-frm")
|| value.equalsIgnoreCase("x-ptc/x-lay")
|| value.equalsIgnoreCase("x-ptc/x-package")
|| value.equalsIgnoreCase("x-ptc/x-part")
|| value.equalsIgnoreCase("x-ptc/x-pic")
|| value.equalsIgnoreCase("x-ptc/x-rep")
|| value.equalsIgnoreCase("x-ptc/x-vpf")
|| value.equalsIgnoreCase("x-unknown/x-unknown")
|| value.equalsIgnoreCase("x-world/x-gaf")
|| value.equalsIgnoreCase("x-world/x-gbf")){
downloadRequest = true;
logger.debug("GenericResponseWrapper.setHeader: content-type="+value+". downloadRequest = "+downloadRequest);
}
else {
logger.debug("GenericResponseWrapper.setHeader: content-type="+value+" not programmed in custom Servlet Filter. downloadRequest = "+downloadRequest);
}
}
super.setHeader(name, value);
}

public boolean isDownloadRequest() {
return downloadRequest;
}
}

package wt.content.filter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import wt.util.WTProperties;
/**
* Custom OutputStream which holds the data either in memory or in temporary file.
* The data can be later retrieved using getInputStream API.
*/
public class FilterServletOutputStream extends ServletOutputStream {

private OutputStream stream;
private ByteArrayOutputStream output;
private File tempfile;
private long length = 0;
//fine tune property to avoid memory issues.
private static long MAX_FILE_SIZE = 10 * 1024 * 1024;//10MB
private static final Logger logger = LogManager.getLogger(FilterServletOutputStream.class.getName());

public FilterServletOutputStream() {
output = new ByteArrayOutputStream();
stream = new DataOutputStream(output);
}

public void write(int b) throws IOException {
stream.write(b);
length++;
switchToFile();
}

public void write(byte[] b) throws IOException {
stream.write(b);
length += b.length;
switchToFile();
}

public void write(byte[] b, int off, int len) throws IOException {
stream.write(b,off,len);
length += len;
switchToFile();
}

private void switchToFile() {
if(length > MAX_FILE_SIZE) {
if(tempfile == null) {
logger.debug("switching to temp file..");
WTProperties props;
try {
props = WTProperties.getServerProperties();
String WT_TEMP = props.getProperty("wt.temp");
Random random = new Random();
tempfile = new File(WT_TEMP + "/" + random.nextInt() + "_" + System.currentTimeMillis()) ;
FileOutputStream fos = new FileOutputStream(tempfile);
fos.write(output.toByteArray());
stream = fos;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setWriteListener(WriteListener arg0) {
}

public InputStream getInputStream() {
if(length > MAX_FILE_SIZE) {
try {
stream.flush();
stream.close();
FileInputStream fis = new FileInputStream(tempfile);
return fis;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
else {
ByteArrayInputStream inputStream = new ByteArrayInputStream(output.toByteArray());
return inputStream;
}
}

public File getTempFile() {
return tempfile;
}

}
* 
This configuration is not applicable to clients (that use a client connector cache to store content) like Creo and other CAD applications.
If the servlet filter is configured to encrypt specific MIME types, the downloaded content displayed in the browser is also encrypted and may not work as expected.
When the content file is opened in Office 365 application, it will not be encrypted.
Was this helpful?