    001package org.apache.turbine.services.intake;
    002
    003
    004/*
    005 * Licensed to the Apache Software Foundation (ASF) under one
    006 * or more contributor license agreements.  See the NOTICE file
    007 * distributed with this work for additional information
    008 * regarding copyright ownership.  The ASF licenses this file
    009 * to you under the Apache License, Version 2.0 (the
    010 * "License"); you may not use this file except in compliance
    011 * with the License.  You may obtain a copy of the License at
    012 *
    013 *   http://www.apache.org/licenses/LICENSE-2.0
    014 *
    015 * Unless required by applicable law or agreed to in writing,
    016 * software distributed under the License is distributed on an
    017 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    018 * KIND, either express or implied.  See the License for the
    019 * specific language governing permissions and limitations
    020 * under the License.
    021 */
    022
    023
    024import java.util.HashMap;
    025import java.util.List;
    026import java.util.Map;
    027
    028import org.apache.commons.logging.Log;
    029import org.apache.commons.logging.LogFactory;
    030import org.apache.fulcrum.intake.IntakeException;
    031import org.apache.fulcrum.intake.IntakeServiceFacade;
    032import org.apache.fulcrum.intake.Retrievable;
    033import org.apache.fulcrum.intake.model.Group;
    034import org.apache.fulcrum.parser.ValueParser;
    035import org.apache.fulcrum.pool.Recyclable;
    036import org.apache.turbine.services.pull.ApplicationTool;
    037import org.apache.turbine.util.RunData;
    038
    039
    040/**
    041 * The main class through which Intake is accessed.  Provides easy access
    042 * to the Fulcrum Intake component.
    043 *
    044 * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
    045 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    046 * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
    047 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
    048 * @version $Id: IntakeTool.java 1706239 2015-10-01 13:18:35Z tv $
    049 */
    050public class IntakeTool
    051        implements ApplicationTool, Recyclable
    052{
    053    /** Used for logging */
    054    protected static final Log log = LogFactory.getLog(IntakeTool.class);
    055
    056    /** Constant for default key */
    057    public static final String DEFAULT_KEY = "_0";
    058
    059    /** Constant for the hidden fieldname */
    060    public static final String INTAKE_GRP = "intake-grp";
    061
    062    /** Groups from intake.xml */
    063    protected HashMap<String, Group> groups;
    064
    065    /** ValueParser instance */
    066    protected ValueParser pp;
    067
    068    private final HashMap<String, Group> declaredGroups = new HashMap<String, Group>();
    069    private final StringBuilder allGroupsSB = new StringBuilder(256);
    070    private final StringBuilder groupSB = new StringBuilder(128);
    071
    072    /** The cache of PullHelpers. **/
    073    private final Map<String, IntakeTool.PullHelper> pullMap;
    074
    075    /**
    076     * Constructor
    077     */
    078    public IntakeTool()
    079    {
    080        String[] groupNames = IntakeServiceFacade.getGroupNames();
    081        int groupCount = 0;
    082        if (groupNames != null)
    083        {
    084            groupCount = groupNames.length;
    085        }
    086        groups = new HashMap<String, Group>((int) (1.25 * groupCount + 1));
    087        pullMap = new HashMap<String, IntakeTool.PullHelper>((int) (1.25 * groupCount + 1));
    088
    089        for (int i = groupCount - 1; i >= 0; i--)
    090        {
    091            pullMap.put(groupNames[i], new PullHelper(groupNames[i]));
    092        }
    093    }
    094
    095    /**
    096     * Prepares intake for a single request
    097     */
    098    @Override
    099    public void init(Object runData)
    100    {
    101        this.pp = ((RunData) runData).getParameters();
    102
    103        String[] groupKeys = pp.getStrings(INTAKE_GRP);
    104        String[] groupNames = null;
    105        if (groupKeys == null || groupKeys.length == 0)
    106        {
    107            groupNames = IntakeServiceFacade.getGroupNames();
    108        }
    109        else
    110        {
    111            groupNames = new String[groupKeys.length];
    112            for (int i = groupKeys.length - 1; i >= 0; i--)
    113            {
    114                groupNames[i] = IntakeServiceFacade.getGroupName(groupKeys[i]);
    115            }
    116
    117        }
    118
    119        for (int i = groupNames.length - 1; i >= 0; i--)
    120        {
    121            try
    122            {
    123                List<Group> foundGroups = IntakeServiceFacade.getGroup(groupNames[i])
    124                    .getObjects(pp);
    125
    126                if (foundGroups != null)
    127                {
    128                    for (Group group : foundGroups)
    129                    {
    130                        groups.put(group.getObjectKey(), group);
    131                    }
    132                }
    133            }
    134            catch (IntakeException e)
    135            {
    136                log.error(e);
    137            }
    138        }
    139    }
    140
    141    /**
    142     * Add all registered group ids to the value parser
    143     *
    144     * @param vp the value parser
    145     */
    146    public void addGroupsToParameters(ValueParser vp)
    147    {
    148        for (Group group : groups.values())
    149        {
    150            if (!declaredGroups.containsKey(group.getIntakeGroupName()))
    151            {
    152                declaredGroups.put(group.getIntakeGroupName(), null);
    153                vp.add("intake-grp", group.getGID());
    154            }
    155            vp.add(group.getGID(), group.getOID());
    156        }
    157        declaredGroups.clear();
    158    }
    159
    160    /**
    161     * A convenience method to write out the hidden form fields
    162     * that notify intake of the relevant groups.  It should be used
    163     * only in templates with 1 form.  In multiform templates, the groups
    164     * that are relevant for each form need to be declared using
    165     * $intake.newForm() and $intake.declareGroup($group) for the relevant
    166     * groups in the form.
    167     *
    168     * @return the HTML that declares all groups to Intake in hidden input fields
    169     *
    170     */
    171    public String declareGroups()
    172    {
    173        allGroupsSB.setLength(0);
    174        for (Group group : groups.values())
    175        {
    176            declareGroup(group, allGroupsSB);
    177        }
    178        return allGroupsSB.toString();
    179    }
    180
    181    /**
    182     * A convenience method to write out the hidden form fields
    183     * that notify intake of the group.
    184     *
    185     * @param group the group to declare
    186     * @return the HTML that declares the group to Intake in a hidden input field
    187     */
    188    public String declareGroup(Group group)
    189    {
    190        groupSB.setLength(0);
    191        declareGroup(group, groupSB);
    192        return groupSB.toString();
    193    }
    194
    195    /**
    196     * xhtml valid hidden input field(s) that notifies intake of the
    197     * group's presence.
    198     * @param group the group to declare
    199     * @param sb a String Builder where the hidden field HTML will be appended
    200     */
    201    public void declareGroup(Group group, StringBuilder sb)
    202    {
    203        if (!declaredGroups.containsKey(group.getIntakeGroupName()))
    204        {
    205            declaredGroups.put(group.getIntakeGroupName(), null);
    206            sb.append("<input type=\"hidden\" name=\"")
    207                    .append(INTAKE_GRP)
    208                    .append("\" value=\"")
    209                    .append(group.getGID())
    210                    .append("\"/>\n");
    211        }
    212        group.appendHtmlFormInput(sb);
    213    }
    214
    215    /**
    216     * Declare that a new form starts
    217     */
    218    public void newForm()
    219    {
    220        declaredGroups.clear();
    221        for (Group group : groups.values())
    222        {
    223            group.resetDeclared();
    224        }
    225    }
    226
    227    /**
    228     * Implementation of ApplicationTool interface is not needed for this
    229     * tool as it is request scoped
    230     */
    231    @Override
    232    public void refresh()
    233    {
    234        // empty
    235    }
    236
    237    /**
    238     * Inner class to present a nice interface to the template designer
    239     */
    240    public class PullHelper
    241    {
    242        /** Name of the group used by the pull helper */
    243        String groupName;
    244
    245        /**
    246         * Protected constructor to force use of factory method.
    247         *
    248         * @param groupName
    249         */
    250        protected PullHelper(String groupName)
    251        {
    252            this.groupName = groupName;
    253        }
    254
    255        /**
    256         * Populates the object with the default values from the XML File
    257         *
    258         * @return a Group object with the default values
    259         * @throws IntakeException
    260         */
    261        public Group getDefault()
    262                throws IntakeException
    263        {
    264            return setKey(DEFAULT_KEY);
    265        }
    266
    267        /**
    268         * Calls setKey(key,true)
    269         *
    270         * @param key
    271         * @return an Intake Group
    272         * @throws IntakeException
    273         */
    274        public Group setKey(String key)
    275                throws IntakeException
    276        {
    277            return setKey(key, true);
    278        }
    279
    280        /**
    281         *
    282         * @param key
    283         * @param create
    284         * @return an Intake Group
    285         * @throws IntakeException
    286         */
    287        public Group setKey(String key, boolean create)
    288                throws IntakeException
    289        {
    290            Group g = null;
    291
    292            String inputKey = IntakeServiceFacade.getGroupKey(groupName) + key;
    293            if (groups.containsKey(inputKey))
    294            {
    295                g = groups.get(inputKey);
    296            }
    297            else if (create)
    298            {
    299                g = IntakeServiceFacade.getGroup(groupName);
    300                groups.put(inputKey, g);
    301                g.init(key, pp);
    302            }
    303
    304            return g;
    305        }
    306
    307        /**
    308         * maps an Intake Group to the values from a Retrievable object.
    309         *
    310         * @param obj A retrievable object
    311         * @return an Intake Group
    312         */
    313        public Group mapTo(Retrievable obj)
    314        {
    315            Group g = null;
    316
    317            try
    318            {
    319                String inputKey = IntakeServiceFacade.getGroupKey(groupName)
    320                        + obj.getQueryKey();
    321                if (groups.containsKey(inputKey))
    322                {
    323                    g = groups.get(inputKey);
    324                }
    325                else
    326                {
    327                    g = IntakeServiceFacade.getGroup(groupName);
    328                    groups.put(inputKey, g);
    329                }
    330
    331                return g.init(obj);
    332            }
    333            catch (IntakeException e)
    334            {
    335                log.error(e);
    336            }
    337
    338            return null;
    339        }
    340    }
    341
    342    /**
    343     * get a specific group
    344     * @param groupName the name of the group
    345     * @return a {@link PullHelper} wrapper around the group
    346     */
    347    public PullHelper get(String groupName)
    348    {
    349        return pullMap.get(groupName);
    350    }
    351
    352    /**
    353     * Get a specific group
    354     *
    355     * @param groupName the name of the group
    356     * @param throwExceptions if false, exceptions will be suppressed.
    357     * @return a {@link PullHelper} wrapper around the group
    358     * @throws IntakeException could not retrieve group
    359     */
    360    public PullHelper get(String groupName, boolean throwExceptions)
    361            throws IntakeException
    362    {
    363        return pullMap.get(groupName);
    364    }
    365
    366    /**
    367     * Loops through all of the Groups and checks to see if
    368     * the data within the Group is valid.
    369     * @return true if all groups are valid
    370     */
    371    public boolean isAllValid()
    372    {
    373        boolean allValid = true;
    374        for (Group group : groups.values())
    375        {
    376            allValid &= group.isAllValid();
    377        }
    378        return allValid;
    379    }
    380
    381    /**
    382     * Get a specific group by name and key.
    383     * @param groupName the name of the group
    384     * @param key the key for the group
    385     * @return the {@link Group}
    386     * @throws IntakeException if the group could not be retrieved
    387     */
    388    public Group get(String groupName, String key)
    389            throws IntakeException
    390    {
    391        return get(groupName, key, true);
    392    }
    393
    394    /**
    395     * Get a specific group by name and key. Also specify
    396     * whether or not you want to create a new group.
    397     * @param groupName the name of the group
    398     * @param key the key for the group
    399     * @param create true if a new group should be created
    400     * @return the {@link Group}
    401     * @throws IntakeException if the group could not be retrieved
    402     */
    403    public Group get(String groupName, String key, boolean create)
    404            throws IntakeException
    405    {
    406        if (groupName == null)
    407        {
    408            throw new IntakeException("IntakeServiceFacade.get: groupName == null");
    409        }
    410        if (key == null)
    411        {
    412            throw new IntakeException("IntakeServiceFacade.get: key == null");
    413        }
    414
    415        PullHelper ph = get(groupName);
    416        return (ph == null) ? null : ph.setKey(key, create);
    417    }
    418
    419    /**
    420     * Removes group.  Primary use is to remove a group that has
    421     * been processed by an action and is no longer appropriate
    422     * in the view (screen).
    423     * @param group the group instance to remove
    424     */
    425    public void remove(Group group)
    426    {
    427        if (group != null)
    428        {
    429            groups.remove(group.getObjectKey());
    430            group.removeFromRequest();
    431
    432            String[] groupKeys = pp.getStrings(INTAKE_GRP);
    433
    434            pp.remove(INTAKE_GRP);
    435
    436                        if (groupKeys != null)
    437                        {
    438                        for (int i = 0; i < groupKeys.length; i++)
    439                        {
    440                            if (!groupKeys[i].equals(group.getGID()))
    441                            {
    442                                 pp.add(INTAKE_GRP, groupKeys[i]);
    443                            }
    444                }
    445                    }
    446
    447            try
    448            {
    449                IntakeServiceFacade.releaseGroup(group);
    450            }
    451            catch (IntakeException ie)
    452            {
    453                log.error("Tried to release unknown group "
    454                        + group.getIntakeGroupName());
    455            }
    456        }
    457    }
    458
    459    /**
    460     * Removes all groups.  Primary use is to remove groups that have
    461     * been processed by an action and are no longer appropriate
    462     * in the view (screen).
    463     */
    464    public void removeAll()
    465    {
    466        Object[] allGroups = groups.values().toArray();
    467        for (int i = allGroups.length - 1; i >= 0; i--)
    468        {
    469            Group group = (Group) allGroups[i];
    470            remove(group);
    471        }
    472    }
    473
    474    /**
    475     * Get a Map containing all the groups.
    476     *
    477     * @return the Group Map
    478     */
    479    public Map<String, Group> getGroups()
    480    {
    481        return groups;
    482    }
    483
    484    // ****************** Recyclable implementation ************************
    485
    486    private boolean disposed;
    487
    488    /**
    489     * Recycles the object for a new client. Recycle methods with
    490     * parameters must be added to implementing object and they will be
    491     * automatically called by pool implementations when the object is
    492     * taken from the pool for a new client. The parameters must
    493     * correspond to the parameters of the constructors of the object.
    494     * For new objects, constructors can call their corresponding recycle
    495     * methods whenever applicable.
    496     * The recycle methods must call their super.
    497     */
    498    @Override
    499    public void recycle()
    500    {
    501        disposed = false;
    502    }
    503
    504    /**
    505     * Disposes the object after use. The method is called
    506     * when the object is returned to its pool.
    507     * The dispose method must call its super.
    508     */
    509    @Override
    510    public void dispose()
    511    {
    512        for (Group group : groups.values())
    513        {
    514            try
    515            {
    516                IntakeServiceFacade.releaseGroup(group);
    517            }
    518            catch (IntakeException ie)
    519            {
    520                log.error("Tried to release unknown group "
    521                        + group.getIntakeGroupName());
    522            }
    523        }
    524
    525        groups.clear();
    526        declaredGroups.clear();
    527        pp = null;
    528
    529        disposed = true;
    530    }
    531
    532    /**
    533     * Checks whether the recyclable has been disposed.
    534     *
    535     * @return true, if the recyclable is disposed.
    536     */
    537    @Override
    538    public boolean isDisposed()
    539    {
    540        return disposed;
    541    }
    542}