| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF 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 org.apache.poi.openxml4j.opc; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| |
| import org.apache.poi.openxml4j.exceptions.InvalidFormatException; |
| import org.apache.poi.openxml4j.exceptions.InvalidOperationException; |
| import org.apache.poi.openxml4j.exceptions.OpenXML4JException; |
| import org.apache.poi.openxml4j.opc.internal.ContentType; |
| |
| /** |
| * Provides a base class for parts stored in a Package. |
| */ |
| public abstract class PackagePart implements RelationshipSource, Comparable<PackagePart> { |
| |
| /** |
| * This part's container. |
| */ |
| protected OPCPackage _container; |
| |
| /** |
| * The part name. (required by the specification [M1.1]) |
| */ |
| protected PackagePartName _partName; |
| |
| /** |
| * The type of content of this part. (required by the specification [M1.2]) |
| */ |
| protected ContentType _contentType; |
| |
| /** |
| * Flag to know if this part is a relationship. |
| */ |
| private final boolean _isRelationshipPart; |
| |
| /** |
| * Flag to know if this part has been logically deleted. |
| */ |
| private boolean _isDeleted; |
| |
| /** |
| * This part's relationships. |
| */ |
| private PackageRelationshipCollection _relationships; |
| |
| |
| /** |
| * Constructor. |
| * |
| * @param pack |
| * Parent package. |
| * @param partName |
| * The part name, relative to the parent Package root. |
| * @param contentType |
| * The content type. |
| * @throws InvalidFormatException |
| * If the specified URI is not valid. |
| */ |
| protected PackagePart(OPCPackage pack, PackagePartName partName, |
| ContentType contentType) throws InvalidFormatException { |
| this(pack, partName, contentType, true); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param pack |
| * Parent package. |
| * @param partName |
| * The part name, relative to the parent Package root. |
| * @param contentType |
| * The content type. |
| * @param loadRelationships |
| * Specify if the relationships will be loaded |
| * @throws InvalidFormatException |
| * If the specified URI is not valid. |
| */ |
| protected PackagePart(OPCPackage pack, PackagePartName partName, |
| ContentType contentType, boolean loadRelationships) |
| throws InvalidFormatException { |
| _partName = partName; |
| _contentType = contentType; |
| _container = pack; |
| |
| // Check if this part is a relationship part |
| _isRelationshipPart = this._partName.isRelationshipPartURI(); |
| |
| // Load relationships if any |
| if (loadRelationships) { |
| loadRelationships(); |
| } |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param pack |
| * Parent package. |
| * @param partName |
| * The part name, relative to the parent Package root. |
| * @param contentType |
| * The Multipurpose Internet Mail Extensions (MIME) content type |
| * of the part's data stream. |
| * @throws InvalidFormatException |
| * If the specified URI is not valid. |
| */ |
| public PackagePart(OPCPackage pack, PackagePartName partName, |
| String contentType) throws InvalidFormatException { |
| this(pack, partName, new ContentType(contentType)); |
| } |
| |
| /** |
| * Check if the new part was already added before via PackagePart.addRelationship() |
| * |
| * @param packagePart to find the relationship for |
| * @return The existing relationship, or null if there isn't yet one |
| */ |
| public PackageRelationship findExistingRelation(PackagePart packagePart) { |
| return _relationships.findExistingInternalRelation(packagePart); |
| } |
| |
| /** |
| * Adds an external relationship to a part (except relationships part). |
| * |
| * The targets of external relationships are not subject to the same |
| * validity checks that internal ones are, as the contents is potentially |
| * any file, URL or similar. |
| * |
| * @param target |
| * External target of the relationship |
| * @param relationshipType |
| * Type of relationship. |
| * @return The newly created and added relationship |
| */ |
| @Override |
| public PackageRelationship addExternalRelationship(String target, |
| String relationshipType) { |
| return addExternalRelationship(target, relationshipType, null); |
| } |
| |
| /** |
| * Adds an external relationship to a part (except relationships part). |
| * |
| * The targets of external relationships are not subject to the same |
| * validity checks that internal ones are, as the contents is potentially |
| * any file, URL or similar. |
| * |
| * @param target |
| * External target of the relationship |
| * @param relationshipType |
| * Type of relationship. |
| * @param id |
| * Relationship unique id. |
| * @return The newly created and added relationship |
| * @see org.apache.poi.openxml4j.opc.RelationshipSource#addExternalRelationship(java.lang.String, |
| * java.lang.String) |
| */ |
| @Override |
| public PackageRelationship addExternalRelationship(String target, |
| String relationshipType, String id) { |
| if (target == null) { |
| throw new IllegalArgumentException("target is null for type " + relationshipType); |
| } |
| if (relationshipType == null) { |
| throw new IllegalArgumentException("relationshipType"); |
| } |
| |
| if (_relationships == null) { |
| _relationships = new PackageRelationshipCollection(); |
| } |
| |
| URI targetURI; |
| try { |
| targetURI = new URI(target); |
| } catch (URISyntaxException e) { |
| throw new IllegalArgumentException("Invalid target - " + e); |
| } |
| |
| return _relationships.addRelationship(targetURI, TargetMode.EXTERNAL, |
| relationshipType, id); |
| } |
| |
| /** |
| * Add a relationship to a part (except relationships part). |
| * |
| * @param targetPartName |
| * Name of the target part. This one must be relative to the |
| * source root directory of the part. |
| * @param targetMode |
| * Mode [Internal|External]. |
| * @param relationshipType |
| * Type of relationship. |
| * @return The newly created and added relationship |
| */ |
| @Override |
| public PackageRelationship addRelationship(PackagePartName targetPartName, |
| TargetMode targetMode, String relationshipType) { |
| return addRelationship(targetPartName, targetMode, relationshipType, |
| null); |
| } |
| |
| /** |
| * Add a relationship to a part (except relationships part). |
| * <p> |
| * Check rule M1.25: The Relationships part shall not have relationships to |
| * any other part. Package implementers shall enforce this requirement upon |
| * the attempt to create such a relationship and shall treat any such |
| * relationship as invalid. |
| * </p> |
| * @param targetPartName |
| * Name of the target part. This one must be relative to the |
| * source root directory of the part. |
| * @param targetMode |
| * Mode [Internal|External]. |
| * @param relationshipType |
| * Type of relationship. |
| * @param id |
| * Relationship unique id. |
| * @return The newly created and added relationship |
| * |
| * @throws InvalidOperationException |
| * If a writing operation is done on a read only package or |
| * invalid nested relations are created. |
| * @throws IllegalArgumentException if targetPartName, targetMode |
| * or relationshipType are passed as null |
| */ |
| @Override |
| public PackageRelationship addRelationship(PackagePartName targetPartName, |
| TargetMode targetMode, String relationshipType, String id) { |
| _container.throwExceptionIfReadOnly(); |
| |
| if (targetPartName == null) { |
| throw new IllegalArgumentException("targetPartName"); |
| } |
| if (targetMode == null) { |
| throw new IllegalArgumentException("targetMode"); |
| } |
| if (relationshipType == null) { |
| throw new IllegalArgumentException("relationshipType"); |
| } |
| |
| if (this._isRelationshipPart || targetPartName.isRelationshipPartURI()) { |
| throw new InvalidOperationException( |
| "Rule M1.25: The Relationships part shall not have relationships to any other part."); |
| } |
| |
| if (_relationships == null) { |
| _relationships = new PackageRelationshipCollection(); |
| } |
| |
| return _relationships.addRelationship(targetPartName.getURI(), |
| targetMode, relationshipType, id); |
| } |
| |
| /** |
| * Add a relationship to a part (except relationships part). |
| * |
| * @param targetURI |
| * URI the target part. Must be relative to the source root |
| * directory of the part. |
| * @param targetMode |
| * Mode [Internal|External]. |
| * @param relationshipType |
| * Type of relationship. |
| * @return The newly created and added relationship |
| * @see org.apache.poi.openxml4j.opc.RelationshipSource#addRelationship(org.apache.poi.openxml4j.opc.PackagePartName, |
| * org.apache.poi.openxml4j.opc.TargetMode, java.lang.String) |
| */ |
| public PackageRelationship addRelationship(URI targetURI, |
| TargetMode targetMode, String relationshipType) { |
| return addRelationship(targetURI, targetMode, relationshipType, null); |
| } |
| |
| /** |
| * Add a relationship to a part (except relationships part). |
| * <p> |
| * Check rule M1.25: The Relationships part shall not have relationships to |
| * any other part. Package implementers shall enforce this requirement upon |
| * the attempt to create such a relationship and shall treat any such |
| * relationship as invalid. |
| * </p> |
| * @param targetURI |
| * URI of the target part. Must be relative to the source root |
| * directory of the part. |
| * @param targetMode |
| * Mode [Internal|External]. |
| * @param relationshipType |
| * Type of relationship. |
| * @param id |
| * Relationship unique id. |
| * @return The newly created and added relationship |
| * |
| * @throws InvalidOperationException |
| * If the URI point to a relationship part URI. |
| * @see org.apache.poi.openxml4j.opc.RelationshipSource#addRelationship(org.apache.poi.openxml4j.opc.PackagePartName, |
| * org.apache.poi.openxml4j.opc.TargetMode, java.lang.String, java.lang.String) |
| */ |
| public PackageRelationship addRelationship(URI targetURI, |
| TargetMode targetMode, String relationshipType, String id) { |
| _container.throwExceptionIfReadOnly(); |
| |
| if (targetURI == null) { |
| throw new IllegalArgumentException("targetPartName"); |
| } |
| if (targetMode == null) { |
| throw new IllegalArgumentException("targetMode"); |
| } |
| if (relationshipType == null) { |
| throw new IllegalArgumentException("relationshipType"); |
| } |
| |
| // Try to retrieve the target part |
| |
| if (this._isRelationshipPart |
| || PackagingURIHelper.isRelationshipPartURI(targetURI)) { |
| throw new InvalidOperationException( |
| "Rule M1.25: The Relationships part shall not have relationships to any other part."); |
| } |
| |
| if (_relationships == null) { |
| _relationships = new PackageRelationshipCollection(); |
| } |
| |
| return _relationships.addRelationship(targetURI, |
| targetMode, relationshipType, id); |
| } |
| |
| @Override |
| public void clearRelationships() { |
| if (_relationships != null) { |
| _relationships.clear(); |
| } |
| } |
| |
| /** |
| * Delete the relationship specified by its id. |
| * |
| * @param id |
| * The ID identified the part to delete. |
| */ |
| @Override |
| public void removeRelationship(String id) { |
| this._container.throwExceptionIfReadOnly(); |
| if (this._relationships != null) |
| this._relationships.removeRelationship(id); |
| } |
| |
| /** |
| * Retrieve all the relationships attached to this part. |
| * |
| * @return This part's relationships. |
| * @throws InvalidOperationException |
| * Throws if the package is open en write only mode. |
| */ |
| @Override |
| public PackageRelationshipCollection getRelationships() |
| throws InvalidFormatException { |
| return getRelationshipsCore(null); |
| } |
| |
| /** |
| * Retrieves a package relationship from its id. |
| * |
| * @param id |
| * ID of the package relationship to retrieve. |
| * @return The package relationship |
| */ |
| @Override |
| public PackageRelationship getRelationship(String id) { |
| return this._relationships.getRelationshipByID(id); |
| } |
| |
| /** |
| * Retrieve all relationships attached to this part which have the specified |
| * type. |
| * |
| * @param relationshipType |
| * Relationship type filter. |
| * @return All relationships from this part that have the specified type. |
| * @throws InvalidFormatException |
| * If an error occurs while parsing the part. |
| * @throws InvalidOperationException |
| * If the package is open in write only mode. |
| */ |
| @Override |
| public PackageRelationshipCollection getRelationshipsByType( |
| String relationshipType) throws InvalidFormatException { |
| _container.throwExceptionIfWriteOnly(); |
| |
| return getRelationshipsCore(relationshipType); |
| } |
| |
| /** |
| * Implementation of the getRelationships method(). |
| * |
| * @param filter |
| * Relationship type filter. If <i>null</i> then the filter is |
| * disabled and return all the relationships. |
| * @return All relationships from this part that have the specified type. |
| * @throws InvalidFormatException |
| * Throws if an error occurs during parsing the relationships |
| * part. |
| * @throws InvalidOperationException |
| * Throws if the package is open en write only mode. |
| * @see #getRelationshipsByType(String) |
| */ |
| private PackageRelationshipCollection getRelationshipsCore(String filter) |
| throws InvalidFormatException { |
| this._container.throwExceptionIfWriteOnly(); |
| if (_relationships == null) { |
| this.throwExceptionIfRelationship(); |
| _relationships = new PackageRelationshipCollection(this); |
| } |
| return new PackageRelationshipCollection(_relationships, filter); |
| } |
| |
| /** |
| * Knows if the part have any relationships. |
| * |
| * @return <b>true</b> if the part have at least one relationship else |
| * <b>false</b>. |
| */ |
| @Override |
| public boolean hasRelationships() { |
| return (!this._isRelationshipPart && (_relationships != null && _relationships |
| .size() > 0)); |
| } |
| |
| /** |
| * Checks if the specified relationship is part of this package part. |
| * |
| * @param rel |
| * The relationship to check. |
| * @return <b>true</b> if the specified relationship exists in this part, |
| * else returns <b>false</b> |
| */ |
| @Override |
| public boolean isRelationshipExists(PackageRelationship rel) { |
| return rel != null && _relationships.getRelationshipByID(rel.getId()) != null; |
| } |
| |
| /** |
| * Get the PackagePart that is the target of a relationship. |
| * |
| * @param rel A relationship from this part to another one |
| * @return The target part of the relationship |
| * @throws InvalidFormatException |
| * If the specified URI is not valid. |
| */ |
| public PackagePart getRelatedPart(PackageRelationship rel) throws InvalidFormatException { |
| // Ensure this is one of ours |
| if(! isRelationshipExists(rel)) { |
| throw new IllegalArgumentException("Relationship " + rel + " doesn't start with this part " + _partName); |
| } |
| // Get the target URI, excluding any relative fragments |
| URI target = rel.getTargetURI(); |
| if(target.getFragment() != null) { |
| String t = target.toString(); |
| try { |
| target = new URI( t.substring(0, t.indexOf('#')) ); |
| } catch(URISyntaxException e) { |
| throw new InvalidFormatException("Invalid target URI: " + target); |
| } |
| } |
| |
| // Turn that into a name, and fetch |
| PackagePartName relName = PackagingURIHelper.createPartName(target); |
| PackagePart part = _container.getPart(relName); |
| if (part == null) { |
| throw new IllegalArgumentException("No part found for relationship " + rel); |
| } |
| return part; |
| } |
| |
| /** |
| * Get the input stream of this part to read its content. |
| * |
| * @return The input stream of the content of this part, else |
| * {@code null}. |
| * |
| * @throws IOException If creating the input-stream fails. |
| */ |
| public InputStream getInputStream() throws IOException { |
| InputStream inStream = this.getInputStreamImpl(); |
| if (inStream == null) { |
| throw new IOException("Can't obtain the input stream from " |
| + _partName.getName()); |
| } |
| return inStream; |
| } |
| |
| /** |
| * Get the output stream of this part. If the part is originally embedded in |
| * Zip package, it'll be transform into a <i>MemoryPackagePart</i> in |
| * order to write inside (the standard Java API doesn't allow to write in |
| * the file) |
| * |
| * @return output stream for this part |
| * @see org.apache.poi.openxml4j.opc.internal.MemoryPackagePart |
| */ |
| public OutputStream getOutputStream() { |
| OutputStream outStream; |
| // If this part is a zip package part (read only by design) we convert |
| // this part into a MemoryPackagePart instance for write purpose. |
| if (this instanceof ZipPackagePart) { |
| // Delete logically this part |
| _container.removePart(this._partName); |
| |
| // Create a memory part |
| PackagePart part = _container.createPart(this._partName, |
| this._contentType.toString(), false); |
| if (part == null) { |
| throw new InvalidOperationException( |
| "Can't create a temporary part !"); |
| } |
| part._relationships = this._relationships; |
| outStream = part.getOutputStreamImpl(); |
| } else { |
| outStream = this.getOutputStreamImpl(); |
| } |
| return outStream; |
| } |
| |
| /** |
| * Throws an exception if this package part is a relationship part. |
| * |
| * @throws InvalidOperationException |
| * If this part is a relationship part. |
| */ |
| private void throwExceptionIfRelationship() |
| throws InvalidOperationException { |
| if (this._isRelationshipPart) |
| throw new InvalidOperationException( |
| "Can do this operation on a relationship part !"); |
| } |
| |
| /** |
| * Ensure the package relationships collection instance is built. |
| * |
| * @throws InvalidFormatException |
| * Throws if |
| */ |
| /* package */ void loadRelationships() throws InvalidFormatException { |
| if (this._relationships == null && !this._isRelationshipPart) { |
| this.throwExceptionIfRelationship(); |
| _relationships = new PackageRelationshipCollection(this); |
| } |
| } |
| |
| /* |
| * Accessors |
| */ |
| |
| /** |
| * @return the uri |
| */ |
| public PackagePartName getPartName() { |
| return _partName; |
| } |
| |
| /** |
| * @return The Content Type of the part |
| */ |
| public String getContentType() { |
| return _contentType.toString(); |
| } |
| |
| /** |
| * @return The Content Type, including parameters, of the part |
| */ |
| public ContentType getContentTypeDetails() { |
| return _contentType; |
| } |
| |
| /** |
| * Set the content type. |
| * |
| * @param contentType |
| * the contentType to set |
| * |
| * @throws InvalidFormatException |
| * Throws if the content type is not valid. |
| * @throws InvalidOperationException |
| * Throws if you try to change the content type whereas this |
| * part is already attached to a package. |
| */ |
| public void setContentType(String contentType) |
| throws InvalidFormatException { |
| if (_container == null) { |
| _contentType = new ContentType(contentType); |
| } |
| else { |
| _container.unregisterPartAndContentType(_partName); |
| _contentType = new ContentType(contentType); |
| _container.registerPartAndContentType(this); |
| } |
| } |
| |
| public OPCPackage getPackage() { |
| return _container; |
| } |
| |
| /** |
| * @return true if this part is a relationship |
| */ |
| public boolean isRelationshipPart() { |
| return this._isRelationshipPart; |
| } |
| |
| /** |
| * @return true if this part has been logically deleted |
| */ |
| public boolean isDeleted() { |
| return _isDeleted; |
| } |
| |
| /** |
| * @param isDeleted |
| * the isDeleted to set |
| */ |
| public void setDeleted(boolean isDeleted) { |
| this._isDeleted = isDeleted; |
| } |
| |
| /** |
| * @return The length of the part in bytes, or -1 if not known |
| */ |
| public long getSize() { |
| return -1; |
| } |
| |
| @Override |
| public String toString() { |
| return "Name: " + this._partName + " - Content Type: " |
| + this._contentType; |
| } |
| |
| /** |
| * Compare based on the package part name, using a natural sort order |
| */ |
| @Override |
| public int compareTo(PackagePart other) |
| { |
| // NOTE could also throw a NullPointerException() if desired |
| if (other == null) |
| return -1; |
| |
| return PackagePartName.compare(this._partName, other._partName); |
| } |
| |
| /*-------------- Abstract methods ------------- */ |
| |
| /** |
| * Method that gets the input stream for this part. |
| * |
| * @return input stream for this part |
| * @exception IOException |
| * Throws if an IO Exception occur in the implementation |
| * method. |
| */ |
| protected abstract InputStream getInputStreamImpl() throws IOException; |
| |
| /** |
| * Method that gets the output stream for this part. |
| * |
| * @return output stream for this part |
| */ |
| protected abstract OutputStream getOutputStreamImpl(); |
| |
| /** |
| * Save the content of this part and the associated relationships part (if |
| * this part own at least one relationship) into the specified output |
| * stream. |
| * |
| * @param zos |
| * Output stream to save this part. |
| * @return true if the content has been successfully stored, false otherwise. |
| * More information about errors may be logged via Log4j 2. |
| * @throws OpenXML4JException |
| * If any exception occur. |
| */ |
| public abstract boolean save(OutputStream zos) throws OpenXML4JException; |
| |
| /** |
| * Load the content of this part. |
| * |
| * @param ios |
| * The input stream of the content to load. |
| * @return true if the content has been successfully loaded, false otherwise. |
| * More information about errors may be logged via Log4j 2. |
| * @throws InvalidFormatException |
| * Throws if the content format is invalid. |
| */ |
| public abstract boolean load(InputStream ios) throws InvalidFormatException; |
| |
| /** |
| * Close this part : flush this part, close the input stream and output |
| * stream. After this method call, the part must be available for packaging. |
| */ |
| public abstract void close(); |
| |
| /** |
| * Flush the content of this part. If the input stream and/or output stream |
| * as in a waiting state to read or write, the must to empty their |
| * respective buffer. |
| */ |
| public abstract void flush(); |
| |
| /** |
| * Allows sub-classes to clean up before new data is added. |
| */ |
| public void clear() { |
| } |
| } |