/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.rdf.internal.impl.extensions;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.apache.log4j.Logger;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.datatypes.XMLDatatypeUtil;
import com.bigdata.rdf.internal.IDatatypeURIResolver;
import com.bigdata.rdf.internal.IExtension;
import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.internal.XSD;
import com.bigdata.rdf.internal.impl.literal.AbstractLiteralIV;
import com.bigdata.rdf.internal.impl.literal.LiteralExtensionIV;
import com.bigdata.rdf.internal.impl.literal.XSDNumericIV;
import com.bigdata.rdf.model.BigdataURI;
import com.bigdata.rdf.model.BigdataValue;
import com.bigdata.rdf.model.BigdataValueFactory;
import com.bigdata.rdf.store.AbstractTripleStore;
import com.bigdata.util.InnerCause;
/**
* This implementation of {@link IExtension} implements inlining for literals
* that represent xsd:dateTime literals. These literals will be stored as time
* in milliseconds since the epoch. The milliseconds are encoded as an inline
* long.
*/
public class DateTimeExtension<V extends BigdataValue> implements IExtension<V> {
private static final transient Logger log = Logger.getLogger(DateTimeExtension.class);
private final Map<IV,BigdataURI> datatypes;
private final TimeZone defaultTZ;
public DateTimeExtension(final IDatatypeURIResolver resolver,
final TimeZone defaultTZ) {
// this.dateTime = resolver.resolve(XSD.DATETIME);
this.datatypes = new LinkedHashMap<IV,BigdataURI>();
resolve(resolver, XSD.DATETIME);
resolve(resolver, XSD.DATE);
resolve(resolver, XSD.TIME);
resolve(resolver, XSD.GDAY);
resolve(resolver, XSD.GMONTH);
resolve(resolver, XSD.GMONTHDAY);
resolve(resolver, XSD.GYEAR);
resolve(resolver, XSD.GYEARMONTH);
this.defaultTZ = defaultTZ;
}
private void resolve(final IDatatypeURIResolver resolver, final URI uri) {
if (log.isDebugEnabled()) {
log.debug("resolving: " + uri);
}
final BigdataURI val = resolver.resolve(uri);
datatypes.put(val.getIV(), val);
}
@Override
public Set<BigdataURI> getDatatypes() {
return new LinkedHashSet<BigdataURI>(datatypes.values());
}
/**
* Attempts to convert the supplied value into an epoch representation.
* Tests for a literal value with the correct datatype that can be converted
* to a positive long integer. Encodes the long in a delegate
* {@link XSDLongIV}, and returns an {@link LiteralExtensionIV} to wrap the native
* type.
*/
public LiteralExtensionIV createIV(final Value value) {
if (value instanceof Literal == false)
throw new IllegalArgumentException();
final Literal lit = (Literal) value;
final URI dt = lit.getDatatype();
final String s = value.stringValue();
/*
* Returns the current time as UTC milliseconds from the epoch
*/
final long l = getTimestamp(s, defaultTZ);
return createIV(l, dt);
}
/**
* Convert an xsd:dateTime into its milliseconds from the epoch
* representation.
*/
public static long getTimestamp(final String dateTime, final TimeZone defaultTZ) {
final XMLGregorianCalendar c = XMLDatatypeUtil.parseCalendar(dateTime);
if (c.getTimezone() == DatatypeConstants.FIELD_UNDEFINED) {
final GregorianCalendar gc = c.toGregorianCalendar();
gc.setGregorianChange(new Date(Long.MIN_VALUE));
final int offsetInMillis =
// defaultTZ.getRawOffset();
defaultTZ.getOffset(gc.getTimeInMillis());
final int offsetInMinutes =
offsetInMillis / 1000 / 60;
c.setTimezone(offsetInMinutes);
}
final GregorianCalendar gc = c.toGregorianCalendar();
gc.setGregorianChange(new Date(Long.MIN_VALUE));
/*
* Returns the current time as milliseconds from the epoch
*/
final long l = gc.getTimeInMillis();
return l;
}
/**
* Convert an xsd:dateTime into its milliseconds from the epoch
* representation.
*/
public static long getTimestamp(final String dateTime) {
return getTimestamp(dateTime, TimeZone.getTimeZone(
AbstractTripleStore.Options.DEFAULT_INLINE_DATE_TIMES_TIMEZONE));
}
public LiteralExtensionIV createIV(final long timestamp, final URI dt) {
if (dt == null)
throw new IllegalArgumentException();
BigdataURI resolvedDT = null;
for (BigdataURI val : datatypes.values()) {
// Note: URI.stringValue() is efficient....
if (val.stringValue().equals(dt.stringValue())) {
resolvedDT = val;
}
}
if (resolvedDT == null)
throw new IllegalArgumentException();
final AbstractLiteralIV delegate = new XSDNumericIV(timestamp);
return new LiteralExtensionIV(delegate, resolvedDT.getIV());
}
/**
* Use the long value of the {@link XSDLongIV} delegate (which represents
* milliseconds since the epoch) to create a an XMLGregorianCalendar
* object (GMT timezone). Use the XMLGregorianCalendar to create a datatype
* literal value with the appropriate datatype.
*/
public V asValue(final LiteralExtensionIV iv, final BigdataValueFactory vf) {
if (!datatypes.containsKey(iv.getExtensionIV())) {
throw new IllegalArgumentException("unrecognized datatype");
}
/*
* Milliseconds since the epoch.
*/
final long l = iv.getDelegate().longValue();
final TimeZone tz = BSBMHACK ? TimeZone.getDefault()/*getTimeZone("GMT")*/ : defaultTZ;
final GregorianCalendar c = new GregorianCalendar(tz);
c.setGregorianChange(new Date(Long.MIN_VALUE));
c.setTimeInMillis(l);
try {
final BigdataURI dt = datatypes.get(iv.getExtensionIV());
final DatatypeFactory f = datatypeFactorySingleton;
final XMLGregorianCalendar xmlGC = f.newXMLGregorianCalendar(c);
String s = xmlGC.toString();
final int offset = s.startsWith("-") ? 1 : 0;
if (dt.equals(XSD.DATETIME)) {
if (BSBMHACK) {
// Chopping off the milliseconds part and the trailing 'Z'.
final int i = s.lastIndexOf('.');
if (i >= 0) {
s = s.substring(0, i);
}
}
} else if (dt.equals(XSD.DATE)) {
// YYYY-MM-DD (10 chars)
s = s.substring(0, 10+offset);
} else if (dt.equals(XSD.TIME)) {
// everything after the date (from 11 chars in)
s = s.substring(11+offset);
} else if (dt.equals(XSD.GDAY)) {
// gDay Defines a part of a date - the day (---DD)
s = "---" + s.substring(8+offset, 10+offset);
} else if (dt.equals(XSD.GMONTH)) {
// gMonth Defines a part of a date - the month (--MM)
s = "--" + s.substring(5+offset, 7+offset);
} else if (dt.equals(XSD.GMONTHDAY)) {
// gMonthDay Defines a part of a date - the month and day (--MM-DD)
s = "--" + s.substring(5+offset, 10+offset);
} else if (dt.equals(XSD.GYEAR)) {
// gYear Defines a part of a date - the year (YYYY)
s = s.substring(0, 4+offset);
} else if (dt.equals(XSD.GYEARMONTH)) {
// gYearMonth Defines a part of a date - the year and month (YYYY-MM)
s = s.substring(0, 7+offset);
}
return (V) vf.createLiteral(s, dt);
} catch (RuntimeException ex) {
if (InnerCause.isInnerCause(ex, InterruptedException.class)) {
throw ex;
}
throw new IllegalArgumentException("bad iv: " + iv, ex);
}
}
/** Singleton. */
public static final DatatypeFactory datatypeFactorySingleton;
/**
* Singleton caching pattern for the Datatype factory reference.
*
* @see <a href="http://sourceforge.net/apps/trac/bigdata/ticket/802">
* Optimize DatatypeFactory instantiation in DateTimeExtension </a>
*/
static {
DatatypeFactory f = null;
try {
f = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException ex) {
log.error("Could not configure DatatypeFactory: " + ex, ex);
}
datatypeFactorySingleton = f;
}
/**
* This conditionally enables some logic for xsd:dateTime compatibility with
* BSBM.
*
* @see http://sourceforge.net/apps/trac/bigdata/ticket/277
*/
static private transient boolean BSBMHACK = Boolean.getBoolean("BSBM_HACK");
}