Repository: incubator-juneau Updated Branches: refs/heads/master 24209d4b8 -> c79850318
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-core/src/main/java/org/apache/juneau/utils/CalendarUtils.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/utils/CalendarUtils.java b/juneau-core/src/main/java/org/apache/juneau/utils/CalendarUtils.java new file mode 100644 index 0000000..6b72fdf --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/utils/CalendarUtils.java @@ -0,0 +1,697 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.utils; + +import static org.apache.juneau.internal.DateUtils.*; + +import java.text.*; +import java.util.*; +import java.util.concurrent.*; + +import javax.xml.bind.*; + +import org.apache.juneau.internal.*; + +/** + * Utility class for converting {@link Calendar} and {@link Date} objects to common serialized forms. + */ +public class CalendarUtils { + + private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + + /** + * Valid conversion formats. + */ + public static enum Format { + + /** + * Transform to ISO8601 date-time-local strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"2001-07-04T15:30:45"</js> + * </ul> + * + * <h6 class='topic'>Example input:</h6> + * <ul> + * <li><js>"2001-07-04T15:30:45"</js> + * <li><js>"2001-07-04T15:30:45.1"</js> + * <li><js>"2001-07-04T15:30"</js> + * <li><js>"2001-07-04"</js> + * <li><js>"2001-07"</js> + * <li><js>"2001"</js> + * </ul> + */ + ISO8601_DTL, + + /** + * Transform to ISO8601 date-time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"2001-07-04T15:30:45-05:00"</js> + * <li><js>"2001-07-04T15:30:45Z"</js> + * </ul> + * + * <h6 class='topic'>Example input:</h6> + * <ul> + * <li><js>"2001-07-04T15:30:45-05:00"</js> + * <li><js>"2001-07-04T15:30:45Z"</js> + * <li><js>"2001-07-04T15:30:45.1Z"</js> + * <li><js>"2001-07-04T15:30Z"</js> + * <li><js>"2001-07-04"</js> + * <li><js>"2001-07"</js> + * <li><js>"2001"</js> + * </ul> + */ + ISO8601_DT, + + /** + * Same as {@link CalendarUtils.Format#ISO8601_DT}, except always serializes in GMT. + * + * <h5 class='section'>Example output:</h5> + * <js>"2001-07-04T15:30:45Z"</js> + */ + ISO8601_DTZ, + + /** + * Same as {@link CalendarUtils.Format#ISO8601_DT} except serializes to millisecond precision. + * + * <h5 class='section'>Example output:</h5> + * <js>"2001-07-04T15:30:45.123Z"</js> + */ + ISO8601_DTP, + + /** + * Same as {@link CalendarUtils.Format#ISO8601_DTZ} except serializes to millisecond precision. + * + * <h5 class='section'>Example output:</h5> + * <js>"2001-07-04T15:30:45.123"</js> + */ + ISO8601_DTPZ, + + /** + * ISO8601 date only. + * + * <h5 class='section'>Example output:</h5> + * <js>"2001-07-04"</js> + */ + ISO8601_D, + + /** + * Transform to {@link String Strings} using the {@code Date.toString()} method. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"Wed Jul 04 15:30:45 EST 2001"</js> + * </ul> + */ + TO_STRING, + + /** + * Transform to RFC2822 date-time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"Sat, 03 Mar 2001 10:11:12 +0000"</js> <jc>// en_US</jc> + * <li><js>"å, 03 3 2001 10:11:12 +0000"</js> <jc>// ja_JP</jc> + * <li><js>"í , 03 3ì 2001 10:11:12 +0000"</js> <jc>// ko_KR</jc> + * </ul> + */ + RFC2822_DT, + + /** + * Same as {@link CalendarUtils.Format#RFC2822_DT}, except always serializes in GMT. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"Sat, 03 Mar 2001 10:11:12 GMT"</js> <jc>// en_US</jc> + * <li><js>"å, 03 3 2001 10:11:12 GMT"</js> <jc>// ja_JP</jc> + * <li><js>"í , 03 3ì 2001 10:11:12 GMT"</js> <jc>// ko_KR</jc> + * </ul> + */ + RFC2822_DTZ, + + /** + * Transform to RFC2822 date strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"03 Mar 2001"</js> <jc>// en_US</jc> + * <li><js>"03 3 2001"</js> <jc>// ja_JP</jc> + * <li><js>"03 3ì 2001"</js> <jc>// ko_KR</jc> + * </ul> + */ + RFC2822_D, + + /** + * Transform to simple <js>"yyyy/MM/dd HH:mm:ss"</js> date-time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"2001/03/03 10:11:12"</js> + * </ul> + */ + SIMPLE_DT, + + /** + * Transform to simple <js>"yyyy/MM/dd"</js> date strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"2001/03/03"</js> + * </ul> + */ + SIMPLE_D, + + /** + * Transform to simple <js>"HH:mm:ss"</js> time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"10:11:12"</js> + * </ul> + */ + SIMPLE_T, + + /** + * Transform to {@link DateFormat#FULL} date strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"Saturday, March 3, 2001"</js> <jc>// en_US</jc> + * <li><js>"2001å¹´3æ3æ¥"</js> <jc>// ja_JP</jc> + * <li><js>"2001ë 3ì 3ì¼ í ìì¼"</js> <jc>// ko_KR</jc> + * </ul> + */ + FULL_D, + + /** + * Transform to {@link DateFormat#LONG} date strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"March 3, 2001"</js> <jc>// en_US</jc> + * <li><js>"2001/03/03"</js> <jc>// ja_JP</jc> + * <li><js>"2001ë 3ì 3ì¼ (í )"</js> <jc>// ko_KR</jc> + * </ul> + */ + LONG_D, + + /** + * Transform to {@link DateFormat#MEDIUM} date strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"Mar 3, 2001"</js> <jc>// en_US</jc> + * <li><js>"2001/03/03"</js> <jc>// ja_JP</jc> + * <li><js>"2001. 3. 3"</js> <jc>// ko_KR</jc> + * </ul> + */ + MEDIUM_D, + + /** + * Transform to {@link DateFormat#SHORT} date strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"3/3/01"</js> <jc>// en_US</jc> + * <li><js>"01/03/03"</js> <jc>// ja_JP</jc> + * <li><js>"01. 3. 3"</js> <jc>// ko_KR</jc> + * </ul> + */ + SHORT_D, + + /** + * Transform to {@link DateFormat#FULL} time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"10:11:12 AM GMT"</js> <jc>// en_US</jc> + * <li><js>"10æ11å12ç§ GMT"</js> <jc>// ja_JP</jc> + * <li><js>"ì¤ì 10ì 11ë¶ 12ì´ GMT"</js> <jc>// ko_KR</jc> + * </ul> + */ + FULL_T, + + /** + * Transform to {@link DateFormat#LONG} time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"10:11:12 AM GMT"</js> <jc>// en_US</jc> + * <li><js>"10:11:12 GMT"</js> <jc>// ja_JP</jc> + * <li><js>"ì¤ì 10ì 11ë¶ 12ì´"</js> <jc>// ko_KR</jc> + * </ul> + */ + LONG_T, + + /** + * Transform to {@link DateFormat#MEDIUM} time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"10:11:12 AM"</js> <jc>// en_US</jc> + * <li><js>"10:11:12"</js> <jc>// ja_JP</jc> + * <li><js>"ì¤ì 10:11:12"</js> <jc>// ko_KR</jc> + * </ul> + */ + MEDIUM_T, + + /** + * Transform to {@link DateFormat#SHORT} time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"10:11 AM"</js> <jc>// en_US</jc> + * <li><js>"10:11 AM"</js> <jc>// ja_JP</jc> + * <li><js>"ì¤ì 10:11"</js> <jc>// ko_KR</jc> + * </ul> + */ + SHORT_T, + + /** + * Transform to {@link DateFormat#FULL} date-time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"Saturday, March 3, 2001 10:11:12 AM GMT"</js> <jc>// en_US</jc> + * <li><js>"2001å¹´3æ3æ¥ 10æ11å12ç§ GMT"</js> <jc>// ja_JP</jc> + * <li><js>"2001ë 3ì 3ì¼ í ìì¼ ì¤ì 10ì 11ë¶ 12ì´ GMT"</js> <jc>// ko_KR</jc> + * </ul> + */ + FULL_DT, + + /** + * Transform to {@link DateFormat#LONG} date-time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"March 3, 2001 10:11:12 AM GMT"</js> <jc>// en_US</jc> + * <li><js>"2001/03/03 10:11:12 GMT"</js> <jc>// ja_JP</jc> + * <li><js>"2001ë 3ì 3ì¼ (í ) ì¤ì 10ì 11ë¶ 12ì´"</js> <jc>// ko_KR</jc> + * </ul> + */ + LONG_DT, + + /** + * Transform to {@link DateFormat#MEDIUM} date-time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"Mar 3, 2001 10:11:12 AM"</js> <jc>// en_US</jc> + * <li><js>"2001/03/03 10:11:12"</js> <jc>// ja_JP</jc> + * <li><js>"2001. 3. 3 ì¤ì 10:11:12"</js> <jc>// ko_KR</jc> + * </ul> + */ + MEDIUM_DT, + + /** + * Transform to {@link DateFormat#SHORT} date-time strings. + * + * <h5 class='section'>Example output:</h5> + * <ul> + * <li><js>"3/3/01 10:11 AM"</js> <jc>// en_US</jc> + * <li><js>"01/03/03 10:11"</js> <jc>// ja_JP</jc> + * <li><js>"01. 3. 3 ì¤ì 10:11"</js> <jc>// ko_KR</jc> + * </ul> + */ + SHORT_DT + } + + private static ThreadLocal<Map<DateFormatKey,DateFormat>> patternCache = new ThreadLocal<Map<DateFormatKey,DateFormat>>(); + + static class DateFormatKey { + final CalendarUtils.Format format; + final Locale locale; + final TimeZone timeZone; + final int hashCode; + + DateFormatKey(CalendarUtils.Format format, Locale locale, TimeZone timeZone) { + this.format = format; + this.locale = locale; + this.timeZone = timeZone; + this.hashCode = format.hashCode() + locale.hashCode() + timeZone.hashCode(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (o == null) + return false; + DateFormatKey key = (DateFormatKey)o; + return format.equals(key.format) && locale.equals(key.locale) && timeZone.equals(key.timeZone); + } + } + + + private static DateFormat getFormat(CalendarUtils.Format format, Locale locale, TimeZone timeZone) { + + if (locale == null) + locale = Locale.getDefault(); + + if (timeZone == null) + timeZone = TimeZone.getDefault(); + + DateFormatKey key = new DateFormatKey(format, locale, timeZone); + + Map<DateFormatKey,DateFormat> m1 = patternCache.get(); + if (m1 == null) { + m1 = new ConcurrentHashMap<DateFormatKey,DateFormat>(); + patternCache.set(m1); + } + + DateFormat df = m1.get(key); + + if (df == null) { + String p = null; + switch (format) { + case ISO8601_DTL: p = "yyyy-MM-dd'T'HH:mm:ss"; break; + case ISO8601_D: p = "yyyy-MM-dd"; break; + case TO_STRING: p = "EEE MMM dd HH:mm:ss zzz yyyy"; break; + case RFC2822_DT: p = "EEE, dd MMM yyyy HH:mm:ss Z"; break; + case RFC2822_DTZ: p = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; break; + case RFC2822_D: p = "dd MMM yyyy"; break; + case SIMPLE_DT: p = "yyyy/MM/dd HH:mm:ss"; break; + case SIMPLE_D: p = "yyyy/MM/dd"; break; + case SIMPLE_T: p = "HH:mm:ss"; break; + case FULL_D: df = DateFormat.getDateInstance(DateFormat.FULL, locale); break; + case LONG_D: df = DateFormat.getDateInstance(DateFormat.LONG, locale); break; + case MEDIUM_D: df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); break; + case SHORT_D: df = DateFormat.getDateInstance(DateFormat.SHORT, locale); break; + case FULL_T: df = DateFormat.getTimeInstance(DateFormat.FULL, locale); break; + case LONG_T: df = DateFormat.getTimeInstance(DateFormat.LONG, locale); break; + case MEDIUM_T: df = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale); break; + case SHORT_T: df = DateFormat.getTimeInstance(DateFormat.SHORT, locale); break; + case FULL_DT: df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, locale); break; + case LONG_DT: df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); break; + case MEDIUM_DT: df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale); break; + case SHORT_DT: df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); break; + default: return null; + } + if (p != null) { + df = new SimpleDateFormat(p, locale); + } + if (df != null) + df.setTimeZone(timeZone); + m1.put(key, df); + } + + return df; + } + + /** + * Converts the specified calendar to a string of the specified format. + * + * @param c The calendar to serialize. + * @param format The date format. + * @param locale The locale to use. If <jk>null</jk>, uses {@link Locale#getDefault()}. + * @param timeZone The time zone to use. If <jk>null</jk>, uses {@link TimeZone#getDefault()}. + * @return The serialized date, or <jk>null</jk> if the calendar was <jk>null</jk>. + * @throws Exception + */ + public static final String serialize(Calendar c, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { + if (c == null) + return null; + if (timeZone == null) + timeZone = c.getTimeZone(); + switch(format) { + case ISO8601_DTL: + case ISO8601_D: + case RFC2822_D: + case RFC2822_DT: + case TO_STRING: + case FULL_D: + case FULL_DT: + case FULL_T: + case LONG_D: + case LONG_DT: + case LONG_T: + case MEDIUM_D: + case MEDIUM_DT: + case MEDIUM_T: + case SHORT_D: + case SHORT_DT: + case SHORT_T: + case SIMPLE_D: + case SIMPLE_DT: + case SIMPLE_T: + return serializeFromDateFormat(c.getTime(), format, locale, timeZone); + case ISO8601_DT: + return DatatypeConverter.printDateTime(setTimeZone(c, timeZone)); + case ISO8601_DTP: + String s = DatatypeConverter.printDateTime(setTimeZone(c, timeZone)); + return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); + case ISO8601_DTZ: + if (c.getTimeZone().getRawOffset() != 0) { + Calendar c2 = Calendar.getInstance(GMT); + c2.setTime(c.getTime()); + c = c2; + } + return DatatypeConverter.printDateTime(c); + case ISO8601_DTPZ: + if (c.getTimeZone().getRawOffset() != 0) { + Calendar c2 = Calendar.getInstance(GMT); + c2.setTime(c.getTime()); + c = c2; + } + s = DatatypeConverter.printDateTime(c); + return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); + case RFC2822_DTZ: + return serializeFromDateFormat(c.getTime(), format, locale, GMT); + default: + break; + } + return null; + } + + /** + * Converts the specified date to a string of the specified format. + * + * @param format The date format. + * @param d The date to serialize. + * @param locale The locale to use. If <jk>null</jk>, uses {@link Locale#getDefault()}. + * @param timeZone The time zone to use. If <jk>null</jk>, uses {@link TimeZone#getDefault()}. + * @return The serialized date, or <jk>null</jk> if the calendar was <jk>null</jk>. + * @throws Exception + */ + public static final String serialize(Date d, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { + if (d == null) + return null; + if (timeZone == null) + timeZone = TimeZone.getDefault(); + switch(format) { + case ISO8601_DTL: + case ISO8601_D: + case RFC2822_D: + case RFC2822_DT: + case TO_STRING: + case FULL_D: + case FULL_DT: + case FULL_T: + case LONG_D: + case LONG_DT: + case LONG_T: + case MEDIUM_D: + case MEDIUM_DT: + case MEDIUM_T: + case SHORT_D: + case SHORT_DT: + case SHORT_T: + case SIMPLE_D: + case SIMPLE_DT: + case SIMPLE_T: + return serializeFromDateFormat(d, format, locale, timeZone); + case ISO8601_DT: + Calendar c = new GregorianCalendar(); + c.setTime(d); + c.setTimeZone(timeZone); + return DatatypeConverter.printDateTime(c); + case ISO8601_DTP: + c = new GregorianCalendar(); + c.setTime(d); + c.setTimeZone(timeZone); + String s = DatatypeConverter.printDateTime(setTimeZone(c, timeZone)); + return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); + case ISO8601_DTZ: + c = new GregorianCalendar(); + c.setTime(d); + c.setTimeZone(GMT); + return DatatypeConverter.printDateTime(c); + case ISO8601_DTPZ: + c = new GregorianCalendar(); + c.setTime(d); + c.setTimeZone(GMT); + s = DatatypeConverter.printDateTime(c); + return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); + case RFC2822_DTZ: + return serializeFromDateFormat(d, format, locale, GMT); + } + return null; + } + + + /** + * Converts the specified serialized date back into a {@link Calendar} object. + * + * @param format The date format. + * @param in The serialized date. + * @param locale + * The locale to use. + * If <jk>null</jk>, uses {@link Locale#getDefault()}. + * @param timeZone + * The timezone to assume if input string doesn't contain timezone info. + * If <jk>null</jk>, uses {@link TimeZone#getDefault()}. + * @return The date as a {@link Calendar}, or <jk>null</jk> if the input was <jk>null</jk> or empty. + * @throws Exception + */ + public static final Calendar parseCalendar(String in, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { + if (StringUtils.isEmpty(in)) + return null; + if (timeZone == null) + timeZone = TimeZone.getDefault(); + Date d = null; + switch(format) { + + // These use DatatypeConverter to parse the date. + case ISO8601_DTL: + case ISO8601_DT: + case ISO8601_DTZ: + case ISO8601_DTP: + case ISO8601_DTPZ: + case ISO8601_D: + return DatatypeConverter.parseDateTime(toValidISO8601DT(in)); + + // These don't specify timezones, so we have to assume the timezone is whatever is specified. + case RFC2822_D: + case SIMPLE_DT: + case SIMPLE_D: + case SIMPLE_T: + case FULL_D: + case LONG_D: + case MEDIUM_D: + case SHORT_D: + case MEDIUM_T: + case SHORT_T: + case MEDIUM_DT: + case SHORT_DT: + d = getFormat(format, locale, TimeZone.getDefault()).parse(in); + d.setTime(d.getTime() - timeZone.getRawOffset()); + break; + + // This is always in GMT. + case RFC2822_DTZ: + d = getFormat(format, locale, TimeZone.getDefault()).parse(in); + d.setTime(d.getTime() + TimeZone.getDefault().getRawOffset()); + break; + + // These specify timezones in the strings, so we don't use the specified timezone. + case TO_STRING: + case FULL_DT: + case FULL_T: + case LONG_DT: + case LONG_T: + case RFC2822_DT: + d = getFormat(format, locale, timeZone).parse(in); + break; + } + if (d == null) + return null; + Calendar c = new GregorianCalendar(); + c.setTime(d); + c.setTimeZone(timeZone); + return c; + } + + /** + * Converts the specified serialized date back into a {@link Date} object. + * + * @param format The date format. + * @param in The serialized date. + * @param locale + * The locale to use. + * If <jk>null</jk>, uses {@link Locale#getDefault()}. + * @param timeZone + * The timezone to assume if input string doesn't contain timezone info. + * If <jk>null</jk>, uses {@link TimeZone#getDefault()}. + * @return The date as a {@link Date}, or <jk>null</jk> if the input was <jk>null</jk> or empty. + * @throws Exception + */ + public static final Date parseDate(String in, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { + if (StringUtils.isEmpty(in)) + return null; + if (timeZone == null) + timeZone = TimeZone.getDefault(); + switch(format) { + + // These use DatatypeConverter to parse the date. + case ISO8601_DTL: + case ISO8601_D: + case ISO8601_DT: + case ISO8601_DTZ: + case ISO8601_DTP: + case ISO8601_DTPZ: + return DatatypeConverter.parseDateTime(toValidISO8601DT(in)).getTime(); + + // These don't specify timezones, so we have to assume the timezone is whatever is specified. + case FULL_D: + case LONG_D: + case MEDIUM_D: + case MEDIUM_DT: + case MEDIUM_T: + case RFC2822_D: + case SHORT_D: + case SHORT_DT: + case SHORT_T: + case SIMPLE_D: + case SIMPLE_DT: + case SIMPLE_T: + return getFormat(format, locale, timeZone).parse(in); + + // This is always in GMT. + case RFC2822_DTZ: + Date d = getFormat(format, locale, TimeZone.getDefault()).parse(in); + d.setTime(d.getTime() + TimeZone.getDefault().getRawOffset()); + return d; + + // These specify timezones in the strings, so we don't use the specified timezone. + case TO_STRING: + case FULL_DT: + case FULL_T: + case LONG_DT: + case LONG_T: + case RFC2822_DT: + return getFormat(format, locale, timeZone).parse(in); + + } + return null; + } + + private static String serializeFromDateFormat(Date date, CalendarUtils.Format format, Locale locale, TimeZone timeZone) { + DateFormat df = getFormat(format, locale, timeZone); + String s = df.format(date); + return s; + } + + private static Calendar setTimeZone(Calendar c, TimeZone tz) { + if (tz != null && ! tz.equals(c.getTimeZone())) { + c = (Calendar)c.clone(); + c.setTimeZone(tz); + } + return c; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-core/src/main/javadoc/overview.html ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/javadoc/overview.html b/juneau-core/src/main/javadoc/overview.html index 2f41325..e61feaf 100644 --- a/juneau-core/src/main/javadoc/overview.html +++ b/juneau-core/src/main/javadoc/overview.html @@ -6911,6 +6911,15 @@ } </p> This is mostly an internal change and doesn't affect the existing APIs. + <li> + {@link org.apache.juneau.dto.html5.HtmlElementMixed#children(Object...)} can now take in collections + of objects. + <li> + {@link org.apache.juneau.transform.PojoSwap#swap(BeanSession,Object)} and {@link org.apache.juneau.transform.PojoSwap#unswap(BeanSession,Object,ClassMeta)} + can now throw arbitrary exceptions instead of having to wrap them in <code>SerializeException</code>/<code>ParseException</code>. + <li> + New {@link org.apache.juneau.utils.CalendarUtils} class that encapsulates serialization/parsing logic from {@link org.apache.juneau.transforms.CalendarSwap} and + {@link org.apache.juneau.transforms.DateSwap}. </ul> <h6 class='topic'>org.apache.juneau.rest</h6> @@ -6928,6 +6937,10 @@ New setting: {@link org.apache.juneau.rest.RestContext#REST_resourceResolver}. <br>Allows you to specify a resource resolver on the servlet context to make it easier to work with dependency injection frameworks. + <li> + {@link org.apache.juneau.rest.widget.Widget} classes can now be defined as inner classes of servlets/resources. + <li> + New tooltip template: {@link org.apache.juneau.rest.widget.Tooltip}. </ul> <h6 class='topic'>org.apache.juneau.rest.microservice</h6> @@ -6941,6 +6954,14 @@ <li>{@link org.apache.juneau.microservice.RestMicroservice#getServletContextHandler() getServletContextHandler()} </ul> </ul> + + <h6 class='topic'>org.apache.juneau.rest.examples</h6> + <ul class='spaced-list'> + <li> + New example of adding a menu-item widget to the Pet Store resource (including tooltips): + <br><img src='doc-files/ReleaseNotes_632_PetStoreAdd.png'> + </ul> + </div> <!-- ======================================================================================================== --> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java ---------------------------------------------------------------------- diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java index c67371e..b5475b7 100644 --- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java +++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java @@ -12,6 +12,8 @@ // *************************************************************************************************************************** package org.apache.juneau.examples.rest; +import static org.apache.juneau.dto.html5.HtmlBuilder.*; + import java.util.*; import java.util.Map; @@ -23,6 +25,7 @@ import org.apache.juneau.json.*; import org.apache.juneau.microservice.*; import org.apache.juneau.rest.*; import org.apache.juneau.rest.annotation.*; +import org.apache.juneau.rest.annotation.Body; import org.apache.juneau.rest.converters.*; import org.apache.juneau.rest.widget.*; import org.apache.juneau.serializer.*; @@ -38,14 +41,16 @@ import org.apache.juneau.transforms.*; htmldoc=@HtmlDoc( widgets={ ContentTypeMenuItem.class, - StyleMenuItem.class + StyleMenuItem.class, + PetStoreResource.AddPet.class }, links={ "up: request:/..", "options: servlet:/?method=OPTIONS", "$W{ContentTypeMenuItem}", "$W{StyleMenuItem}", - "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java" + "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java", + "$W{AddPet}" }, aside={ "<div style='max-width:400px' class='text'>", @@ -105,6 +110,12 @@ public class PetStoreResource extends ResourceJena { return petDB.get(id); } + @RestMethod(name="POST", path="/") + public Redirect addPet(@Body Pet pet) throws Exception { + this.petDB.put(pet.id, pet); + return new Redirect("servlet:/"); + } + // Our bean class. public static class Pet { @@ -120,7 +131,7 @@ public class PetStoreResource extends ResourceJena { @BeanProperty(format="$%.2f") // Renders price in dollars. public float price; - @BeanProperty(swap=DateSwap.RFC2822D.class) // Renders dates in RFC2822 format. + @BeanProperty(swap=DateSwap.ISO8601D.class) // Renders dates in ISO8601 format. public Date birthDate; public int getAge() { @@ -145,5 +156,79 @@ public class PetStoreResource extends ResourceJena { return "background-color:#FDF2E9"; } } + + /** + * Renders the "ADD" menu item. + */ + public class AddPet extends MenuItemWidget { + + @Override + public String getLabel(RestRequest req) throws Exception { + return "add"; + } + + @Override + public Object getContent(RestRequest req) throws Exception { + return div( + form().id("form").action("servlet:/").method("POST").children( + table( + tr( + th("ID:"), + td(input().name("id").type("number").value(getNextAvailableId())), + td(new Tooltip("(?)", "A unique identifer for the pet.", br(), "Must not conflict with existing IDs")) + ), + tr( + th("Name:"), + td(input().name("name").type("text")), + td(new Tooltip("(?)", "The name of the pet.", br(), "e.g. 'Fluffy'")) + ), + tr( + th("Kind:"), + td( + select().name("kind").children( + option("CAT"), option("DOG"), option("BIRD"), option("FISH"), option("MOUSE"), option("RABBIT"), option("SNAKE") + ) + ), + td(new Tooltip("(?)", "The kind of animal.")) + ), + tr( + th("Breed:"), + td(input().name("breed").type("text")), + td(new Tooltip("(?)", "The breed of animal.", br(), "Can be any arbitrary text")) + ), + tr( + th("Gets along with:"), + td(input().name("getsAlongWith").type("text")), + td(new Tooltip("(?)", "A comma-delimited list of other animal types that this animal gets along with.")) + ), + tr( + th("Price:"), + td(input().name("price").type("number").placeholder("1.0").step("0.01").min(1).max(100)), + td(new Tooltip("(?)", "The price to charge for this pet.")) + ), + tr( + th("Birthdate:"), + td(input().name("birthDate").type("date")), + td(new Tooltip("(?)", "The pets birthday.")) + ), + tr( + td().colspan(2).style("text-align:right").children( + button("reset", "Reset"), + button("button","Cancel").onclick("window.location.href='/'"), + button("submit", "Submit") + ) + ) + ).style("white-space:nowrap") + ) + ); + } + } + + private int getNextAvailableId() { + int i = 100; + for (Integer k : petDB.keySet()) + i = Math.max(i, k); + return i+1; + } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json ---------------------------------------------------------------------- diff --git a/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json b/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json index fe7ba7a..4e523f3 100644 --- a/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json +++ b/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json @@ -11,11 +11,11 @@ // * specific language governing permissions and limitations under the License. * // *************************************************************************************************************************** { - 101: {kind:'CAT',name:'Mr. Frisky',price:39.99,breed:'Persian',getsAlongWith:['CAT','FISH','RABBIT'],birthDate:'04 Jul 2012'}, - 102: {kind:'DOG',name:'Kibbles',price:99.99,breed:'Husky',getsAlongWith:['DOG','BIRD','FISH','MOUSE','RABBIT'],birthDate:'01 Sep 2014'}, - 103: {kind:'RABBIT',name:'Hoppy',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE'],birthDate:'16 Apr 2017'}, - 104: {kind:'RABBIT',name:'Hoppy 2',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE'],birthDate:'17 Apr 2017'}, - 105: {kind:'FISH',name:'Gorton',price:1.99,breed:'Gold',getsAlongWith:['FISH'],birthDate:'20 Jun 2017'}, - 106: {kind:'MOUSE',name:'Hackwrench',price:4.99,breed:'Gadget',getsAlongWith:['FISH','MOUSE'],birthDate:'20 Jun 2017'}, - 107: {kind:'SNAKE',name:'Just Snake',price:9.99,breed:'Human',getsAlongWith:['SNAKE'],birthDate:'21 Jun 2017'} + 101: {kind:'CAT',name:'Mr. Frisky',price:39.99,breed:'Persian',getsAlongWith:['CAT','FISH','RABBIT'],birthDate:'2012-07-04'}, + 102: {kind:'DOG',name:'Kibbles',price:99.99,breed:'Husky',getsAlongWith:['DOG','BIRD','FISH','MOUSE','RABBIT'],birthDate:'2014-09-01'}, + 103: {kind:'RABBIT',name:'Hoppy',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE'],birthDate:'2017-04-16'}, + 104: {kind:'RABBIT',name:'Hoppy 2',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE'],birthDate:'2017-04-17'}, + 105: {kind:'FISH',name:'Gorton',price:1.99,breed:'Gold',getsAlongWith:['FISH'],birthDate:'2017-06-20'}, + 106: {kind:'MOUSE',name:'Hackwrench',price:4.99,breed:'Gadget',getsAlongWith:['FISH','MOUSE'],birthDate:'2017-06-20'}, + 107: {kind:'SNAKE',name:'Just Snake',price:9.99,breed:'Human',getsAlongWith:['SNAKE'],birthDate:'2017-06-21'} } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java index 60d1ff3..59e1cd0 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java @@ -127,7 +127,7 @@ public class RestConfig implements ServletConfig { Object htmlTemplate = HtmlDocTemplateBasic.class; Class<?> resourceClass; - Map<String,Widget> htmlWidgets = new HashMap<String,Widget>(); + List<Class<? extends Widget>> htmlWidgets = new ArrayList<Class<? extends Widget>>(); /** * Constructor for top-level servlets when using dependency injection. @@ -1381,8 +1381,7 @@ public class RestConfig implements ServletConfig { * @return This object (for method chaining). */ public RestConfig addHtmlWidget(Class<? extends Widget> value) { - Widget w = ClassUtils.newInstance(Widget.class, value); - this.htmlWidgets.put(w.getName(), w); + this.htmlWidgets.add(value); return this; } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java index 4a18c33..fbd1f7f 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java @@ -701,7 +701,12 @@ public final class RestContext extends Context { fullPath = (sc.parentContext == null ? "" : (sc.parentContext.fullPath + '/')) + sc.path; - htmlWidgets = sc.htmlWidgets; + this.htmlWidgets = new LinkedHashMap<String,Widget>(); + for (Class<? extends Widget> wc : sc.htmlWidgets) { + Widget w = ClassUtils.newInstanceFromOuter(resource, Widget.class, wc); + this.htmlWidgets.put(w.getName(), w); + } + htmlHeader = sc.htmlHeader; htmlLinks = sc.htmlLinks; htmlNav = sc.htmlNav; http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java index efcd579..d551aa3 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java @@ -16,7 +16,6 @@ import javax.servlet.http.*; import org.apache.juneau.*; import org.apache.juneau.rest.*; -import org.apache.juneau.serializer.*; import org.apache.juneau.utils.*; /** @@ -58,10 +57,10 @@ public final class Traversable implements RestConverter { o = cm.getPojoSwap().swap(req.getBeanSession(), o); PojoRest p = new PojoRest(o, req.getBody().getReaderParser()); o = p.get(req.getPathMatch().getRemainder()); - } catch (SerializeException e) { - throw new RestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } catch (PojoRestException e) { throw new RestException(e.getStatus(), e); + } catch (Exception e) { + throw new RestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/java/org/apache/juneau/rest/widget/Tooltip.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/Tooltip.java b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/Tooltip.java new file mode 100644 index 0000000..829c740 --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/Tooltip.java @@ -0,0 +1,97 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.rest.widget; + +import static org.apache.juneau.dto.html5.HtmlBuilder.*; + +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.dto.html5.*; + +/** + * Simple template for adding tooltips to HTML5 bean constructs, typically in menu item widgets. + * + * <p> + * Tooltips depend on the existence of the <code>tooltip</code> and <code>tooltiptext</code> styles that should be + * present in the stylesheet for the document. + * + * <p> + * The following examples shows how tooltips can be added to a menu item widget. + * + * <p class='bcode'> + * <jk>public class</jk> MyFormMenuItem <jk>extends</jk> MenuItemWidget { + * + * <ja>@Override</ja> + * <jk>public</jk> String getLabel(RestRequest req) <jk>throws</jk> Exception { + * <jk>return</jk> <js>"myform"</js>; + * } + * + * <ja>@Override</ja> + * <jk>public</jk> Object getContent(RestRequest req) <jk>throws</jk> Exception { + * <jk>return</jk> div( + * <jsm>form</jsm>().id(<js>"form"</js>).action(<js>"servlet:/form"</js>).method(<js>"POST"</js>).children( + * <jsm>table</jsm>( + * <jsm>tr</jsm>( + * <jsm>th</jsm>(<js>"Field 1:"</js>), + * <jsm>td</jsm>(<jsm>input</jsm>().name(<js>"field1"</js>).type(<js>"text"</js>)), + * <jsm>td</jsm>(<jk>new</jk> Tooltip(<js>"(?)"</js>, <js>"This is field #1!"</js>, br(), <js>"(e.g. '"</js>, code(<js>"Foo"</js>), <js>"')"</js>)) + * ), + * <jsm>tr</jsm>( + * <jsm>th</jsm>(<js>"Field 2:"</js>), + * <jsm>td</jsm>(<jsm>input</jsm>().name(<js>"field2"</js>).type(<js>"text"</js>)), + * <jsm>td</jsm>(<jk>new</jk> Tooltip(<js>"(?)"</js>, <js>"This is field #2!"</js>, br(), <js>"(e.g. '"</js>, code(<js>"Bar"</js>), <js>"')"</js>)) + * ) + * ) + * ) + * ); + * } + * } + * </p> + */ +public class Tooltip { + + private final String display; + private final List<Object> content; + + /** + * Constructor. + * + * @param display + * The normal display text. + * This is what gets rendered normally. + * @param content + * The hover contents. + * Typically a list of strings, but can also include any HTML5 beans as well. + */ + public Tooltip(String display, Object...content) { + this.display = display; + this.content = new ArrayList<Object>(Arrays.asList(content)); + } + + /** + * The swap method. + * + * <p> + * Converts this bean into a div tag with contents. + * + * @param session The bean session. + * @return The swapped contents of this bean. + */ + public Div swap(BeanSession session) { + return div( + small(display), + span()._class("tooltiptext").children(content) + )._class("tooltip"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css index ee9a94a..487eae7 100644 --- a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css +++ b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css @@ -72,13 +72,13 @@ nav { color: #94A3AB; } -nav ol { +nav>ol { list-style-type: none; margin: 0px 10px; padding: 0px; } -nav li { +nav>ol>li { display: inline; } @@ -125,7 +125,6 @@ article div.data { border-radius: 4px; margin: 20px; display: inline-block; - overflow-x: auto; box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5); font-family: sans-serif; color: #26343F; @@ -221,6 +220,43 @@ footer { } /**********************************************************************************************************************/ +/** Tooltips **/ +/**********************************************************************************************************************/ + +.tooltip { + position: relative; + display: inline-block; +} + +.tooltip .tooltiptext { + visibility: hidden; + background-color: #FEF9E7; + color: black; + padding: 5px; + border-radius: 6px; + position: absolute; + z-index: 1; + top: 0; + left: 0; + margin-left: 30px; + box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5); + opacity: 0; + transition: opacity 0.5s; + font-weight: normal; +} + +.tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + +.tooltiptext { + white-space: nowrap; + float: left; + border: 1px solid black; +} + +/**********************************************************************************************************************/ /** Other classes **/ /**********************************************************************************************************************/ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css index 0a43064..59a641c 100644 --- a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css +++ b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css @@ -79,13 +79,13 @@ nav * { font-weight: lighter; } -nav ol { +nav>ol { list-style-type: none; margin: 0px 10px; padding: 0px; } -nav li { +nav>ol>li { display: inline; } @@ -120,7 +120,6 @@ section { article { display: table-cell; padding: 20px 40px; - overflow-x: auto; } article * { @@ -212,6 +211,43 @@ footer { } /**********************************************************************************************************************/ +/** Tooltips **/ +/**********************************************************************************************************************/ + +.tooltip { + position: relative; + display: inline-block; +} + +.tooltip .tooltiptext { + visibility: hidden; + background-color: #FEF9E7; + color: black; + padding: 5px; + border-radius: 6px; + position: absolute; + z-index: 1; + top: 0; + left: 0; + margin-left: 30px; + box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5); + opacity: 0; + transition: opacity 0.5s; + font-weight: normal; +} + +.tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + +.tooltiptext { + white-space: nowrap; + float: left; + border: 1px solid black; +} + +/**********************************************************************************************************************/ /** Other classes **/ /**********************************************************************************************************************/ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css index 690dad4..3b4aa44 100644 --- a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css +++ b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css @@ -69,13 +69,13 @@ nav { margin: 0px 25px; } -nav ol { +nav>ol { list-style-type: none; margin: 0px 10px; padding: 0px; } -nav li { +nav>ol>li { display: inline; } @@ -174,6 +174,43 @@ footer { } /**********************************************************************************************************************/ +/** Tooltips **/ +/**********************************************************************************************************************/ + +.tooltip { + position: relative; + display: inline-block; +} + +.tooltip .tooltiptext { + visibility: hidden; + background-color: #FEF9E7; + color: black; + padding: 5px; + border-radius: 6px; + position: absolute; + z-index: 1; + top: 0; + left: 0; + margin-left: 30px; + box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5); + opacity: 0; + transition: opacity 0.5s; + font-weight: normal; +} + +.tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + +.tooltiptext { + white-space: nowrap; + float: left; + border: 1px solid black; +} + +/**********************************************************************************************************************/ /** Other classes **/ /**********************************************************************************************************************/ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css index 5b24e83..430df88 100644 --- a/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css +++ b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css @@ -14,4 +14,4 @@ .menu-item { position: relative; display: inline-block; -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css index a1903e6..cdd96c3 100644 --- a/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css +++ b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css @@ -11,35 +11,3 @@ * specific language governing permissions and limitations under the License. * ***************************************************************************************************************************/ -.tooltip { - position: relative; - display: inline-block; -} - -.tooltip .tooltiptext { - visibility: hidden; - background-color: #FEF9E7; - color: black; - padding: 5px; - border-radius: 6px; - position: absolute; - z-index: 1; - top: 0; - left: 0; - margin-left: 30px; - box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5); - opacity: 0; - transition: opacity 0.5s; - font-weight: normal; -} - -.tooltip:hover .tooltiptext { - visibility: visible; - opacity: 1; -} - -.tooltiptext { - white-space: nowrap; - float: left; - border: 1px solid black; -} \ No newline at end of file
