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 }