Java tutorial
/* * Source code in 3rd-party is licensed and owned by their respective * copyright holders. * * All other source code is copyright Tresys Technology and licensed as below. * * Copyright (c) 2012 Tresys Technology LLC, Columbia, Maryland, USA * * This software was developed by Tresys Technology LLC * with U.S. Government sponsorship. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tresys.jalop.utils.jnltest; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.text.DecimalFormat; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import javax.xml.soap.MimeHeaders; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import com.google.common.io.PatternFilenameFilter; import com.tresys.jalop.jnl.DigestPair; import com.tresys.jalop.jnl.Publisher; import com.tresys.jalop.jnl.PublisherSession; import com.tresys.jalop.jnl.RecordType; import com.tresys.jalop.jnl.SourceRecord; public class PublisherImpl implements Publisher { /** A logger for this class. */ private static final Logger LOGGER = Logger.getLogger(PublisherImpl.class); /** The filename for the system meta-data document. */ private static final String SYS_META_FILENAME = "sys_metadata.xml"; /** The filename for the application meta-data document. */ private static final String APP_META_FILENAME = "app_metadata.xml"; /** The filename for the payload. */ private static final String PAYLOAD_FILENAME = "payload"; /** Filename where status information is written to. */ private static final String STATUS_FILENAME = "status.js"; /** The format string for output files. */ private static final String SID_FORMAT_STRING = "0000000000"; /** Formatter used to generate the sub-directories for each record. */ static final DecimalFormat SID_FORMATER = new DecimalFormat(SID_FORMAT_STRING); /** Key in the status file to indicate if a 'sync' message was sent. */ private static final String SYNCED = "synced"; /** Key in the status file for the local digest. */ private static final String LOCALDGST = "local_digest"; /** Key in the status file for the peer digest. */ private static final String PEERDGST = "peer_digest"; /** * Regular expression used for filtering directories, i.e. only directories * which have exactly ten digits as a filename. */ private static final String SID_REGEX = "^\\d{10}$"; /** * Filter used for searching an existing file system tree for previously * downloaded records. */ static final FilenameFilter FILENAME_FILTER = new PatternFilenameFilter(SID_REGEX); /** * FileFilter to get all sub-directories that match the serial ID * pattern. */ private static final FileFilter FILE_FILTER = new FileFilter() { @Override public boolean accept(final File pathname) { if (pathname.isDirectory()) { return FILENAME_FILTER.accept(pathname.getParentFile(), pathname.getName()); } return false; } }; /** * Root of the input directories. Each record has its own * sub-directory. */ final File inputRoot; /** The type of records to transfer. */ private final RecordType recordType; public PublisherImpl(final File inputRoot, final RecordType recordType) { this.recordType = recordType; final String type; switch (recordType) { case Audit: type = "audit"; break; case Journal: type = "journal"; break; case Log: type = "log"; break; default: throw new IllegalArgumentException("illegal record type"); } this.inputRoot = new File(inputRoot, type); if (!(this.inputRoot.exists() && this.inputRoot.isDirectory())) { throw new RuntimeException("Subdirs don't exist for " + type); } } @Override public SourceRecord getNextRecord(final PublisherSession sess, final String lastSerialId) { final long nextSerialId; try { if (Long.valueOf(lastSerialId) == 0) { if (LOGGER.isInfoEnabled()) { LOGGER.info("SerialId of 0 sent. Finding the oldest file."); } final File[] recordDirs = this.inputRoot.listFiles(PublisherImpl.FILE_FILTER); Arrays.sort(recordDirs); final File firstFile = recordDirs[0]; final String fileName = firstFile.getName(); nextSerialId = Long.valueOf(fileName); if (nextSerialId == 0) { if (LOGGER.isEnabledFor(Level.ERROR)) { LOGGER.error("Found a record with the invalid serialId of 0."); } throw new RuntimeException("SerialId of 0 is reserved and cannot be associated with a record."); } if (LOGGER.isInfoEnabled()) { LOGGER.info("Oldest file found with serialId: " + nextSerialId); } } else { nextSerialId = Long.valueOf(lastSerialId) + 1; } final File serialDir = new File(this.inputRoot, SID_FORMATER.format(Long.valueOf(nextSerialId))); if (!serialDir.exists()) { if (LOGGER.isInfoEnabled()) { LOGGER.info("Directory structure for serialId: " + nextSerialId + " does not exist. Returning null."); } return null; } return new SourceRecordImpl(Long.toString(nextSerialId), 0); } catch (final NumberFormatException e) { if (LOGGER.isEnabledFor(Level.ERROR)) { LOGGER.error("SerialId is not numeric - returning null"); } return null; } } @Override public SourceRecord onJournalResume(final PublisherSession sess, final String serialId, final long offset, final MimeHeaders headers) { return new SourceRecordImpl(serialId, offset); } @Override public boolean onSubscribe(final PublisherSession sess, final String serialId, final MimeHeaders headers) { try { final long serial = Long.parseLong(serialId); if (serial < 0) { if (LOGGER.isEnabledFor(Level.ERROR)) { LOGGER.error("serialId must be a postive number"); } return false; } } catch (final NumberFormatException nfe) { if (LOGGER.isEnabledFor(Level.ERROR)) { LOGGER.error("serialId sent is not numeric - " + serialId); } return false; } return true; } @Override public boolean onRecordComplete(final PublisherSession sess, final String serailId, final SourceRecord record) { final Map<String, String> complete = new HashMap<String, String>(); complete.put("recordComplete", "true"); return dumpStatus(record.getSerialId(), complete); } @Override public boolean sync(final PublisherSession sess, final String serialId, final MimeHeaders headers) { final Map<String, String> synched = new HashMap<String, String>(); synched.put(SYNCED, "true"); return dumpStatus(serialId, synched); } @Override public void notifyDigest(final PublisherSession sess, final String serialId, final byte[] digest) { final String hexString = (new BigInteger(1, digest)).toString(16); if (LOGGER.isInfoEnabled()) { LOGGER.info("Calculated digest for " + serialId + ": " + hexString); } final Map<String, String> map = new HashMap<String, String>(); map.put(LOCALDGST, hexString); dumpStatus(serialId, map); } @Override public void notifyPeerDigest(final PublisherSession sess, final Map<String, DigestPair> digestPairs) { for (final String key : digestPairs.keySet()) { final DigestPair pair = digestPairs.get(key); if (LOGGER.isInfoEnabled()) { LOGGER.info("Digest status for " + pair.getSerialId() + ": " + pair.getDigestStatus()); } final String hexString = (new BigInteger(1, pair.getPeerDigest())).toString(16); final Map<String, String> map = new HashMap<String, String>(); map.put(PEERDGST, hexString); dumpStatus(pair.getSerialId(), map); } } /** * Write status information about a record out to disk. * @param lri The {@link LocalRecordInfo} object output stats for. * @return <code>true</code> If the data was successfully written out. * <code>false</code> otherwise. */ @SuppressWarnings("unchecked") final boolean dumpStatus(final String serialId, final Map<String, String> statusMap) { final JSONParser p = new JSONParser(); JSONObject status; File statusFile; BufferedOutputStream w; final File serialDir = new File(this.inputRoot, SID_FORMATER.format(Long.valueOf(serialId))); try { statusFile = new File(serialDir, STATUS_FILENAME); if (!statusFile.exists()) { if (LOGGER.isInfoEnabled()) { LOGGER.info("The '" + STATUS_FILENAME + "' file does not exist so creating it."); } statusFile.createNewFile(); status = new JSONObject(); } else { status = (JSONObject) p.parse(new FileReader(statusFile)); } status.putAll(statusMap); w = new BufferedOutputStream(new FileOutputStream(statusFile)); w.write(status.toJSONString().getBytes("utf-8")); w.close(); } catch (final FileNotFoundException e) { LOGGER.error("Failed to open status file for writing:" + e.getMessage()); return false; } catch (final ParseException e) { if (LOGGER.isInfoEnabled()) { LOGGER.info("'" + STATUS_FILENAME + "' file"); } status = new JSONObject(); } catch (final UnsupportedEncodingException e) { LOGGER.error("cannot find UTF-8 encoder?"); return false; } catch (final IOException e) { LOGGER.error("failed to write to the status file, aborting"); return false; } return true; } private class SourceRecordImpl implements SourceRecord { private final String serialId; private final long offset; final File serialDir; final File sysFile; final File appFile; final File payloadFile; @SuppressWarnings("unchecked") public SourceRecordImpl(final String serialId, final long offset) { this.serialId = serialId; this.offset = offset; this.serialDir = new File(PublisherImpl.this.inputRoot, PublisherImpl.SID_FORMATER.format(Long.valueOf(serialId))); if (LOGGER.isInfoEnabled()) { LOGGER.info("Getting files for sysMetadata, appMetadata, and payload."); } this.sysFile = new File(this.serialDir, SYS_META_FILENAME); if (!this.sysFile.exists()) { throw new RuntimeException("SysMetadata file doesn't exist"); } this.appFile = new File(this.serialDir, APP_META_FILENAME); this.payloadFile = new File(this.serialDir, PAYLOAD_FILENAME); if (offset > 0 && PublisherImpl.this.recordType == RecordType.Journal && !this.payloadFile.exists()) { throw new RuntimeException("Payload file doesn't exist"); } } @Override public String getSerialId() { return this.serialId; } @Override public RecordType getRecordType() { return recordType; } @Override public long getSysMetaLength() { if (this.sysFile == null) { return 0; } return this.sysFile.length(); } @Override public long getAppMetaLength() { if (this.appFile == null) { return 0; } return this.appFile.length(); } @Override public long getPayloadLength() { if (this.payloadFile == null) { return 0; } return this.payloadFile.length(); } @Override public InputStream getSysMetadata() { try { return new FileInputStream(this.sysFile); } catch (final FileNotFoundException e) { if (LOGGER.isEnabledFor(Level.ERROR)) { LOGGER.error("No SysMetadata file has been set."); } //Throw an error since sysmetadata file is required throw new RuntimeException("SysMetadata file does not exist."); } } @Override public InputStream getAppMetadata() { try { return new FileInputStream(this.appFile); } catch (final FileNotFoundException e) { if (LOGGER.isInfoEnabled()) { LOGGER.info("No AppMetadata file has been set."); } return null; } } @Override public InputStream getPayload() { try { final InputStream is = new FileInputStream(this.payloadFile); return is; } catch (final FileNotFoundException e) { if (LOGGER.isInfoEnabled()) { LOGGER.info("No payload file has been set."); } return null; } } } }