001    /*
002     * DSpaceMETSDisseminator.java
003     *
004     * Version: $Revision: 3761 $
005     *
006     * Date: $Date: 2009-05-07 04:18:02 +0000 (Thu, 07 May 2009) $
007     *
008     * Copyright (c) 2002-2009, The DSpace Foundation.  All rights reserved.
009     *
010     * Redistribution and use in source and binary forms, with or without
011     * modification, are permitted provided that the following conditions are
012     * met:
013     *
014     * - Redistributions of source code must retain the above copyright
015     * notice, this list of conditions and the following disclaimer.
016     *
017     * - Redistributions in binary form must reproduce the above copyright
018     * notice, this list of conditions and the following disclaimer in the
019     * documentation and/or other materials provided with the distribution.
020     *
021     * - Neither the name of the DSpace Foundation nor the names of its
022     * contributors may be used to endorse or promote products derived from
023     * this software without specific prior written permission.
024     *
025     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
026     * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
027     * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
028     * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
029     * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
030     * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
031     * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
032     * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
033     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
034     * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
035     * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
036     * DAMAGE.
037     */
038    
039    package org.dspace.content.packager;
040    
041    import java.io.IOException;
042    import java.io.InputStream;
043    import java.sql.SQLException;
044    
045    import org.apache.log4j.Logger;
046    import org.dspace.authorize.AuthorizeException;
047    import org.dspace.content.Bitstream;
048    import org.dspace.content.BitstreamFormat;
049    import org.dspace.content.Bundle;
050    import org.dspace.content.Item;
051    import org.dspace.core.Constants;
052    import org.dspace.core.Context;
053    import org.dspace.license.CreativeCommons;
054    
055    import edu.harvard.hul.ois.mets.AmdSec;
056    import edu.harvard.hul.ois.mets.BinData;
057    import edu.harvard.hul.ois.mets.Loctype;
058    import edu.harvard.hul.ois.mets.MdRef;
059    import edu.harvard.hul.ois.mets.MdWrap;
060    import edu.harvard.hul.ois.mets.Mdtype;
061    import edu.harvard.hul.ois.mets.Mets;
062    import edu.harvard.hul.ois.mets.RightsMD;
063    import edu.harvard.hul.ois.mets.helper.Base64;
064    import edu.harvard.hul.ois.mets.helper.MetsException;
065    
066    /**
067     * Packager plugin to produce a
068     * METS (Metadata Encoding & Transmission Standard) package
069     * that is accepted as a DSpace METS SIP (Submission Information Package).
070     * See <a href="http://www.loc.gov/standards/mets/">http://www.loc.gov/standards/mets/</a>
071     * for more information on METS.
072     * <p>
073     * This class does not produce a true DSpace DIP, because there was no
074     * DIP standard when it was implemented.  It does contain some features
075     * beyond the requirements of a SIP (e.g. deposit licenses), anticipating
076     * the DIP specification.
077     * <p>
078     * DSpaceMETSDisseminator was intended to be an useful example of a packager
079     * plugin, and a way to create packages acceptable to the METS SIP importer.
080     *
081     * @author Larry Stone
082     * @version $Revision: 3761 $
083     */
084    public class DSpaceMETSDisseminator
085        extends AbstractMETSDisseminator
086    {
087        /** log4j category */
088        private static Logger log = Logger.getLogger(DSpaceMETSDisseminator.class);
089    
090        /**
091         * Identifier for the package we produce, i.e. DSpace METS SIP
092         * Profile.  Though not strictly true, there is no DIP standard yet
093         * so it's the most meaningful label we can apply.
094         */
095        private final static String PROFILE_LABEL = "DSpace METS SIP Profile 1.0";
096    
097        // MDTYPE value for deposit license -- "magic string"
098        private final static String DSPACE_DEPOSIT_LICENSE_MDTYPE =
099                                    "DSpace Deposit License";
100    
101        // MDTYPE value for CC license -- "magic string"
102        private final static String CREATIVE_COMMONS_LICENSE_MDTYPE =
103                                    "Creative Commons";
104    
105        /**
106         * Return identifier string for the profile this produces.
107         *
108         * @return string name of profile.
109         */
110        public String getProfile()
111        {
112            return PROFILE_LABEL;
113        }
114    
115        /**
116         * Returns name of METS fileGrp corresponding to a DSpace bundle name.
117         * They are mostly the same except for bundle "ORIGINAL" maps to "CONTENT".
118         * Don't worry about the metadata bundles since they are not
119         * packaged as fileGrps, but in *mdSecs.
120         * @param bname name of DSpace bundle.
121         * @return string name of fileGrp
122         */
123        public String bundleToFileGrp(String bname)
124        {
125            if (bname.equals("ORIGINAL"))
126                return "CONTENT";
127            else
128                return bname;
129        }
130    
131        /**
132         * Get DMD choice for Item.  It defaults to MODS, but is overridden
133         * by the package parameters if they contain any "dmd" keys.  The
134         * params may contain one or more values for "dmd"; each of those is
135         * the name of a crosswalk plugin, optionally followed by colon and
136         * its METS MDTYPE name.
137         */
138        public String [] getDmdTypes(PackageParameters params)
139            throws SQLException, IOException, AuthorizeException
140        {
141    
142        // XXX maybe let dmd choices be configured in DSpace config too?
143    
144            String result[] = null;
145            if (params != null)
146                result = params.getProperties("dmd");
147            if (result == null || result.length == 0)
148            {
149                result = new String[1];
150                result[0] = "MODS";
151            }
152            return result;
153        }
154    
155        /**
156         * Get name of technical metadata crosswalk for Bitstreams.
157         * Default is PREMIS.  This is both the name of the crosswalk plugin
158         * and the METS MDTYPE.
159         */
160        public String getTechMdType(PackageParameters params)
161            throws SQLException, IOException, AuthorizeException
162        {
163            return "PREMIS";
164        }
165    
166        /**
167         * Add rights MD (licenses) for DSpace item.  These
168         * may include a deposit license, and Creative Commons.
169         */
170        public void addRightsMd(Context context, Item item, AmdSec amdSec)
171            throws SQLException, IOException, AuthorizeException, MetsException
172        {
173            addDepositLicense(context, item, amdSec);
174            addCreativeCommons(context, item, amdSec);
175        }
176    
177        // Add deposit license, if any, as external file.
178        // Give it a unique name including the SID in case there are other
179        // deposit license artifacts in the Item.
180        private boolean addDepositLicense(Context context, Item item, AmdSec amdSec)
181            throws SQLException, IOException, AuthorizeException, MetsException
182        {
183            Bitstream licenseBs = findDepositLicense(context, item);
184    
185            if (licenseBs == null)
186                return false;
187            else
188            {
189                String resource = "depositlicense_"+
190                                  String.valueOf(licenseBs.getSequenceID())+".txt";
191                addRightsStream(licenseBs.retrieve(), resource, "text/plain",
192                               DSPACE_DEPOSIT_LICENSE_MDTYPE, amdSec);
193                return true;
194            }
195        }
196    
197        // if there's a CC RDF license, chuck it in external file.
198        private boolean addCreativeCommons(Context context, Item item, AmdSec amdSec)
199            throws SQLException, IOException, AuthorizeException, MetsException
200        {
201            // License as <rightsMD><mdWrap><binData>base64encoded</binData>...
202            Bitstream cc;
203            
204            if ((cc = CreativeCommons.getLicenseRdfBitstream(item)) != null)
205            {
206                addRightsStream(cc.retrieve(),
207                                (gensym("creativecommons") + ".rdf"),
208                                "text/rdf",
209                                CREATIVE_COMMONS_LICENSE_MDTYPE, amdSec);
210            }
211            else if ((cc = CreativeCommons.getLicenseTextBitstream(item)) != null)
212            {
213                addRightsStream(cc.retrieve(),
214                                (gensym("creativecommons") + ".txt"),
215                                "text/plain",
216                                CREATIVE_COMMONS_LICENSE_MDTYPE, amdSec);
217            }
218            else
219                return false;
220            return true;
221        }
222    
223        // utility to add a stream to the METS manifest.
224        // use external file and mdRef if possible, wrap and binData if not.
225        private void addRightsStream(InputStream is , String resourceName,
226                                     String mimeType, String mdType, AmdSec amdSec)
227            throws IOException, MetsException
228        {
229            RightsMD rightsMD = new RightsMD();
230            rightsMD.setID(gensym("rights"));
231            if (extraFiles == null)
232            {
233                MdWrap rightsMDWrap = new MdWrap();
234                rightsMDWrap.setMIMETYPE(mimeType);
235                rightsMDWrap.setMDTYPE(Mdtype.OTHER);
236                rightsMDWrap.setOTHERMDTYPE(mdType);
237                BinData bin = new BinData();
238                bin.getContent().add(new Base64(is));
239                rightsMDWrap.getContent().add(bin);
240                rightsMD.getContent().add(rightsMDWrap);
241            }
242            else
243            {
244                extraFiles.put(resourceName, is);
245                MdRef rightsMDRef = new MdRef();
246                rightsMDRef.setMIMETYPE(mimeType);
247                rightsMDRef.setMDTYPE(Mdtype.OTHER);
248                rightsMDRef.setOTHERMDTYPE(mdType);
249                rightsMDRef.setLOCTYPE(Loctype.URL);
250                rightsMDRef.setXlinkHref(resourceName);
251                rightsMD.getContent().add(rightsMDRef);
252            }
253            amdSec.getContent().add(rightsMD);
254        }
255    
256        /**
257         * Utility to find the license bitstream from an item
258         *
259         * @param context
260         *            DSpace context
261         * @param item
262         *            the item
263         * @return the license bitstream or null
264         *
265         * @throws IOException
266         *             if the license bitstream can't be read
267         */
268        private static Bitstream findDepositLicense(Context context, Item item)
269                throws SQLException, IOException, AuthorizeException
270        {
271            // get license format ID
272            int licenseFormatId = -1;
273            BitstreamFormat bf = BitstreamFormat.findByShortDescription(context,
274                    "License");
275            if (bf != null)
276                licenseFormatId = bf.getID();
277    
278            Bundle[] bundles = item.getBundles(Constants.LICENSE_BUNDLE_NAME);
279            for (int i = 0; i < bundles.length; i++)
280            {
281                // Assume license will be in its own bundle
282                Bitstream[] bitstreams = bundles[i].getBitstreams();
283    
284                if (bitstreams[0].getFormat().getID() == licenseFormatId)
285                {
286                    return bitstreams[0];
287                }
288            }
289    
290            // Oops! No license!
291            return null;
292        }
293    
294        // This is where we'd elaborate on the default structMap; nothing to add, yet.
295        public void addStructMap(Context context, Item item,
296                                   PackageParameters params, Mets mets)
297            throws SQLException, IOException, AuthorizeException, MetsException
298        {
299        }
300    }