001    /*
002     * Databinder: a simple bridge from Wicket to Hibernate
003     * Copyright (C) 2007  Nathan Hamblen nathan@technically.us
004     * Copyright (C) 2007  xoocode.org project
005    
006     * This library is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 2.1 of the License, or (at your option) any later version.
010     * 
011     * This library is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     * 
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with this library; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
019     */
020    package net.databinder.components.hib;
021    
022    import java.io.Serializable;
023    import java.util.ArrayList;
024    import java.util.Iterator;
025    import java.util.List;
026    
027    import net.databinder.hib.Databinder;
028    import net.databinder.models.hib.HibernateObjectModel;
029    
030    import org.apache.wicket.Component;
031    import org.apache.wicket.ajax.AjaxRequestTarget;
032    import org.apache.wicket.ajax.markup.html.form.AjaxButton;
033    import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
034    import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
035    import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar;
036    import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
037    import org.apache.wicket.extensions.markup.html.repeater.data.table.NavigationToolbar;
038    import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
039    import org.apache.wicket.markup.html.WebMarkupContainer;
040    import org.apache.wicket.markup.html.basic.Label;
041    import org.apache.wicket.markup.html.form.Form;
042    import org.apache.wicket.markup.html.form.TextArea;
043    import org.apache.wicket.markup.html.panel.Panel;
044    import org.apache.wicket.markup.repeater.Item;
045    import org.apache.wicket.markup.repeater.data.IDataProvider;
046    import org.apache.wicket.model.BoundCompoundPropertyModel;
047    import org.apache.wicket.model.CompoundPropertyModel;
048    import org.apache.wicket.model.IModel;
049    import org.apache.wicket.model.Model;
050    import org.apache.wicket.model.PropertyModel;
051    import org.apache.wicket.util.string.Strings;
052    import org.hibernate.Query;
053    import org.hibernate.QueryException;
054    import org.hibernate.Session;
055    import org.hibernate.SessionFactory;
056    import org.hibernate.metadata.ClassMetadata;
057    import org.hibernate.type.Type;
058    
059    /**
060     * A Panel used to display a textarea to enter an HQL query and execute it
061     * against the current session of a {@link SessionFactory}.
062     * <p>
063     * The panel result is displayed in a data table, where columns are created
064     * according to the query.
065     * <p>
066     * For instance, a query like:
067     * <pre>
068     * select job.name as name, job.id as id from JobModel job
069     * </pre>
070     * will result in two columns, 'name' and 'id', in the result data table.
071     * <p>
072     * If you run:
073     * <pre>
074     * from JobModel
075     * </pre>
076     * the columns in the result table will be the available properties of a 
077     * JobModel
078     */
079    public class QueryPanel extends Panel {
080            private static final long serialVersionUID = 1L;
081            
082            /**
083             * Bean used to store the query
084             */
085            private QueryBean query = new QueryBean();
086            /**
087             * Stores information about the query execution (executed query, time, ...)
088             */
089            private String executionInfo;
090            
091            /**
092             * Constructs an {@link QueryPanel}
093             * @param id 
094             *                      the panel identifier. Must not be null.
095             */
096            public QueryPanel(String id) {
097                    super(id);
098    
099                    final WebMarkupContainer resultsHolder = new WebMarkupContainer("resultsHolder");
100                    resultsHolder.add(new Label("executionInfo", new PropertyModel(this, "executionInfo")));
101                    resultsHolder.add(getResultsTable());
102                    resultsHolder.setOutputMarkupId(true);
103                    add(resultsHolder);
104                    
105                    Form form = new Form("form", new CompoundPropertyModel(query));
106                    form.setOutputMarkupId(true);
107                    form.add(new TextArea("query"));
108                    form.add(new AjaxButton("submit", form) {
109                            private static final long serialVersionUID = 1L;
110    
111                            protected void onSubmit(AjaxRequestTarget target, Form form)
112                            {
113                                    if (resultsHolder.get("results") != null) {
114                                            resultsHolder.remove("results");
115                                    }
116                                    try {
117                                            resultsHolder.add(getResultsTable());
118                                    } catch (QueryException e) {
119                                            note(e);
120                                    } catch (IllegalArgumentException e) {
121                                            note(e);
122                                    } catch (IllegalStateException e) {
123                                            note(e);
124                                    }
125                                    target.addComponent(resultsHolder);
126                            }
127                            private void note(Exception e) {
128                                    resultsHolder.add(new Label("results", 
129                                                    e.getClass().getSimpleName()+ ": " + e.getMessage()));
130                            }
131                    });
132                    add(form);
133            }
134    
135            /**
136             * Creates a result table for the current query.
137             * @return a result table, or an empty label if there is no current query
138             */
139            private Component getResultsTable() {
140                    if (Strings.isEmpty(query.getQuery())) {
141                            return new Label("results", "");
142                    } else {
143                            IDataProvider dataProvider = new IDataProvider() {
144                                    private static final long serialVersionUID = 1L;
145    
146                                    public void detach() {
147                                    }
148                            
149                                    public int size() {
150                                            Session sess = Databinder.getHibernateSession();
151                                            Query query = sess.createQuery(getQuery());
152                                            return query.list().size();
153                                    }
154                                    
155                                    public String getQuery() {
156                                            return QueryPanel.this.query.getQuery();
157                                    }
158                            
159                                    public IModel model(Object object) {
160                                            return new BoundCompoundPropertyModel(new HibernateObjectModel(object));
161                                    }
162                            
163                                    public Iterator iterator(int first, int count) {
164                                            Session sess =  Databinder.getHibernateSession();
165                                            long start = System.nanoTime();
166                                            try {
167                                                    Query q = sess.createQuery(getQuery());
168                                                    q.setFirstResult(first);
169                                                    q.setMaxResults(count);
170                                                    return q.iterate();
171                                            } finally {
172                                                    float nanoTime = ((System.nanoTime()-start) / 1000) / 1000.0f;
173                                                    setExecutionInfo("query executed in "+nanoTime+" ms: "+getQuery());
174                                            }
175                                    }
176                            };
177                            IColumn[] columns;
178                            Session sess =  Databinder.getHibernateSession();
179                            Query q = sess.createQuery(query.getQuery());
180                            String[] aliases;
181                            Type[] returnTypes;
182                            try {
183                                    aliases = q.getReturnAliases();
184                                    returnTypes = q.getReturnTypes();
185                            } catch (NullPointerException e) { // thrown on updates
186                                    return new Label("results", "");
187                            }
188                            
189                            if (returnTypes.length != 1) {
190                                    columns = new IColumn[returnTypes.length];
191                                    for (int i = 0; i < returnTypes.length; i++) {
192                                            String alias = aliases == null || aliases.length <= i?returnTypes[i].getName():aliases[i];
193                                            final int index = i;
194                                            columns[i] = new AbstractColumn(new Model(alias)) {
195                                                    private static final long serialVersionUID = 1L;
196    
197                                                    public void populateItem(Item cellItem, String componentId, IModel rowModel) {
198                                                            Object[] objects = (Object[]) rowModel.getObject();
199                                                            cellItem.add(new Label(componentId, 
200                                                                            new Model(objects[index]==null?"":objects[index].toString())));
201                                                    }
202                                            };
203                                    }
204                            } else {
205                                    Type returnType = returnTypes[0];
206                                    if (returnType.isEntityType()) {
207                                            Class clss = returnType.getReturnedClass();
208                                            ClassMetadata metadata = Databinder.getHibernateSessionFactory().getClassMetadata(clss);
209                                            List<IColumn> cols = new ArrayList<IColumn>();
210                                            String idProp = metadata.getIdentifierPropertyName();
211                                            cols.add(new PropertyColumn(new Model(idProp), idProp));
212                                            String[] properties = metadata.getPropertyNames();
213                                            for (String prop : properties) {
214                                                    Type type = metadata.getPropertyType(prop);
215                                                    if (type.isCollectionType()) {
216                                                            // TODO: see if we could provide a link to the collection value
217                                                    } else {
218                                                            cols.add(new PropertyColumn(new Model(prop), prop));
219                                                    }
220                                            }
221                                            columns = (IColumn[]) cols.toArray(new IColumn[cols.size()]);
222                                    } else {
223                                            String alias = aliases == null || aliases.length == 0?returnType.getName():aliases[0];
224                                            columns = new IColumn[] {new AbstractColumn(new Model(alias)) {
225                                                    private static final long serialVersionUID = 1L;
226    
227                                                    public void populateItem(Item cellItem, String componentId, IModel rowModel) {
228                                                            cellItem.add(new Label(componentId, rowModel));
229                                                    }
230                                            }};
231                                    }
232                            }
233                            DataTable dataTable = new DataTable("results", columns, dataProvider, 10);
234                            
235                            dataTable.addTopToolbar(new HeadersToolbar(dataTable, null));
236                            dataTable.addBottomToolbar(new NavigationToolbar(dataTable));
237                            dataTable.setOutputMarkupId(true);
238                            return dataTable;
239                    }
240            }
241            
242            private static class QueryBean implements Serializable {
243                    private static final long serialVersionUID = 1L;
244                    private String query;
245    
246                    public String getQuery() {
247                            return query;
248                    }
249    
250                    public void setQuery(String query) {
251                            this.query = query;
252                    }
253            }
254    
255            public String getExecutionInfo() {
256                    return executionInfo;
257            }
258    
259            public void setExecutionInfo(String executionInfo) {
260                    this.executionInfo = executionInfo;
261            }
262    
263    }