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}