/***************************************************************************** * * 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.padaf.preflight.actions; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_KEY_NEXT; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_KEY_S; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_GOTO; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_GOTOR; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_HIDE; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_IMPORT; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_JAVASCRIPT; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_LAUNCH; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_MOVIE; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_NAMED; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_NOOP; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_RESET; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_SETSTATE; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_SOUND; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_SUBMIT; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_THREAD; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_ATYPE_URI; import static org.apache.padaf.preflight.ValidationConstants.ACTION_DICTIONARY_VALUE_TYPE; import static org.apache.padaf.preflight.ValidationConstants.DICTIONARY_KEY_ACTION; import static org.apache.padaf.preflight.ValidationConstants.DICTIONARY_KEY_ADDITIONAL_ACTION; import static org.apache.padaf.preflight.ValidationConstants.DICTIONARY_KEY_OPEN_ACTION; import static org.apache.padaf.preflight.ValidationConstants.DICTIONARY_KEY_TYPE; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.padaf.preflight.ValidationException; import org.apache.padaf.preflight.utils.COSUtils; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSDocument; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSObject; import org.apache.pdfbox.persistence.util.COSObjectKey; public class ActionManagerFactory { // /** // * This map is used to know if an Action has already been validated. It is // * useful to avoid infinite loop in an action which has a Next entry. // */ // private Map<COSObjectKey, Boolean> alreadyCreated = new HashMap<COSObjectKey, Boolean>(); /** * This method extract actions from the given dictionary. An action is * identified by the following entries : * <UL> * <li>A (Action) : Available in Annotations, Outline items * <li>OpenAction (OpenAction) : Available in the Catalog dictionary * <li>AA (Additional Action) : Available in the Catalog dictionary, * Annotations, Pages * </UL> * * If there are no action, an empty list is returned. * * @param dictionary * @param cDoc * @return * @throws ValidationException */ public final List<AbstractActionManager> getActions(COSDictionary dictionary, COSDocument cDoc) throws ValidationException { List<AbstractActionManager> result = new ArrayList<AbstractActionManager>(0); Map<COSObjectKey, Boolean> alreadyCreated = new HashMap<COSObjectKey, Boolean>(); COSBase aDict = dictionary.getDictionaryObject(DICTIONARY_KEY_ACTION); if (aDict != null) { callCreateAction(aDict, cDoc, result, alreadyCreated); } COSBase oaDict = dictionary.getDictionaryObject(DICTIONARY_KEY_OPEN_ACTION); if (oaDict != null) { if (!COSUtils.isArray(oaDict, cDoc)) { callCreateAction(oaDict, cDoc, result, alreadyCreated); } // else Nothing to do because of an array contains a Destination not an // action. } COSBase aa = dictionary .getDictionaryObject(DICTIONARY_KEY_ADDITIONAL_ACTION); if (aa != null) { COSDictionary aaDict = COSUtils.getAsDictionary(aa, cDoc); if (aaDict != null) { for (Object key : aaDict.keySet()) { COSName name = (COSName) key; callCreateAction(aaDict.getItem(name), cDoc, result, name.getName(), alreadyCreated); } } } return result; } /** * Call the callCreateAction(COSBase, COSDocument, List<ActionManager>, * String) method with null as isAA parameter. * * @param aDict * a COSBase object (COSObject or COSDictionary) which represent the * action dictionary. * @param cDoc * the COSDocument which contains the action. * @param result * the list of ActionManager to updated if the aDict parameter is * valid. * @param alreadyCreated This map is used to know if an Action has already been validated. It is * useful to avoid infinite loop in an action which has a Next entry. * @throws ValidationException */ private void callCreateAction(COSBase aDict, COSDocument cDoc, List<AbstractActionManager> result, Map<COSObjectKey, Boolean> alreadyCreated) throws ValidationException { callCreateAction(aDict, cDoc, result, null, alreadyCreated); } /** * Call the create action to add the ActionManager to the result list. If the * aDict parameter isn't an instance of COSDictionary, this method throws a * ValdiationException. If the aDict parameter is a reference to a * COSDicitonary, the action manager is create only if the linked COSObjectKey * is missing from the "alreadyCreated" map, in this case the action is added * to the map. If the aDict parameter is an instance of COSDIctionary, it is * impossible to check if the ActionManager already exists in the * "alreadyCreated" map. * * @param aDict * a COSBase object (COSObject or COSDictionary) which represent the * action dictionary. * @param cDoc * the COSDocument which contains the action. * @param result * the list of ActionManager to updated if the aDict parameter is * valid. * @param additionActionKey * the Action identifier if it is an additional action * @param alreadyCreated This map is used to know if an Action has already been validated. It is * useful to avoid infinite loop in an action which has a Next entry. * @throws ValidationException */ private void callCreateAction(COSBase aDict, COSDocument cDoc, List<AbstractActionManager> result, String additionActionKey, Map<COSObjectKey, Boolean> alreadyCreated) throws ValidationException { if (COSUtils.isDictionary(aDict, cDoc)) { if (aDict instanceof COSObject) { COSObjectKey cok = new COSObjectKey((COSObject) aDict); if (!alreadyCreated.containsKey(cok)) { result.add(createActionManager(COSUtils.getAsDictionary(aDict, cDoc), cDoc, additionActionKey)); alreadyCreated.put(cok, true); } } else { result.add(createActionManager(COSUtils.getAsDictionary(aDict, cDoc), cDoc, additionActionKey)); } } else { throw new ValidationException( "Action entry isn't an instance of COSDictionary"); } } /** * Returns all actions contained by the Next entry. If the action dictionary * doesn't have Next action, the result is an empty list. * * @param actionDictionary * the action dictionary which contains Next entry * @param cDoc * the COSDocument which contains actions. * @return * @throws ValidationException */ public final List<AbstractActionManager> getNextActions( COSDictionary actionDictionary, COSDocument cDoc) throws ValidationException { List<AbstractActionManager> result = new ArrayList<AbstractActionManager>(0); Map<COSObjectKey, Boolean> alreadyCreated = new HashMap<COSObjectKey, Boolean>(); COSBase nextDict = actionDictionary.getDictionaryObject(ACTION_DICTIONARY_KEY_NEXT); if (nextDict != null) { if (COSUtils.isArray(nextDict, cDoc)) { COSArray array = COSUtils.getAsArray(nextDict, cDoc); // ---- Next may contains an array of Action dictionary for (int i = 0; i < array.size(); ++i) { callCreateAction(array.get(i), cDoc, result, alreadyCreated); } } else { // ---- Next field contains a Dictionary or a reference to a Dictionary callCreateAction(nextDict, cDoc, result, alreadyCreated); } } return result; } /** * Create an instance of ActionManager according to the value of the S entry. * If the type entry isn't Action, a ValidationException will be thrown. * * If the action type isn't authorized in a PDF/A file, an instance of * InvalidAction is returned. * * @param action * the action dictionary used to instantiate the ActionManager * @param cDoc * the COSDocument which contains the action * @param isAA * the Action identifier if it is an additional action * @return * @throws ValidationException */ protected AbstractActionManager createActionManager(COSDictionary action, COSDocument cDoc, String aaKey) throws ValidationException { String type = action.getNameAsString(DICTIONARY_KEY_TYPE); if (type != null && !ACTION_DICTIONARY_VALUE_TYPE.equals(type)) { throw new ValidationException( "The given dictionary isn't the dictionary of an Action"); } // ---- S is a mandatory fields. If S entry is missing, the return will // return the InvalidAction manager String s = action.getNameAsString(ACTION_DICTIONARY_KEY_S); // --- Here is authorized actions if (ACTION_DICTIONARY_VALUE_ATYPE_GOTO.equals(s)) { return new GoToAction(this, action, cDoc, aaKey); } if (ACTION_DICTIONARY_VALUE_ATYPE_GOTOR.equals(s)) { return new GoToRemoteAction(this, action, cDoc, aaKey); } if (ACTION_DICTIONARY_VALUE_ATYPE_THREAD.equals(s)) { return new ThreadAction(this, action, cDoc, aaKey); } if (ACTION_DICTIONARY_VALUE_ATYPE_URI.equals(s)) { return new UriAction(this, action, cDoc, aaKey); } if (ACTION_DICTIONARY_VALUE_ATYPE_HIDE.equals(s)) { return new HideAction(this, action, cDoc, aaKey); } if (ACTION_DICTIONARY_VALUE_ATYPE_NAMED.equals(s)) { return new NamedAction(this, action, cDoc, aaKey); } if (ACTION_DICTIONARY_VALUE_ATYPE_SUBMIT.equals(s)) { return new SubmitAction(this, action, cDoc, aaKey); } // --- Here is forbidden actions if (ACTION_DICTIONARY_VALUE_ATYPE_LAUNCH.equals(s) || ACTION_DICTIONARY_VALUE_ATYPE_SOUND.equals(s) || ACTION_DICTIONARY_VALUE_ATYPE_MOVIE.equals(s) || ACTION_DICTIONARY_VALUE_ATYPE_RESET.equals(s) || ACTION_DICTIONARY_VALUE_ATYPE_IMPORT.equals(s) || ACTION_DICTIONARY_VALUE_ATYPE_JAVASCRIPT.equals(s) || ACTION_DICTIONARY_VALUE_ATYPE_SETSTATE.equals(s) || ACTION_DICTIONARY_VALUE_ATYPE_NOOP.equals(s)) { return new InvalidAction(this, action, cDoc, aaKey,s); } // ---- The default ActionManager is the undefined one. // Actions defined in a PDF Reference greater than 1.4 will be considered as // Undefined actions, here the list of new actions until the PDF 1.6 : // # GoToE (1.6) : Not PDF/A, uses EmbeddedFiles. // # SetOCGState (1.5) : Not PDF/A, uses optional content. // # Rendition (1.5) : Not PDF/A, use multimedia content. // # Trans (1.5) : ?? // # GoTo3DView (1.6) : ?? return new UndefAction(this, action, cDoc, aaKey, s); } }