Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
704dcb0
ILLDEV-423 metadata lookup
adamdickmeiss Jul 3, 2026
59c468f
Lookup -> HoldingsLookup
adamdickmeiss Jul 3, 2026
05588b3
Add test
adamdickmeiss Jul 3, 2026
eb2831c
metadata parsing handles opac; testing
adamdickmeiss Jul 3, 2026
3effdc9
Metadata: add subtitle, author, edition
adamdickmeiss Jul 3, 2026
3825f4a
Improve matching
adamdickmeiss Jul 3, 2026
2bf4828
LookupAdapterFactory
adamdickmeiss Jul 3, 2026
6488e7e
supplierlocator uses LookupAdapterFactory
adamdickmeiss Jul 3, 2026
416fe34
Test metadata lookup for post patron requests
adamdickmeiss Jul 3, 2026
15e723d
Test supplierlocator with with metadata lookup
adamdickmeiss Jul 3, 2026
fa50974
Fix check for auto
adamdickmeiss Jul 3, 2026
30f8406
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
e3a043a
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
70d79eb
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
b7846c8
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
fbab30a
Fix up test
adamdickmeiss Jul 3, 2026
b05f57c
Comment
adamdickmeiss Jul 3, 2026
f4fc243
Update messages
adamdickmeiss Jul 3, 2026
e0ef7a9
Fixes
adamdickmeiss Jul 3, 2026
9cf1d5d
CP
adamdickmeiss Jul 3, 2026
647c5e1
CP
adamdickmeiss Jul 3, 2026
9f3ebe1
Metadata parser always present in creator
adamdickmeiss Jul 3, 2026
65463f7
adapter zoom: iterateQueries
adamdickmeiss Jul 3, 2026
ffac79a
metadatalookup not implemented for SRU adapter
adamdickmeiss Jul 3, 2026
3d59057
lenderLastResort inspects requester first
adamdickmeiss Jul 3, 2026
fd7668e
Update error message
adamdickmeiss Jul 3, 2026
fe5e8fd
Replace only non-empty vaLues
adamdickmeiss Jul 3, 2026
0ec0cfb
Simplify MetadataRequestUpdate
adamdickmeiss Jul 3, 2026
8249d07
For non-consortia, use marc metadata parser
adamdickmeiss Jul 3, 2026
a018855
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
e70c21d
Merge remote-tracking branch 'origin/ILLDEV-423-redo' into ILLDEV-423…
adamdickmeiss Jul 3, 2026
7382ac5
Fix syntax error
adamdickmeiss Jul 3, 2026
6e50989
Metaddata lookup for SRU sru holdings parser
adamdickmeiss Jul 3, 2026
c47cd7e
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
4d625c2
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
d98f454
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
bc29d58
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
5054538
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
35b66cc
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
9e10e0e
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
15d65a2
Potential fix for pull request finding
adamdickmeiss Jul 3, 2026
ff245c2
sru/zoom adapters use same strategy for parsing/iterating
adamdickmeiss Jul 4, 2026
3c5c4f5
CP
adamdickmeiss Jul 4, 2026
733f45a
Simplify
adamdickmeiss Jul 4, 2026
d98db9a
LookupResult
adamdickmeiss Jul 4, 2026
57f2900
lint
adamdickmeiss Jul 4, 2026
10041be
Metadata parser: empty field prevents parsing the value
adamdickmeiss Jul 4, 2026
8ce41a6
better reporting for missing lookup params; rename
adamdickmeiss Jul 6, 2026
fe960a6
lint
adamdickmeiss Jul 6, 2026
b46dd3a
CP
adamdickmeiss Jul 6, 2026
3a2dc2c
Let Lookup check if query is empty
adamdickmeiss Jul 6, 2026
5ec95a7
CP
adamdickmeiss Jul 6, 2026
83bfac2
GetAdapterRequester
adamdickmeiss Jul 6, 2026
6a46e9d
CP
adamdickmeiss Jul 6, 2026
94677bc
Merge branch 'main' into ILLDEV-423-redo
adamdickmeiss Jul 8, 2026
492a1c2
Merge remote-tracking branch 'origin/main' into ILLDEV-423-redo
adamdickmeiss Jul 9, 2026
56c1774
SRU/ZOOM check holdings when iterating over result set
adamdickmeiss Jul 9, 2026
dd55815
GetHoldings error
adamdickmeiss Jul 9, 2026
a824dc5
More tests
adamdickmeiss Jul 9, 2026
270612f
LookupResult.GetQuery returns just query
adamdickmeiss Jul 13, 2026
7a50a6f
Merge branch 'main' into ILLDEV-423-redo
adamdickmeiss Jul 13, 2026
5ec3048
Parse metadata along with holdings in zoom/sru adapter
adamdickmeiss Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion broker/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,17 @@ func Init(ctx context.Context) (Context, error) {
prActionService := prservice.CreatePatronRequestActionService(prRepo, illRepo, eventBus, &iso18626Handler, lmsCreator, email.NewEmailService())
prMessageHandler.SetAutoActionRunner(prActionService)
iso18626Client := client.CreateIso18626Client(eventBus, illRepo, prMessageHandler, MAX_MESSAGE_SIZE, delay)
supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, holdingsAdapter, availabilityCreator, CONSORTIUM_SYMBOL)
lookupAdapterFactory := service.NewLookupAdapterFactory(illRepo, dirAdapter, CONSORTIUM_SYMBOL, holdingsAdapter, availabilityCreator)
supplierLocator := service.CreateSupplierLocator(eventBus, illRepo, dirAdapter, lookupAdapterFactory)
workflowManager := service.CreateWorkflowManager(eventBus, illRepo, service.WorkflowConfig{})
tenantResolver := tenant.NewResolver().WithIllRepo(illRepo).WithLookupAdapter(dirAdapter).WithTenantToSymbol(TENANT_TO_SYMBOL)
apiHandler := api.NewApiHandler(eventRepo, illRepo, tenantResolver, API_PAGE_SIZE)
prApiHandler := prapi.NewPrApiHandler(prRepo, eventBus, eventRepo, tenantResolver, &iso18626Handler, API_PAGE_SIZE)
prApiHandler.SetAutoActionRunner(prActionService)
prApiHandler.SetActionTaskProcessor(prActionService)
prApiHandler.SetIllRepo(illRepo)
prApiHandler.SetDirectoryLookupAdapter(dirAdapter)
prApiHandler.SetLookupAdapterFactory(lookupAdapterFactory)
sseBroker := api.NewSseBroker(appCtx, tenantResolver)
psApiHandler := psapi.NewPsApiHandler(psRepo, prRepo, tenantResolver)

