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