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}