Expand Down
6 changes: 3 additions & 3 deletions broker/handler/iso18626-handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"sync"
"time"

"github.com/indexdata/crosslink/broker/service"
"github.com/indexdata/crosslink/broker/holdings"
"github.com/indexdata/crosslink/broker/shim"

"github.com/indexdata/crosslink/broker/adapter"
Expand Down Expand Up @@ -198,7 +198,7 @@ func handleRetryRequest(ctx common.ExtendedContext, request *iso18626.Request, r

illTrans.LastRequesterAction = createPgText("Request")

oldParams := service.CreateHoldingsParams(illTrans.IllTransactionData)
oldParams := holdings.LookupParamsFromBibliographicInfo(illTrans.IllTransactionData.BibliographicInfo, illTrans.IllTransactionData.ServiceInfo)

illTransactionData := ill_db.IllTransactionData{
BibliographicInfo: request.BibliographicInfo,
Expand All @@ -212,7 +212,7 @@ func handleRetryRequest(ctx common.ExtendedContext, request *iso18626.Request, r
}
illTrans.IllTransactionData = illTransactionData

newParams := service.CreateHoldingsParams(illTrans.IllTransactionData)
newParams := holdings.LookupParamsFromBibliographicInfo(illTrans.IllTransactionData.BibliographicInfo, illTrans.IllTransactionData.ServiceInfo)
retryLookupChanged = oldParams != newParams

timestamp := pgtype.Timestamp{
Expand Down
6 changes: 3 additions & 3 deletions broker/holdings/adapter_metaproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ type MetaproxyAvailabilityAdapter struct {
holdingsLookupAdapter LookupAdapter
}

func NewMetaproxyAvailabilityAdapter(config directory.ZoomConfig, metaproxyUrl string, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser) (LookupAdapter, error) {
func NewMetaproxyAvailabilityAdapter(config directory.ZoomConfig, metaproxyUrl string, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser, metadataParser MetadataParser) (LookupAdapter, error) {
a := &MetaproxyAvailabilityAdapter{
holdingsLookupAdapter: CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{metaproxyUrl}, config.Address, queryBuilder, holdingsParser, "marcxml"),
holdingsLookupAdapter: CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{metaproxyUrl}, config.Address, queryBuilder, holdingsParser, metadataParser, "marcxml"),
}
return a, nil
}

func (a *MetaproxyAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string, error) {
func (a *MetaproxyAvailabilityAdapter) Lookup(params LookupParams) (LookupResult, error) {
return a.holdingsLookupAdapter.Lookup(params)
}
23 changes: 20 additions & 3 deletions broker/holdings/adapter_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import (
type MockAvailabilityAdapter struct {
Err error
Holdings []Holding
Metadata Metadata
}

type MockAvailabilityLookupResult struct {
parent *MockAvailabilityAdapter
}

func NewMockAvailabilityAdapter(config directory.HoldingsConfig) (LookupAdapter, error) {
Expand Down Expand Up @@ -38,9 +43,21 @@ func NewMockAvailabilityAdapter(config directory.HoldingsConfig) (LookupAdapter,
return &MockAvailabilityAdapter{}, nil
}

func (a *MockAvailabilityAdapter) Lookup(params LookupParams) ([]Holding, string, error) {
func (a *MockAvailabilityAdapter) Lookup(params LookupParams) (LookupResult, error) {
if a.Err != nil {
return nil, "", a.Err
return nil, a.Err
}
return a.Holdings, "", nil
return &MockAvailabilityLookupResult{parent: a}, nil
}

func (a *MockAvailabilityLookupResult) GetHoldings() ([]Holding, error) {
return a.parent.Holdings, nil
}

func (a *MockAvailabilityLookupResult) GetMetadata() (Metadata, error) {
return a.parent.Metadata, nil
}

func (a *MockAvailabilityLookupResult) GetQuery() string {
return ""
}
49 changes: 41 additions & 8 deletions broker/holdings/adapter_mock_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,38 @@ import (
type MockHoldingsLookupAdapter struct {
}

type MockHoldingsLookupResult struct {
holdings []Holding
holdingsErr error
query string
}

// the original mock holdings adapter that we used for shared index testing
func (m *MockHoldingsLookupAdapter) Lookup(params LookupParams) ([]Holding, string, error) {
func (m *MockHoldingsLookupAdapter) Lookup(params LookupParams) (LookupResult, error) {
var result MockHoldingsLookupResult
result.holdings = []Holding{}
result.query = params.Identifier
ids := strings.Split(params.Identifier, ";")
i := 1
var holdings []Holding
for _, id := range ids {
if id == "" {
// if the identifier is empty, we return an error to simulate a missing parameter
return &result, errors.New("missing lookup parameter: identifier")
}
// LookupResult should return an error if the identifier is "error"
if id == "error" {
return []Holding{}, id, errors.New("there is error")
return &result, errors.New("there is error")
}
if id == "error-holdings" {
result.holdingsErr = errors.New("there is error in holdings")
}
if id == "not-found" { // we could also just not append?
return []Holding{}, id, nil
return &result, nil
}
if strings.Index(id, "return-") == 0 {
val := strings.SplitN(strings.TrimPrefix(id, "return-"), "::", 2)
if len(val) < 1 || len(val[0]) < 1 {
return nil, id, fmt.Errorf("invalid return- value")
return &result, fmt.Errorf("invalid return- value")
}
var s, l string
if len(val) == 1 {
Expand All @@ -36,17 +52,34 @@ func (m *MockHoldingsLookupAdapter) Lookup(params LookupParams) ([]Holding, stri
s = val[0]
l = val[1]
}
holdings = append(holdings, Holding{
result.holdings = append(result.holdings, Holding{
Symbol: s,
LocalIdentifier: l,
})
} else {
holdings = append(holdings, Holding{
result.holdings = append(result.holdings, Holding{
Symbol: "ISIL:SUP" + strconv.Itoa(i),
LocalIdentifier: id,
})
}
i++
}
return holdings, params.Identifier, nil
return &result, nil
}

func (m *MockHoldingsLookupResult) GetMetadata() (Metadata, error) {
var metadata Metadata
metadata.Identifier = m.query
return metadata, nil
}

func (m *MockHoldingsLookupResult) GetHoldings() ([]Holding, error) {
if m.holdingsErr != nil {
return nil, m.holdingsErr
}
return m.holdings, nil
}

func (m *MockHoldingsLookupResult) GetQuery() string {
return m.query
}
131 changes: 83 additions & 48 deletions broker/holdings/adapter_sru.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,52 +18,53 @@ type SruHoldingsLookupAdapter struct {
sruUrl []string
client *http.Client
holdingsParser HoldingsParser
metadataParser MetadataParser
queryBuilder LookupQueryBuilder
xTarget string
recordSchema string
}

func CreateSruHoldingsLookupAdapter(client *http.Client, sruUrl []string, xTarget string, queryBuilder LookupQueryBuilder, parser HoldingsParser, recordSchema string) LookupAdapter {
return &SruHoldingsLookupAdapter{client: client, sruUrl: sruUrl, queryBuilder: queryBuilder, holdingsParser: parser, xTarget: xTarget, recordSchema: recordSchema}
type SruLookupResult struct {
query string
holdings []Holding
metadata *Metadata
}

func NewSruAvailabilityAdapter(config directory.SruConfig, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser) (LookupAdapter, error) {
func CreateSruHoldingsLookupAdapter(client *http.Client, sruUrl []string, xTarget string, queryBuilder LookupQueryBuilder, parser HoldingsParser, metadataParser MetadataParser, recordSchema string) LookupAdapter {
return &SruHoldingsLookupAdapter{client: client, sruUrl: sruUrl, queryBuilder: queryBuilder, holdingsParser: parser, metadataParser: metadataParser, xTarget: xTarget, recordSchema: recordSchema}
}

func NewSruAvailabilityAdapter(config directory.SruConfig, queryBuilder LookupQueryBuilder, holdingsParser HoldingsParser, metadataParser MetadataParser) (LookupAdapter, error) {
var recordSchema string
if config.RecordSchema != nil {
recordSchema = *config.RecordSchema
}
if recordSchema == "" {
recordSchema = "marcxml" // default to marcxml if not specified
}
return CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{config.Address}, "", queryBuilder, holdingsParser, recordSchema), nil
return CreateSruHoldingsLookupAdapter(http.DefaultClient, []string{config.Address}, "", queryBuilder, holdingsParser, metadataParser, recordSchema), nil
}

func (s *SruHoldingsLookupAdapter) parseRecord(record *sru.RecordDefinition, params LookupParams, holdings *[]Holding) error {
func (s *SruHoldingsLookupAdapter) parseRecord(record *sru.RecordDefinition, processRecord func([]byte) (bool, error)) (bool, error) {
if record.RecordXMLEscaping != nil && *record.RecordXMLEscaping != sru.RecordXMLEscapingDefinitionXml {
return fmt.Errorf("unsupported RecordXMLEscaping: %s", *record.RecordXMLEscaping)
return false, fmt.Errorf("unsupported RecordXMLEscaping: %s", *record.RecordXMLEscaping)
}
receivedSchema := record.RecordSchema
if receivedSchema == "info:srw/schema/1/diagnostics-v1.1" { // surrogate diagnostic record
var diagnostic diag.Diagnostic
err := xml.Unmarshal(record.RecordData.XMLContent, &diagnostic)
if err != nil {
return fmt.Errorf("decoding surrogate diagnostic failed: %s", err.Error())
return false, fmt.Errorf("decoding surrogate diagnostic failed: %s", err.Error())
}
return errors.New("surrogate diagnostic: " + diagnostic.Message + ": " + diagnostic.Details)
return false, errors.New("surrogate diagnostic: " + diagnostic.Message + ": " + diagnostic.Details)
}
if receivedSchema == "info:srw/schema/1/marcxml-v1.1" {
receivedSchema = "marcxml"
}
if receivedSchema != "" && receivedSchema != s.recordSchema {
return fmt.Errorf("unsupported RecordSchema: %s", record.RecordSchema)
}

ret, err := s.holdingsParser.Parse(record.RecordData.XMLContent, params)
if err != nil {
return fmt.Errorf("parsing holdings failed: %s", err.Error())
return false, fmt.Errorf("unsupported RecordSchema: %s", record.RecordSchema)
}
*holdings = append(*holdings, ret...)
return nil
return processRecord(record.RecordData.XMLContent)
}

func encodeCqlSearchClause(field string, value string) (string, error) {
Expand All @@ -74,76 +75,110 @@ func encodeCqlSearchClause(field string, value string) (string, error) {
return cqlQuery.String(), nil
}

func (s *SruHoldingsLookupAdapter) search(sruUrl string, params LookupParams, query string) ([]Holding, string, error) {
func (s *SruHoldingsLookupAdapter) search(sruUrl string, params LookupParams, query string, processRecord func([]byte) (bool, error)) (bool, error) {
var sruResponse sru.SearchRetrieveResponse
query = "?maximumRecords=1000&recordSchema=" + url.QueryEscape(s.recordSchema) + "&" + query
if s.xTarget != "" {
query += "&x-target=" + url.QueryEscape(s.xTarget)
}
found := false
err := httpclient.NewClient().GetXml(s.client, sruUrl+query, &sruResponse)
// notice: returning query even in case of error, to allow logging the query that caused the error
if err != nil {
return nil, query, err
return false, err
Comment thread
adamdickmeiss marked this conversation as resolved.
}
if sruResponse.Diagnostics != nil {
// non-surrogate diagnostics
diags := sruResponse.Diagnostics.Diagnostic
if len(diags) > 0 {
return nil, query, errors.New(diags[0].Message + ": " + diags[0].Details)
return false, errors.New(diags[0].Message + ": " + diags[0].Details)
}
}
var holdings []Holding
if sruResponse.Records != nil {
for _, record := range sruResponse.Records.Record {
err := s.parseRecord(&record, params, &holdings)
foundRecord, err := s.parseRecord(&record, processRecord)
if err != nil {
return nil, query, err
return false, fmt.Errorf("failed to parse holdings from SRU record: %w", err)
}
if foundRecord {
found = true
}
}
}
return holdings, query, nil
return found, nil
}

func (s *SruHoldingsLookupAdapter) getHoldings(sruUrl string, params LookupParams) ([]Holding, string, error) {
var holdings []Holding
func (s *SruHoldingsLookupAdapter) lookupServer(sruUrl string, params LookupParams, processRecord func([]byte) (bool, error)) (bool, string, error) {
cqlList, pqfList, err := s.queryBuilder.Build(params)
if err != nil {
return nil, "", err
return false, "", err
}
Comment thread
adamdickmeiss marked this conversation as resolved.
var queryParams string
var query string
var found bool
for _, cql := range cqlList {
query = cql
sruQuery := "query=" + url.QueryEscape(cql)
holdings, queryParams, err = s.search(sruUrl, params, sruQuery)
if err != nil {
return nil, queryParams, err
}
if len(holdings) > 0 {
return holdings, queryParams, nil
found, err = s.search(sruUrl, params, sruQuery, processRecord)
if err != nil || found {
return found, query, err
}
}
for _, pqf := range pqfList {
query = pqf
sruQuery := "x-pquery=" + url.QueryEscape(pqf)
holdings, queryParams, err = s.search(sruUrl, params, sruQuery)
if err != nil {
return nil, queryParams, err
}
if len(holdings) > 0 {
return holdings, queryParams, nil
found, err = s.search(sruUrl, params, sruQuery, processRecord)
if err != nil || found {
return found, query, err
}
}
return holdings, queryParams, nil
return false, query, nil
}

func (s *SruHoldingsLookupAdapter) Lookup(params LookupParams) ([]Holding, string, error) {
var holdings []Holding
logQuery := ""
func (s *SruHoldingsLookupAdapter) Lookup(params LookupParams) (LookupResult, error) {
var result SruLookupResult

for _, sruUrl := range s.sruUrl {
h, query, err := s.getHoldings(sruUrl, params)
var err error
found, query, err := s.lookupServer(sruUrl, params, func(xmlBuffer []byte) (bool, error) {
h, err := s.holdingsParser.Parse(xmlBuffer, params)
if err != nil {
return false, err
}
if result.metadata == nil && s.metadataParser != nil {
metadata, err := s.metadataParser.Parse(xmlBuffer)
if err != nil {
return false, fmt.Errorf("failed to parse metadata from SRU record: %w", err)
}
result.metadata = &metadata
}
if len(h) == 0 {
return false, nil
}
result.holdings = append(result.holdings, h...)
return true, nil
})
result.query = query
if err != nil {
return nil, query, err
return &result, err
}
holdings = append(holdings, h...)
logQuery = query
if found {
break
}
Comment thread
adamdickmeiss marked this conversation as resolved.
}
return &result, nil
}

func (r *SruLookupResult) GetQuery() string {
return r.query
}

func (s *SruLookupResult) GetHoldings() ([]Holding, error) {
return s.holdings, nil
}

func (s *SruLookupResult) GetMetadata() (Metadata, error) {
if s.metadata == nil {
return Metadata{}, nil
}
return holdings, logQuery, nil
return *s.metadata, nil
}
Loading
Loading