1010import br .com .swconsultoria .nfe .dom .enuns .ServicosEnum ;
1111import br .com .swconsultoria .nfe .exception .NfeException ;
1212import lombok .extern .java .Log ;
13- import org .ini4j .Wini ;
1413
1514import java .io .*;
15+ import java .io .InputStreamReader ;
16+ import java .nio .charset .StandardCharsets ;
1617import java .util .logging .Logger ;
18+ import java .util .Map ;
19+ import java .util .HashMap ;
20+ import java .util .regex .Matcher ;
21+ import java .util .regex .Pattern ;
22+
1723
1824/**
1925 * @author Samuel Oliveira
2430public class WebServiceUtil {
2531
2632 private final static Logger logger = Logger .getLogger (WebServiceUtil .class .getName ());
33+ private static final Pattern sectionPattern = Pattern .compile ("^\\ [(.+)\\ ]$" );
34+
35+ /**
36+ * Obtém um valor de um Mapa que representa uma seção de um arquivo INI,
37+ * buscando pela {@code targetKey} de forma case-insensitive.
38+ * Este método também normaliza as chaves lidas do mapa (que vêm do arquivo INI)
39+ * que contêm "..", substituindo por ".", antes de realizar a comparação case-insensitive.
40+ *
41+ * @param sectionMap O Mapa ({@code Map<String, String>}) contendo os pares de chave-valor da seção específica.
42+ * Pode ser nulo ou vazio.
43+ * @param targetKey A chave alvo (geralmente esperada em lowercase, vinda de {@code ServicosEnum}, ou "Usar" em PascalCase)
44+ * a ser buscada dentro da seção.
45+ * @param logger O logger para registrar informações de depuração (ex: qual chave está sendo comparada).
46+ * @return O valor da propriedade como String, se uma correspondência case-insensitive for encontrada;
47+ * {@code null} caso contrário, ou se {@code sectionMap} for nulo/vazio, ou se {@code targetKey} for nula.
48+ */
49+ private static String getIniValueIgnoreCase (Map <String , String > sectionMap , String targetKey , Logger logger ) {
50+ if (sectionMap == null || sectionMap .isEmpty () || targetKey == null ) {
51+ return null ;
52+ }
53+ for (Map .Entry <String , String > entry : sectionMap .entrySet ()) {
54+ String keyFromIni = entry .getKey ();
55+ String normalizedKeyFromIni = keyFromIni .replace (".." , "." );
56+ String normalizedTargetKey = targetKey .replace (".." , "." );
57+ if (normalizedTargetKey .equalsIgnoreCase (normalizedKeyFromIni )) {
58+ return entry .getValue ();
59+ }
60+ }
61+ return null ;
62+ }
63+
64+ /**
65+ * Analisa (parse) um arquivo INI a partir de um {@link InputStream} e o carrega em uma estrutura de dados aninhada de Mapas.
66+ * O método lê o stream linha por linha, identificando seções (ex: {@code [NomeDaSecao]}),
67+ * pares de chave-valor (ex: {@code chave=valor} ou {@code chave:valor}), e linhas de comentário (iniciadas com ';' ou '#').
68+ * Espaços em branco ao redor de nomes de seção, chaves e valores são removidos (trim).
69+ * As seções e chaves são armazenadas preservando o case original do arquivo.
70+ *
71+ * @param inputStream O {@link InputStream} do arquivo INI a ser analisado. O stream é fechado ao final do parsing.
72+ * @return Um {@code Map<String, Map<String, String>>} representando os dados do INI.
73+ * A chave do mapa externo é o nome da seção. O valor é outro mapa contendo
74+ * os pares de chave-valor daquela seção.
75+ * @throws IOException Se ocorrer um erro de I/O durante a leitura do stream.
76+ * @throws NfeException Se forem encontradas linhas malformadas que não se encaixam no padrão esperado
77+ * de seção ou chave-valor (ex: nome de seção vazio em {@code []}, ou uma chave-valor fora de uma seção).
78+ */
79+ private static Map <String , Map <String , String >> parseIniFile (InputStream inputStream ) throws IOException , NfeException {
80+ Map <String , Map <String , String >> iniData = new HashMap <>();
81+ String currentSectionName = null ;
82+ Map <String , String > currentSectionMap = null ;
83+
84+ try (BufferedReader reader = new BufferedReader (new InputStreamReader (inputStream , StandardCharsets .UTF_8 ))) {
85+ String line ;
86+ while ((line = reader .readLine ()) != null ) {
87+ line = line .trim ();
88+
89+ if (line .isEmpty () || line .startsWith (";" ) || line .startsWith ("#" )) {
90+ continue ; // Skip empty lines and comments
91+ }
92+
93+ Matcher sectionMatcher = sectionPattern .matcher (line );
94+ if (sectionMatcher .matches ()) {
95+ // If currentSectionMap is not null and not empty, it means a previous section was being processed.
96+ // It's already in iniData, as we put it there when its name was found.
97+ currentSectionName = sectionMatcher .group (1 ).trim ();
98+ if (currentSectionName .isEmpty ()) {
99+ throw new NfeException ("Nome da seção inválido (vazio) no arquivo INI." );
100+ }
101+ // Ensure new section map is created, even if previous one with same name existed (though INI typically doesn't repeat sections)
102+ currentSectionMap = new HashMap <>();
103+ iniData .put (currentSectionName , currentSectionMap );
104+ } else {
105+ if (currentSectionName == null ) {
106+ // Property outside of any section - not expected for WebServicesNfe.ini
107+ // For now, we can log and ignore, or throw an exception.
108+ // Based on prompt, let's be strict for this specific INI structure.
109+ throw new NfeException ("Propriedade encontrada fora de uma seção: " + line );
110+ }
111+
112+ int separatorPos = -1 ;
113+ int equalsPos = line .indexOf ('=' );
114+ // According to INI standards, some parsers also accept ':' but '=' is more common.
115+ // The original ini4j might have handled both. For this custom parser, let's stick to '=' for simplicity
116+ // unless ':' is confirmed to be used in WebServicesNfe.ini for key-value.
117+ // A quick check of WebServicesNfe.ini shows only '='.
118+ separatorPos = equalsPos ;
119+
120+ if (separatorPos != -1 ) {
121+ String key = line .substring (0 , separatorPos ).trim ();
122+ String value = line .substring (separatorPos + 1 ).trim ();
123+ if (!key .isEmpty () && currentSectionMap != null ) {
124+ currentSectionMap .put (key , value );
125+ } else if (key .isEmpty ()){
126+ logger .warning ("Linha malformada (chave vazia): " + line );
127+ } else {
128+ // currentSectionMap should not be null here if currentSectionName is set.
129+ // This case implies currentSectionName was set, but currentSectionMap wasn't put in iniData or was null.
130+ // This should ideally not happen if logic is correct.
131+ logger .warning ("Tentativa de adicionar propriedade a uma seção nula: " + line );
132+ }
133+ } else {
134+ // Line is not a comment, not a section, and not a valid key-value pair.
135+ logger .warning ("Linha malformada ignorada: " + line );
136+ }
137+ }
138+ }
139+ }
140+ return iniData ;
141+ }
142+
27143
28144 /**
29145 * Retorna a URL para consulta de operações do SEFAZ.<br>
30146 *
31147 * <p>
32- * O método carrega o arquivo <b>WebServicesNfe.ini</b> que contêm as
33- * URL's de operações do SEFAZ, busca pela seção no arquivo .ini que
148+ * O método carrega o arquivo <b>WebServicesNfe.ini</b> (utilizando um parser customizado)
149+ * que contêm as URL's de operações do SEFAZ, busca pela seção no arquivo .ini que
34150 * corresponda com os argumentos <b>tipo</b>, <b>config</b>, <b>servico</b>
35151 * e retorna essa URL.
36152 * </p>
37153 *
38154 * @param config interface que contêm os dados necessários para a comunicação.
39- * @param tipoDocumento DocumentoEnum. NFE e ConstantesUtil. NFCE
155+ * @param tipoDocumento {@link DocumentoEnum# NFE} ou {@link DocumentoEnum# NFCE}.
40156 * @param tipoServico é a operação que se deseja fazer.<br>
41157 * Ex.: para consultas status deserviço no ambiente de produção
42158 * use ServicosEnum.NfeStatusServico_4.00
@@ -45,84 +161,96 @@ public class WebServiceUtil {
45161 * @throws NfeException
46162 *
47163 * @see ConfiguracoesNfe
48- * @see ConstantesUtil
49- **/
164+ */
50165 public static String getUrl (ConfiguracoesNfe config , DocumentoEnum tipoDocumento , ServicosEnum tipoServico ) throws NfeException {
51-
166+ InputStream is = null ;
167+ Map <String , Map <String , String >> iniData ;
52168 try {
53-
54- String secao = tipoDocumento .getTipo () + "_" + config .getEstado () + "_"
55- + (config .getAmbiente ().equals (AmbienteEnum .HOMOLOGACAO ) ? "H" : "P" );
56-
57- InputStream is ;
58169 if (ObjetoUtil .verifica (config .getArquivoWebService ()).isPresent ()) {
59170 File arquivo = new File (config .getArquivoWebService ());
60- if (!arquivo .exists ())
61- throw new FileNotFoundException ("Arquivo WebService" + config .getArquivoWebService () + " não encontrado" );
171+ if (!arquivo .exists ()) {
172+ throw new FileNotFoundException ("Arquivo WebService " + config .getArquivoWebService () + " não encontrado" );
173+ }
62174 is = new FileInputStream (arquivo );
63- log .info ("[ARQUIVO INI CUSTOMIZADO]: " + config .getArquivoWebService ());
175+ logger .info ("[ARQUIVO INI CUSTOMIZADO]: " + config .getArquivoWebService ());
64176 } else {
65177 is = WebServiceUtil .class .getResourceAsStream ("/WebServicesNfe.ini" );
178+ if (is == null ) {
179+ throw new NfeException ("Arquivo WebServicesNfe.ini não encontrado no classpath." );
180+ }
66181 }
67-
68- Wini ini = new Wini ();
69- ini .getConfig ().setLowerCaseOption (true );
70- ini .load (is );
71- is .close ();
72- String url = ini .get (secao , "usar" );
73-
74- //URLS CONSULTA CADASTO
75- if (tipoServico .equals (ServicosEnum .CONSULTA_CADASTRO ) && (
76- config .getEstado ().equals (EstadosEnum .PA ) ||
77- config .getEstado ().equals (EstadosEnum .AM ) ||
78- config .getEstado ().equals (EstadosEnum .AL ) ||
79- config .getEstado ().equals (EstadosEnum .AP ) ||
80- config .getEstado ().equals (EstadosEnum .DF ) ||
81- config .getEstado ().equals (EstadosEnum .PI ) ||
82- config .getEstado ().equals (EstadosEnum .RJ ) ||
83- config .getEstado ().equals (EstadosEnum .RO ) ||
84- config .getEstado ().equals (EstadosEnum .SE ) ||
85- config .getEstado ().equals (EstadosEnum .TO ))) {
86- throw new NfeException ("Estado não possui Consulta Cadastro." );
87- // URLS de ambiente nacional
88- } else if (tipoServico .equals (ServicosEnum .DISTRIBUICAO_DFE )
89- || tipoServico .equals (ServicosEnum .MANIFESTACAO )
90- || tipoServico .equals (ServicosEnum .EPEC )) {
91- secao = config .getAmbiente ().equals (AmbienteEnum .HOMOLOGACAO ) ? "NFe_AN_H" : "NFe_AN_P" ;
92- } else if (!tipoServico .equals (ServicosEnum .URL_CONSULTANFCE )
93- && !tipoServico .equals (ServicosEnum .URL_QRCODE )
94- && config .isContigenciaSVC () && tipoDocumento .equals (DocumentoEnum .NFE )) {
95- // SVC-RS
96- if (config .getEstado ().equals (EstadosEnum .GO ) || config .getEstado ().equals (EstadosEnum .AM )
97- || config .getEstado ().equals (EstadosEnum .BA ) || config .getEstado ().equals (EstadosEnum .CE )
98- || config .getEstado ().equals (EstadosEnum .MA ) || config .getEstado ().equals (EstadosEnum .MS )
99- || config .getEstado ().equals (EstadosEnum .MT ) || config .getEstado ().equals (EstadosEnum .PA )
100- || config .getEstado ().equals (EstadosEnum .PE ) || config .getEstado ().equals (EstadosEnum .PI )
101- || config .getEstado ().equals (EstadosEnum .PR )) {
102- secao = tipoDocumento .getTipo () + "_SVRS_"
103- + (config .getAmbiente ().equals (AmbienteEnum .HOMOLOGACAO ) ? "H" : "P" );
104- // SVC-AN
105- } else {
106- secao = tipoDocumento .getTipo () + "_SVC-AN_"
107- + (config .getAmbiente ().equals (AmbienteEnum .HOMOLOGACAO ) ? "H" : "P" );
182+ iniData = parseIniFile (is );
183+ } catch (IOException e ) {
184+ throw new NfeException ("Erro ao carregar arquivo de configuração WebService: " + e .getMessage (), e );
185+ } finally {
186+ if (is != null ) {
187+ try {
188+ is .close ();
189+ } catch (IOException e ) {
190+ logger .fine ("Erro ao fechar InputStream: " + e .getMessage ());
108191 }
109- }else if (!tipoServico .equals (ServicosEnum .URL_CONSULTANFCE )
110- && !tipoServico .equals (ServicosEnum .URL_QRCODE ) && ObjetoUtil .verifica (url ).isPresent ()) {
111- secao = url ;
112192 }
193+ }
113194
114- url = ini .get (secao , tipoServico .getServico ().toLowerCase ());
115-
116- ObjetoUtil .verifica (url ).orElseThrow (() -> new NfeException (
117- "WebService de " + tipoServico + " não encontrado para " + config .getEstado ().getNome ()));
195+ String initialSecaoKey = tipoDocumento .getTipo () + "_" + config .getEstado () + "_"
196+ + (config .getAmbiente ().equals (AmbienteEnum .HOMOLOGACAO ) ? "H" : "P" );
118197
119- log .info ("[URL]: " + tipoServico + ": " + url );
198+ String lookupSectionKey = initialSecaoKey ;
199+ Map <String , String > initialSectionMap = iniData .get (initialSecaoKey );
200+ // Pass the static logger from the class to the helper method
201+ String usarValue = getIniValueIgnoreCase (initialSectionMap , "Usar" , logger );
120202
121- return url ;
203+ String finalUrl = null ;
122204
123- } catch (IOException e ) {
124- throw new NfeException (e .getMessage (),e );
205+ if (tipoServico .equals (ServicosEnum .CONSULTA_CADASTRO ) && (
206+ config .getEstado ().equals (EstadosEnum .PA ) ||
207+ config .getEstado ().equals (EstadosEnum .AM ) ||
208+ config .getEstado ().equals (EstadosEnum .AL ) ||
209+ config .getEstado ().equals (EstadosEnum .AP ) ||
210+ config .getEstado ().equals (EstadosEnum .DF ) ||
211+ config .getEstado ().equals (EstadosEnum .PI ) ||
212+ config .getEstado ().equals (EstadosEnum .RJ ) ||
213+ config .getEstado ().equals (EstadosEnum .RO ) ||
214+ config .getEstado ().equals (EstadosEnum .SE ) ||
215+ config .getEstado ().equals (EstadosEnum .TO ))) {
216+ throw new NfeException ("Estado não possui Consulta Cadastro." );
217+ } else if (tipoServico .equals (ServicosEnum .DISTRIBUICAO_DFE ) ||
218+ tipoServico .equals (ServicosEnum .MANIFESTACAO ) ||
219+ tipoServico .equals (ServicosEnum .EPEC )) {
220+ lookupSectionKey = config .getAmbiente ().equals (AmbienteEnum .HOMOLOGACAO ) ? "NFe_AN_H" : "NFe_AN_P" ;
221+ Map <String , String > nationalSectionMap = iniData .get (lookupSectionKey );
222+ finalUrl = getIniValueIgnoreCase (nationalSectionMap , tipoServico .getServico (), logger );
223+ } else if (!tipoServico .equals (ServicosEnum .URL_CONSULTANFCE ) &&
224+ !tipoServico .equals (ServicosEnum .URL_QRCODE ) &&
225+ config .isContigenciaSVC () && tipoDocumento .equals (DocumentoEnum .NFE )) {
226+ if (config .getEstado ().equals (EstadosEnum .GO ) || config .getEstado ().equals (EstadosEnum .AM ) ||
227+ config .getEstado ().equals (EstadosEnum .BA ) || config .getEstado ().equals (EstadosEnum .CE ) ||
228+ config .getEstado ().equals (EstadosEnum .MA ) || config .getEstado ().equals (EstadosEnum .MS ) ||
229+ config .getEstado ().equals (EstadosEnum .MT ) || config .getEstado ().equals (EstadosEnum .PA ) ||
230+ config .getEstado ().equals (EstadosEnum .PE ) || config .getEstado ().equals (EstadosEnum .PI ) ||
231+ config .getEstado ().equals (EstadosEnum .PR )) {
232+ lookupSectionKey = tipoDocumento .getTipo () + "_SVRS_" + (config .getAmbiente ().equals (AmbienteEnum .HOMOLOGACAO ) ? "H" : "P" );
233+ } else {
234+ lookupSectionKey = tipoDocumento .getTipo () + "_SVC-AN_" + (config .getAmbiente ().equals (AmbienteEnum .HOMOLOGACAO ) ? "H" : "P" );
235+ }
236+ Map <String , String > svcSectionMap = iniData .get (lookupSectionKey );
237+ finalUrl = getIniValueIgnoreCase (svcSectionMap , tipoServico .getServico (), logger );
238+ } else if (ObjetoUtil .verifica (usarValue ).isPresent () &&
239+ !tipoServico .equals (ServicosEnum .URL_CONSULTANFCE ) &&
240+ !tipoServico .equals (ServicosEnum .URL_QRCODE )) {
241+ lookupSectionKey = usarValue ;
242+ Map <String , String > usarRedirectedSectionMap = iniData .get (lookupSectionKey );
243+ finalUrl = getIniValueIgnoreCase (usarRedirectedSectionMap , tipoServico .getServico (), logger );
244+ } else {
245+ Map <String , String > currentSectionMap = iniData .get (lookupSectionKey );
246+ finalUrl = getIniValueIgnoreCase (currentSectionMap , tipoServico .getServico (), logger );
125247 }
126248
249+ final String finalLookupSectionKeyForLambda = lookupSectionKey ;
250+ ObjetoUtil .verifica (finalUrl ).orElseThrow (() -> new NfeException (
251+ "WebService de " + tipoServico + " não encontrado para " + config .getEstado ().getNome () + " na seção " + finalLookupSectionKeyForLambda ));
252+
253+ logger .info ("[URL]: " + tipoServico + ": " + finalUrl );
254+ return finalUrl ;
127255 }
128256}
0 commit comments