Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit 35d2b01

Browse files
dfcoffinclaude
andcommitted
Refactor ExportService to use DTO-based generic approach
- Create AtomExportService with generic export patterns - Add UsagePointExportService and MeterReadingExportService - Implement ModernExportService as unified interface - Add comprehensive test suite for export functionality - Replace 40+ legacy export methods with type-safe generic approach - Prepare for full implementation once entity classes are fixed 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent b8db9c3 commit 35d2b01

File tree

5 files changed

+1137
-0
lines changed

5 files changed

+1137
-0
lines changed
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/*
2+
*
3+
* Copyright (c) 2018-2025 Green Button Alliance, Inc.
4+
*
5+
* Portions (c) 2013-2018 EnergyOS.org
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
package org.greenbuttonalliance.espi.common.service.export;
22+
23+
import org.greenbuttonalliance.espi.common.dto.atom.AtomEntryDto;
24+
import org.greenbuttonalliance.espi.common.dto.atom.AtomFeedDto;
25+
import org.greenbuttonalliance.espi.common.dto.atom.AtomContentDto;
26+
import org.greenbuttonalliance.espi.common.dto.atom.LinkDto;
27+
import org.greenbuttonalliance.espi.common.mapper.GreenButtonMapper;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.beans.factory.annotation.Qualifier;
30+
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
31+
import org.springframework.stereotype.Service;
32+
33+
import javax.xml.transform.stream.StreamResult;
34+
import java.io.OutputStream;
35+
import java.time.OffsetDateTime;
36+
import java.util.List;
37+
import java.util.UUID;
38+
import java.util.function.Function;
39+
import java.util.stream.Collectors;
40+
41+
/**
42+
* Modern generic export service for Green Button resources.
43+
*
44+
* Replaces the legacy ExportServiceImpl with 40+ specific methods with a clean,
45+
* generic approach using MapStruct for Entity-DTO conversion and modern Java patterns.
46+
*
47+
* This service handles the complete export pipeline:
48+
* 1. Entity → DTO conversion via MapStruct
49+
* 2. DTO → Atom Entry/Feed wrapping
50+
* 3. JAXB marshalling to XML output stream
51+
*/
52+
@Service
53+
public class AtomExportService {
54+
55+
private final GreenButtonMapper mapper;
56+
private final Jaxb2Marshaller marshaller;
57+
58+
@Autowired
59+
public AtomExportService(
60+
GreenButtonMapper mapper,
61+
@Qualifier("fragmentMarshaller") Jaxb2Marshaller marshaller
62+
) {
63+
this.mapper = mapper;
64+
this.marshaller = marshaller;
65+
}
66+
67+
/**
68+
* Exports a single resource as an Atom entry.
69+
*
70+
* Generic method that replaces 20+ specific export methods from legacy ExportServiceImpl.
71+
* Uses MapStruct to convert Entity to DTO, then wraps in Atom entry format.
72+
*
73+
* @param <E> the entity type
74+
* @param <D> the DTO type
75+
* @param entity the entity to export
76+
* @param mapperFunction function to convert entity to DTO using MapStruct
77+
* @param resourceName the resource name for Atom entry
78+
* @param selfHref the self link href
79+
* @param stream the output stream to write XML
80+
*/
81+
public <E, D> void exportResource(
82+
E entity,
83+
Function<E, D> mapperFunction,
84+
String resourceName,
85+
String selfHref,
86+
OutputStream stream
87+
) {
88+
if (entity == null) {
89+
throw new IllegalArgumentException("Entity cannot be null");
90+
}
91+
92+
// Convert Entity to DTO using MapStruct
93+
D dto = mapperFunction.apply(entity);
94+
95+
// Wrap DTO in Atom entry
96+
AtomEntryDto entry = createAtomEntry(dto, resourceName, selfHref);
97+
98+
// Marshal to XML
99+
marshaller.marshal(entry, new StreamResult(stream));
100+
}
101+
102+
/**
103+
* Gets the GreenButtonMapper for external access.
104+
*
105+
* @return the mapper instance
106+
*/
107+
public GreenButtonMapper getMapper() {
108+
return mapper;
109+
}
110+
111+
/**
112+
* Exports a collection of resources as an Atom feed.
113+
*
114+
* Generic method that replaces 20+ specific collection export methods.
115+
* Converts each entity to DTO, wraps in Atom entries, and creates an Atom feed.
116+
*
117+
* @param <E> the entity type
118+
* @param <D> the DTO type
119+
* @param entities the entities to export
120+
* @param mapperFunction function to convert entity to DTO using MapStruct
121+
* @param resourceName the resource name for feed
122+
* @param feedTitle the feed title
123+
* @param feedId the feed ID
124+
* @param selfHref the feed self link
125+
* @param hrefGenerator function to generate href for each entity
126+
* @param stream the output stream to write XML
127+
*/
128+
public <E, D> void exportResourceCollection(
129+
List<E> entities,
130+
Function<E, D> mapperFunction,
131+
String resourceName,
132+
String feedTitle,
133+
String feedId,
134+
String selfHref,
135+
Function<E, String> hrefGenerator,
136+
OutputStream stream
137+
) {
138+
if (entities == null) {
139+
throw new IllegalArgumentException("Entities list cannot be null");
140+
}
141+
142+
// Convert entities to DTOs and wrap in Atom entries
143+
List<AtomEntryDto> entries = entities.stream()
144+
.map(entity -> {
145+
D dto = mapperFunction.apply(entity);
146+
String entryHref = hrefGenerator.apply(entity);
147+
return createAtomEntry(dto, resourceName, entryHref);
148+
})
149+
.collect(Collectors.toList());
150+
151+
// Create Atom feed
152+
AtomFeedDto feed = createAtomFeed(
153+
feedTitle,
154+
feedId,
155+
selfHref,
156+
entries
157+
);
158+
159+
// Marshal to XML
160+
marshaller.marshal(feed, new StreamResult(stream));
161+
}
162+
163+
/**
164+
* Convenience method for exporting UsagePoint entities.
165+
*
166+
* @param entity the usage point entity
167+
* @param stream the output stream
168+
*/
169+
public void exportUsagePoint(Object entity, OutputStream stream) {
170+
// TODO: Implement with proper type-safe casting when entity classes are fixed
171+
throw new UnsupportedOperationException("exportUsagePoint not yet implemented - requires entity class fixes");
172+
}
173+
174+
public void exportMeterReading(Object entity, OutputStream stream) {
175+
// TODO: Implement with proper type-safe casting when entity classes are fixed
176+
throw new UnsupportedOperationException("exportMeterReading not yet implemented - requires entity class fixes");
177+
}
178+
179+
public void exportIntervalBlock(Object entity, OutputStream stream) {
180+
// TODO: Implement with proper type-safe casting when entity classes are fixed
181+
throw new UnsupportedOperationException("exportIntervalBlock not yet implemented - requires entity class fixes");
182+
}
183+
184+
public void exportUsageSummary(Object entity, OutputStream stream) {
185+
// TODO: Implement with proper type-safe casting when entity classes are fixed
186+
throw new UnsupportedOperationException("exportUsageSummary not yet implemented - requires entity class fixes");
187+
}
188+
189+
public void exportElectricPowerQualitySummary(Object entity, OutputStream stream) {
190+
// TODO: Implement with proper type-safe casting when entity classes are fixed
191+
throw new UnsupportedOperationException("exportElectricPowerQualitySummary not yet implemented - requires entity class fixes");
192+
}
193+
194+
public void exportCustomer(Object entity, OutputStream stream) {
195+
// TODO: Implement with proper type-safe casting when entity classes are fixed
196+
throw new UnsupportedOperationException("exportCustomer not yet implemented - requires entity class fixes");
197+
}
198+
199+
/**
200+
* Creates an Atom entry wrapping a DTO resource.
201+
*
202+
* @param dto the DTO resource
203+
* @param resourceName the resource name
204+
* @param selfHref the self link href
205+
* @return the Atom entry
206+
*/
207+
private AtomEntryDto createAtomEntry(Object dto, String resourceName, String selfHref) {
208+
String entryId = generateEntryId(resourceName);
209+
String title = generateEntryTitle(resourceName);
210+
211+
AtomContentDto content = new AtomContentDto("application/xml", dto);
212+
213+
// Create self link
214+
LinkDto selfLink = new LinkDto(
215+
"self",
216+
selfHref,
217+
"application/atom+xml"
218+
);
219+
220+
return new AtomEntryDto(
221+
entryId,
222+
title,
223+
OffsetDateTime.now(),
224+
OffsetDateTime.now(),
225+
List.of(selfLink),
226+
content
227+
);
228+
}
229+
230+
/**
231+
* Creates an Atom feed containing multiple entries.
232+
*
233+
* @param title the feed title
234+
* @param feedId the feed ID
235+
* @param selfHref the feed self link
236+
* @param entries the entries to include
237+
* @return the Atom feed
238+
*/
239+
private AtomFeedDto createAtomFeed(
240+
String title,
241+
String feedId,
242+
String selfHref,
243+
List<AtomEntryDto> entries
244+
) {
245+
// Create self link for feed
246+
LinkDto selfLink = new LinkDto(
247+
"self",
248+
selfHref,
249+
"application/atom+xml"
250+
);
251+
252+
return new AtomFeedDto(
253+
feedId,
254+
title,
255+
OffsetDateTime.now(),
256+
OffsetDateTime.now(),
257+
List.of(selfLink),
258+
entries
259+
);
260+
}
261+
262+
/**
263+
* Generates a unique entry ID for Atom entries.
264+
*
265+
* @param resourceName the resource name
266+
* @return unique entry ID
267+
*/
268+
private String generateEntryId(String resourceName) {
269+
return "urn:uuid:" + resourceName.toLowerCase() + "-" + UUID.randomUUID();
270+
}
271+
272+
/**
273+
* Generates a human-readable entry title.
274+
*
275+
* @param resourceName the resource name
276+
* @return entry title
277+
*/
278+
private String generateEntryTitle(String resourceName) {
279+
return resourceName + " Entry";
280+
}
281+
282+
// Href generation methods (would be extracted to a separate service in production)
283+
284+
private String generateUsagePointHref(Object entity) {
285+
// Extract UUID from entity and generate href
286+
return "/espi/1_1/resource/UsagePoint/" + extractUuid(entity);
287+
}
288+
289+
private String generateMeterReadingHref(Object entity) {
290+
return "/espi/1_1/resource/MeterReading/" + extractUuid(entity);
291+
}
292+
293+
private String generateIntervalBlockHref(Object entity) {
294+
return "/espi/1_1/resource/IntervalBlock/" + extractUuid(entity);
295+
}
296+
297+
private String generateUsageSummaryHref(Object entity) {
298+
return "/espi/1_1/resource/UsageSummary/" + extractUuid(entity);
299+
}
300+
301+
private String generateElectricPowerQualitySummaryHref(Object entity) {
302+
return "/espi/1_1/resource/ElectricPowerQualitySummary/" + extractUuid(entity);
303+
}
304+
305+
private String generateCustomerHref(Object entity) {
306+
return "/espi/1_1/resource/Customer/" + extractUuid(entity);
307+
}
308+
309+
/**
310+
* Extracts UUID from entity using reflection (fallback method).
311+
* In production, this would use proper entity interfaces.
312+
*
313+
* @param entity the entity
314+
* @return the UUID string
315+
*/
316+
private String extractUuid(Object entity) {
317+
try {
318+
var method = entity.getClass().getMethod("getUuid");
319+
Object uuid = method.invoke(entity);
320+
return uuid != null ? uuid.toString() : "unknown";
321+
} catch (Exception e) {
322+
return "unknown-" + System.currentTimeMillis();
323+
}
324+
}
325+
}

0 commit comments

Comments
 (0)