{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies, OverloadedStrings, FlexibleInstances, UndecidableInstances, NoMonomorphismRestriction, FlexibleContexts #-} -- | -- Module: NetSpider.Graph.Internal -- Description: -- Maintainer: Toshio Ito -- -- __this module is internal. End-users should not use it.__ module NetSpider.Graph.Internal ( -- * EID EID, -- * VNode VNode, -- * VFoundNode VFoundNode, VFoundNodeData(..), NodeAttributes(..), keyTimestamp, gSetTimestamp, gVFoundNodeData, -- * EFinds EFinds, EFindsData(..), LinkAttributes(..), gSetLinkState, gFindsTarget, gEFindsData, -- * Reconstruction makeFoundNode, makeFoundLink ) where import Control.Category ((<<<)) import Data.Aeson (ToJSON(..), FromJSON(..), Value(..)) import Data.Foldable (Foldable) import Data.Greskell ( FromGraphSON(..), ElementData(..), Element(..), Vertex, Edge, ElementID, AVertexProperty, AVertex, AEdge, Walk, SideEffect, Transform, unsafeCastEnd, Binder, Parser, GValue, gIdentity, gProperty, gPropertyV, (=:), gProperties, gInV, newBind, Key, AsLabel, unKey, PMap, Multi, Single, lookupAs, PMapLookupException, pMapFromList, pMapToList, pMapToFail, gProject, gValueMap, gByL, gId, Keys(..) ) import Data.Greskell.NonEmptyLike (NonEmptyLike) import Data.Greskell.Extra (writePMapProperties) import Data.Int (Int64) import Data.Text (Text, unpack, pack) import Data.Time.LocalTime (TimeZone(..)) import NetSpider.Timestamp (Timestamp(..)) import NetSpider.Found ( LinkState, linkStateToText, linkStateFromText, FoundLink(..), FoundNode(..) ) -- | Generic element ID used in the graph DB. type EID = ElementID -- | The \"node\" vertex. newtype VNode = VNode AVertex deriving (Show,Eq,ElementData,Element,Vertex,FromGraphSON) type TsEpoch = Int64 keyTimestamp :: Key VFoundNode TsEpoch keyTimestamp = "@timestamp" keyTzOffset :: Key (AVertexProperty TsEpoch) Int keyTzOffset = "@tz_offset_min" keyTzSummerOnly :: Key (AVertexProperty TsEpoch) Bool keyTzSummerOnly = "@tz_summer_only" keyTzName :: Key (AVertexProperty TsEpoch) Text keyTzName = "@tz_name" gSetTimestamp :: Timestamp -> Binder (Walk SideEffect VFoundNode VFoundNode) gSetTimestamp ts = do var_epoch <- newBind $ epochTime ts meta_props <- makeMetaProps $ timeZone ts return $ gPropertyV Nothing keyTimestamp var_epoch meta_props where makeMetaProps Nothing = return [] makeMetaProps (Just tz) = do offset <- newBind $ timeZoneMinutes tz summer <- newBind $ timeZoneSummerOnly tz name <- newBind $ pack $ timeZoneName tz return $ [ keyTzOffset =: offset, keyTzSummerOnly =: summer, keyTzName =: name ] -- | The \"found_node\" vertex. newtype VFoundNode = VFoundNode AVertex deriving (Show,Eq,ElementData,Element,Vertex,FromGraphSON) -- | Properties of the \"found_node\" vertex. data VFoundNodeData na = VFoundNodeData { vfnId :: EID VFoundNode, vfnTimestamp :: Timestamp, vfnAttributes :: na } deriving (Show) labelVProps :: AsLabel (PMap Multi GValue) labelVProps = "props" labelVFoundNodeID :: AsLabel (ElementID VFoundNode) labelVFoundNodeID = "vid" labelMetaProps :: AsLabel (PMap Single GValue) labelMetaProps = "mprops" gVFoundNodeData :: Walk Transform VFoundNode (VFoundNodeData na) gVFoundNodeData = unsafeCastEnd $ gProject ( gByL labelVFoundNodeID gId) [ gByL labelVProps $ gValueMap KeysNil, gByL labelMetaProps (gValueMap KeysNil <<< gProperties [keyTimestamp]) ] instance NodeAttributes na => FromGraphSON (VFoundNodeData na) where parseGraphSON gv = fromPMap =<< parseGraphSON gv where lookupAsF k pm = pMapToFail $ lookupAs k pm fromPMap :: NodeAttributes na => PMap Single GValue -> Parser (VFoundNodeData na) fromPMap pm = do eid <- lookupAsF labelVFoundNodeID pm props <- lookupAsF labelVProps pm mprops <- lookupAsF labelMetaProps pm attrs <- parseNodeAttributes props epoch_ts <- lookupAsF keyTimestamp props mtz <- parseTimeZone mprops return $ VFoundNodeData { vfnId = eid, vfnTimestamp = Timestamp { epochTime = epoch_ts, timeZone = mtz }, vfnAttributes = attrs } parseTimeZone ts_prop = do case (get keyTzOffset, get keyTzSummerOnly, get keyTzName) of (Left _, Left _, Left _) -> return Nothing (eo, es, en) -> do offset <- eToP eo is_summer_only <- eToP es name <- eToP en return $ Just $ TimeZone { timeZoneMinutes = offset, timeZoneSummerOnly = is_summer_only, timeZoneName = unpack name } where get :: FromGraphSON b => Key a b -> Either PMapLookupException b get k = lookupAs k ts_prop eToP = either (fail . show) return -- | \"finds\" edge. newtype EFinds = EFinds AEdge deriving (Show,Eq,ElementData,Element,Edge,FromGraphSON) -- | Properties of \"finds\" edge. data EFindsData la = EFindsData { efId :: EID EFinds, efTargetId :: EID VNode, efLinkState :: LinkState, efLinkAttributes :: la } deriving (Show) keyLinkState :: Key EFinds Text keyLinkState = "@link_state" gSetLinkState :: LinkState -> Binder (Walk SideEffect EFinds EFinds) gSetLinkState ls = do var_ls <- newBind $ linkStateToText $ ls return $ gProperty keyLinkState var_ls gFindsTarget :: Walk Transform EFinds VNode gFindsTarget = gInV labelEProps :: AsLabel (PMap Single GValue) labelEProps = "eprops" labelEFindsID :: AsLabel (ElementID EFinds) labelEFindsID = "eid" labelEFindsTarget :: AsLabel (ElementID VNode) labelEFindsTarget = "vnode" gEFindsData :: Walk Transform EFinds (EFindsData la) gEFindsData = unsafeCastEnd $ gProject ( gByL labelEProps $ gValueMap KeysNil ) [ gByL labelEFindsID $ gId, gByL labelEFindsTarget (gId <<< gFindsTarget) ] instance LinkAttributes la => FromGraphSON (EFindsData la) where parseGraphSON gv = fromPMap =<< parseGraphSON gv where lookupAsF k pm = pMapToFail $ lookupAs k pm fromPMap :: LinkAttributes la => PMap Single GValue -> Parser (EFindsData la) fromPMap pm = do props <- lookupAsF labelEProps pm eid <- lookupAsF labelEFindsID pm target <- lookupAsF labelEFindsTarget pm ls <- parseLinkState =<< lookupAsF keyLinkState props attrs <- parseLinkAttributes props return $ EFindsData { efId = eid, efTargetId = target, efLinkState = ls, efLinkAttributes = attrs } parseLinkState t = case linkStateFromText t of Nothing -> fail ("Failed to parse " ++ (unpack $ unKey $ keyLinkState) ++ " field.") Just a -> return a -- | Class of user-defined types for node attributes. Its content is -- stored in the NetSpider database. class NodeAttributes ps where writeNodeAttributes :: ps -> Binder (Walk SideEffect VFoundNode VFoundNode) -- ^ Return 'Walk' to write the attributes to the 'VFoundNode'. parseNodeAttributes :: PMap Multi GValue -> Parser ps -- ^ Parse the vertex proprerties into the attributes. -- | No attributes. instance NodeAttributes () where writeNodeAttributes _ = return gIdentity parseNodeAttributes _ = return () -- | Straightforward implementation. -- -- @since 0.4.0.0 instance (FromGraphSON v, ToJSON v, Foldable c, Traversable c, NonEmptyLike c) => NodeAttributes (PMap c v) where writeNodeAttributes = writePMapProperties parseNodeAttributes = traverse parseGraphSON . pMapFromList . pMapToList -- | Class of user-defined types for link attributes. Its content is -- stored in the NetSpider database. class LinkAttributes ps where writeLinkAttributes :: ps -> Binder (Walk SideEffect EFinds EFinds) -- ^ Return 'Walk' to write the attributes to the 'EFinds'. parseLinkAttributes :: PMap Single GValue -> Parser ps -- ^ Parse the edge proprerties into the attributes. -- | No attributes. instance LinkAttributes () where writeLinkAttributes _ = return gIdentity parseLinkAttributes _ = return () -- | Straightforward implementation. -- -- @since 0.4.0.0 instance (FromGraphSON v, ToJSON v, Foldable c, Traversable c, NonEmptyLike c) => LinkAttributes (PMap c v) where writeLinkAttributes = writePMapProperties parseLinkAttributes = traverse parseGraphSON . pMapFromList . pMapToList -- | Make 'FoundLink' out of 'EFindsData'. makeFoundLink :: n -- ^ Target node ID -> EFindsData la -- ^ Attributes of \"finds\" edge -> FoundLink n la makeFoundLink target_nid ef_data = FoundLink { targetNode = target_nid, linkState = efLinkState ef_data, linkAttributes = efLinkAttributes ef_data } -- | Make 'FoundNode' out of 'VFoundNodeData' and other graph -- elements. makeFoundNode :: n -- ^ Subject node ID -> VFoundNodeData na -- ^ Attributes of the 'FoundNode'. -> [FoundLink n la] -- ^ neighbor links -> FoundNode n na la makeFoundNode subject_nid vfn neighbors = FoundNode { subjectNode = subject_nid, foundAt = vfnTimestamp vfn, neighborLinks = neighbors, nodeAttributes = vfnAttributes vfn }