net.sf.mpxj.reader.UniversalProjectReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mpxj Show documentation
Show all versions of mpxj Show documentation
Library that provides facilities to allow project information to be manipulated in Java and .Net. Supports a range of data formats: Microsoft Project Exchange (MPX), Microsoft Project (MPP,MPT), Microsoft Project Data Interchange (MSPDI XML), Microsoft Project Database (MPD), Planner (XML), Primavera (PM XML, XER, and database), Asta Powerproject (PP, MDB), Asta Easyplan (PP), Phoenix Project Manager (PPX), FastTrack Schedule (FTS), and the Standard Data Exchange Format (SDEF).
/*
* file: UniversalProjectReader.java
* author: Jon Iles
* copyright: (c) Packwood Software 2016
* date: 2016-10-13
*/
/*
* This library 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 2.1 of the License, or (at your
* option) any later version.
*
* This library 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 this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
package net.sf.mpxj.reader;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import net.sf.mpxj.MPXJException;
import net.sf.mpxj.ProjectFile;
import net.sf.mpxj.asta.AstaDatabaseFileReader;
import net.sf.mpxj.asta.AstaDatabaseReader;
import net.sf.mpxj.asta.AstaFileReader;
import net.sf.mpxj.common.InputStreamHelper;
import net.sf.mpxj.listener.ProjectListener;
import net.sf.mpxj.mpd.MPDDatabaseReader;
import net.sf.mpxj.mpp.MPPReader;
import net.sf.mpxj.mpx.MPXReader;
import net.sf.mpxj.mspdi.MSPDIReader;
import net.sf.mpxj.planner.PlannerReader;
import net.sf.mpxj.primavera.PrimaveraDatabaseReader;
import net.sf.mpxj.primavera.PrimaveraPMFileReader;
import net.sf.mpxj.primavera.PrimaveraXERFileReader;
/**
* This class implements a universal project reader: given a file or a stream this reader
* will sample the content and determine the type of file it has been given. It will then
* instantiate the correct reader for that file type and proceed to read the file.
*/
public class UniversalProjectReader extends AbstractProjectReader
{
/**
* {@inheritDoc}
*/
@Override public void addProjectListener(ProjectListener listener)
{
if (m_projectListeners == null)
{
m_projectListeners = new LinkedList();
}
m_projectListeners.add(listener);
}
/**
* Note that this method returns null if we can't determine the file type.
*
* {@inheritDoc}
*/
@Override public ProjectFile read(InputStream inputStream) throws MPXJException
{
try
{
BufferedInputStream bis = new BufferedInputStream(inputStream);
bis.mark(BUFFER_SIZE);
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = bis.read(buffer);
bis.reset();
//
// If the file is smaller than the buffer we are peeking into,
// it's probably not a valid schedule file.
//
if (bytesRead != BUFFER_SIZE)
{
throw new MPXJException(MPXJException.INVALID_FILE);
}
if (matchesFingerprint(buffer, MPP_FINGERPRINT))
{
return readProjectFile(new MPPReader(), bis);
}
if (matchesFingerprint(buffer, MSPDI_FINGERPRINT))
{
return new MSPDIReader().read(bis);
}
if (matchesFingerprint(buffer, PP_FINGERPRINT))
{
return readProjectFile(new AstaFileReader(), bis);
}
if (matchesFingerprint(buffer, MPX_FINGERPRINT))
{
return readProjectFile(new MPXReader(), bis);
}
if (matchesFingerprint(buffer, XER_FINGERPRINT))
{
return readProjectFile(new PrimaveraXERFileReader(), bis);
}
if (matchesFingerprint(buffer, PLANNER_FINGERPRINT))
{
return readProjectFile(new PlannerReader(), bis);
}
if (matchesFingerprint(buffer, PMXML_FINGERPRINT))
{
return readProjectFile(new PrimaveraPMFileReader(), bis);
}
if (matchesFingerprint(buffer, MDB_FINGERPRINT))
{
return handleMDBFile(bis);
}
if (matchesFingerprint(buffer, SQLITE_FINGERPRINT))
{
return handleSQLiteFile(bis);
}
if (matchesFingerprint(buffer, ZIP_FINGERPRINT))
{
return handleZipFile(bis);
}
return null;
}
catch (Exception ex)
{
throw new MPXJException(MPXJException.INVALID_FILE, ex);
}
}
/**
* Determine if the start of the buffer matches a fingerprint byte array.
*
* @param buffer bytes from file
* @param fingerprint fingerprint bytes
* @return true if the file matches the fingerprint
*/
private boolean matchesFingerprint(byte[] buffer, byte[] fingerprint)
{
return Arrays.equals(fingerprint, Arrays.copyOf(buffer, fingerprint.length));
}
/**
* Determine if the buffer, when expressed as text, matches a fingerprint regular expression.
*
* @param buffer bytes from file
* @param fingerprint fingerprint regular expression
* @return true if the file matches the fingerprint
*/
private boolean matchesFingerprint(byte[] buffer, Pattern fingerprint)
{
return fingerprint.matcher(new String(buffer)).matches();
}
/**
* Adds listeners and reads from a stream.
*
* @param reader reader for file type
* @param stream schedule data
* @return ProjectFile instance
*/
private ProjectFile readProjectFile(ProjectReader reader, InputStream stream) throws MPXJException
{
addListeners(reader);
return reader.read(stream);
}
/**
* Adds listeners and reads from a file.
*
* @param reader reader for file type
* @param file schedule data
* @return ProjectFile instance
*/
private ProjectFile readProjectFile(ProjectReader reader, File file) throws MPXJException
{
addListeners(reader);
return reader.read(file);
}
/**
* We have identified that we have an MDB file. This could be a Microsoft Project database
* or an Asta database. Open the database and use the table names present to determine
* which type this is.
*
* @param stream schedule data
* @return ProjectFile instance
*/
private ProjectFile handleMDBFile(InputStream stream) throws Exception
{
File file = InputStreamHelper.writeStreamToTempFile(stream, ".mdb");
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url = "jdbc:odbc:DRIVER=Microsoft Access Driver (*.mdb);DBQ=" + file.getCanonicalPath();
Set tableNames = populateTableNames(url);
if (tableNames.contains("MSP_PROJECTS"))
{
return readProjectFile(new MPDDatabaseReader(), file);
}
if (tableNames.contains("EXCEPTIONN"))
{
return readProjectFile(new AstaDatabaseReader(), file);
}
return null;
}
finally
{
file.delete();
}
}
/**
* We have identified that we have a SQLite file. This could be a Primavera Project database
* or an Asta database. Open the database and use the table names present to determine
* which type this is.
*
* @param stream schedule data
* @return ProjectFile instance
*/
private ProjectFile handleSQLiteFile(InputStream stream) throws Exception
{
File file = InputStreamHelper.writeStreamToTempFile(stream, ".sqlite");
try
{
Class.forName("org.sqlite.JDBC");
String url = "jdbc:sqlite:" + file.getCanonicalPath();
Set tableNames = populateTableNames(url);
if (tableNames.contains("EXCEPTIONN"))
{
return readProjectFile(new AstaDatabaseFileReader(), file);
}
if (tableNames.contains("PROJWBS"))
{
Connection connection = null;
try
{
Properties props = new Properties();
props.setProperty("date_string_format", "yyyy-MM-dd HH:mm:ss");
connection = DriverManager.getConnection(url, props);
PrimaveraDatabaseReader reader = new PrimaveraDatabaseReader();
reader.setConnection(connection);
addListeners(reader);
return reader.read();
}
finally
{
if (connection != null)
{
connection.close();
}
}
}
return null;
}
finally
{
file.delete();
}
}
/**
* We have identified that we have a zip file. Let's assume that it contains just one
* entry so pass the stream representing that entry to UniversalProjectReader to
* see if we recognise the file type.
*
* @param stream schedule data
* @return ProjectFile instance
*/
private ProjectFile handleZipFile(InputStream stream) throws Exception
{
ZipInputStream zip = new ZipInputStream(stream);
ZipEntry entry = zip.getNextEntry();
if (entry != null)
{
return new UniversalProjectReader().read(zip);
}
return null;
}
/**
* Open a database and build a set of table names.
*
* @param url database URL
* @return set containing table names
*/
private Set populateTableNames(String url) throws SQLException
{
Set tableNames = new HashSet();
Connection connection = null;
ResultSet rs = null;
try
{
connection = DriverManager.getConnection(url);
DatabaseMetaData dmd = connection.getMetaData();
rs = dmd.getTables(null, null, null, null);
while (rs.next())
{
tableNames.add(rs.getString("TABLE_NAME").toUpperCase());
}
}
finally
{
if (rs != null)
{
rs.close();
}
if (connection != null)
{
connection.close();
}
}
return tableNames;
}
/**
* Adds any listeners attached to this reader to the reader created internally.
*
* @param reader internal project reader
*/
private void addListeners(ProjectReader reader)
{
if (m_projectListeners != null)
{
for (ProjectListener listener : m_projectListeners)
{
reader.addProjectListener(listener);
}
}
}
private List m_projectListeners;
private static final int BUFFER_SIZE = 255;
private static final byte[] MPP_FINGERPRINT =
{
(byte) 0xD0,
(byte) 0xCF,
(byte) 0x11,
(byte) 0xE0,
(byte) 0xA1,
(byte) 0xB1,
(byte) 0x1A,
(byte) 0xE1
};
private static final byte[] PP_FINGERPRINT =
{
(byte) 0x00,
(byte) 0x00,
(byte) 0x30,
(byte) 0x30,
(byte) 0x30,
(byte) 0x30,
(byte) 0x30,
(byte) 0x30
};
private static final byte[] MPX_FINGERPRINT =
{
(byte) 'M',
(byte) 'P',
(byte) 'X',
(byte) ','
};
private static final byte[] MDB_FINGERPRINT =
{
(byte) 0x00,
(byte) 0x01,
(byte) 0x00,
(byte) 0x00,
(byte) 'S',
(byte) 't',
(byte) 'a',
(byte) 'n',
(byte) 'd',
(byte) 'a',
(byte) 'r',
(byte) 'd',
(byte) ' ',
(byte) 'J',
(byte) 'e',
(byte) 't',
(byte) ' ',
(byte) 'D',
(byte) 'B',
};
private static final byte[] SQLITE_FINGERPRINT =
{
(byte) 'S',
(byte) 'Q',
(byte) 'L',
(byte) 'i',
(byte) 't',
(byte) 'e',
(byte) ' ',
(byte) 'f',
(byte) 'o',
(byte) 'r',
(byte) 'm',
(byte) 'a',
(byte) 't'
};
private static final byte[] XER_FINGERPRINT =
{
(byte) 'E',
(byte) 'R',
(byte) 'M',
(byte) 'H',
(byte) 'D',
(byte) 'R'
};
private static final byte[] ZIP_FINGERPRINT =
{
(byte) 'P',
(byte) 'K'
};
private static final Pattern PLANNER_FINGERPRINT = Pattern.compile(".*
© 2015 - 2025 Weber Informatics LLC | Privacy Policy