feat: Add Official Microsoft & Gemini Skills (845+ Total)
🚀 Impact Significantly expands the capabilities of **Antigravity Awesome Skills** by integrating official skill collections from **Microsoft** and **Google Gemini**. This update increases the total skill count to **845+**, making the library even more comprehensive for AI coding assistants. ✨ Key Changes 1. New Official Skills - **Microsoft Skills**: Added a massive collection of official skills from [microsoft/skills](https://github.com/microsoft/skills). - Includes Azure, .NET, Python, TypeScript, and Semantic Kernel skills. - Preserves the original directory structure under `skills/official/microsoft/`. - Includes plugin skills from the `.github/plugins` directory. - **Gemini Skills**: Added official Gemini API development skills under `skills/gemini-api-dev/`. 2. New Scripts & Tooling - **`scripts/sync_microsoft_skills.py`**: A robust synchronization script that: - Clones the official Microsoft repository. - Preserves the original directory heirarchy. - Handles symlinks and plugin locations. - Generates attribution metadata. - **`scripts/tests/inspect_microsoft_repo.py`**: Debug tool to inspect the remote repository structure. - **`scripts/tests/test_comprehensive_coverage.py`**: Verification script to ensure 100% of skills are captured during sync. 3. Core Improvements - **`scripts/generate_index.py`**: Enhanced frontmatter parsing to safely handle unquoted values containing `@` symbols and commas (fixing issues with some Microsoft skill descriptions). - **`package.json`**: Added `sync:microsoft` and `sync:all-official` scripts for easy maintenance. 4. Documentation - Updated `README.md` to reflect the new skill counts (845+) and added Microsoft/Gemini to the provider list. - Updated `CATALOG.md` and `skills_index.json` with the new skills. 🧪 Verification - Ran `scripts/tests/test_comprehensive_coverage.py` to verify all Microsoft skills are detected. - Validated `generate_index.py` fixes by successfully indexing the new skills.
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
---
|
||||
name: azure-communication-callautomation-java
|
||||
description: Build call automation workflows with Azure Communication Services Call Automation Java SDK. Use when implementing IVR systems, call routing, call recording, DTMF recognition, text-to-speech, or AI-powered call flows.
|
||||
package: com.azure:azure-communication-callautomation
|
||||
---
|
||||
|
||||
# Azure Communication Call Automation (Java)
|
||||
|
||||
Build server-side call automation workflows including IVR systems, call routing, recording, and AI-powered interactions.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-communication-callautomation</artifactId>
|
||||
<version>1.6.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
```java
|
||||
import com.azure.communication.callautomation.CallAutomationClient;
|
||||
import com.azure.communication.callautomation.CallAutomationClientBuilder;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
// With DefaultAzureCredential
|
||||
CallAutomationClient client = new CallAutomationClientBuilder()
|
||||
.endpoint("https://<resource>.communication.azure.com")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
|
||||
// With connection string
|
||||
CallAutomationClient client = new CallAutomationClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Class | Purpose |
|
||||
|-------|---------|
|
||||
| `CallAutomationClient` | Make calls, answer/reject incoming calls, redirect calls |
|
||||
| `CallConnection` | Actions in established calls (add participants, terminate) |
|
||||
| `CallMedia` | Media operations (play audio, recognize DTMF/speech) |
|
||||
| `CallRecording` | Start/stop/pause recording |
|
||||
| `CallAutomationEventParser` | Parse webhook events from ACS |
|
||||
|
||||
## Create Outbound Call
|
||||
|
||||
```java
|
||||
import com.azure.communication.callautomation.models.*;
|
||||
import com.azure.communication.common.CommunicationUserIdentifier;
|
||||
import com.azure.communication.common.PhoneNumberIdentifier;
|
||||
|
||||
// Call to PSTN number
|
||||
PhoneNumberIdentifier target = new PhoneNumberIdentifier("+14255551234");
|
||||
PhoneNumberIdentifier caller = new PhoneNumberIdentifier("+14255550100");
|
||||
|
||||
CreateCallOptions options = new CreateCallOptions(
|
||||
new CommunicationUserIdentifier("<user-id>"), // Source
|
||||
List.of(target)) // Targets
|
||||
.setSourceCallerId(caller)
|
||||
.setCallbackUrl("https://your-app.com/api/callbacks");
|
||||
|
||||
CreateCallResult result = client.createCall(options);
|
||||
String callConnectionId = result.getCallConnectionProperties().getCallConnectionId();
|
||||
```
|
||||
|
||||
## Answer Incoming Call
|
||||
|
||||
```java
|
||||
// From Event Grid webhook - IncomingCall event
|
||||
String incomingCallContext = "<incoming-call-context-from-event>";
|
||||
|
||||
AnswerCallOptions options = new AnswerCallOptions(
|
||||
incomingCallContext,
|
||||
"https://your-app.com/api/callbacks");
|
||||
|
||||
AnswerCallResult result = client.answerCall(options);
|
||||
CallConnection callConnection = result.getCallConnection();
|
||||
```
|
||||
|
||||
## Play Audio (Text-to-Speech)
|
||||
|
||||
```java
|
||||
CallConnection callConnection = client.getCallConnection(callConnectionId);
|
||||
CallMedia callMedia = callConnection.getCallMedia();
|
||||
|
||||
// Play text-to-speech
|
||||
TextSource textSource = new TextSource()
|
||||
.setText("Welcome to Contoso. Press 1 for sales, 2 for support.")
|
||||
.setVoiceName("en-US-JennyNeural");
|
||||
|
||||
PlayOptions playOptions = new PlayOptions(
|
||||
List.of(textSource),
|
||||
List.of(new CommunicationUserIdentifier("<target-user>")));
|
||||
|
||||
callMedia.play(playOptions);
|
||||
|
||||
// Play audio file
|
||||
FileSource fileSource = new FileSource()
|
||||
.setUrl("https://storage.blob.core.windows.net/audio/greeting.wav");
|
||||
|
||||
callMedia.play(new PlayOptions(List.of(fileSource), List.of(target)));
|
||||
```
|
||||
|
||||
## Recognize DTMF Input
|
||||
|
||||
```java
|
||||
// Recognize DTMF tones
|
||||
DtmfTone stopTones = DtmfTone.POUND;
|
||||
|
||||
CallMediaRecognizeDtmfOptions recognizeOptions = new CallMediaRecognizeDtmfOptions(
|
||||
new CommunicationUserIdentifier("<target-user>"),
|
||||
5) // Max tones to collect
|
||||
.setInterToneTimeout(Duration.ofSeconds(5))
|
||||
.setStopTones(List.of(stopTones))
|
||||
.setInitialSilenceTimeout(Duration.ofSeconds(15))
|
||||
.setPlayPrompt(new TextSource().setText("Enter your account number followed by pound."));
|
||||
|
||||
callMedia.startRecognizing(recognizeOptions);
|
||||
```
|
||||
|
||||
## Recognize Speech
|
||||
|
||||
```java
|
||||
// Speech recognition with AI
|
||||
CallMediaRecognizeSpeechOptions speechOptions = new CallMediaRecognizeSpeechOptions(
|
||||
new CommunicationUserIdentifier("<target-user>"))
|
||||
.setEndSilenceTimeout(Duration.ofSeconds(2))
|
||||
.setSpeechLanguage("en-US")
|
||||
.setPlayPrompt(new TextSource().setText("How can I help you today?"));
|
||||
|
||||
callMedia.startRecognizing(speechOptions);
|
||||
```
|
||||
|
||||
## Call Recording
|
||||
|
||||
```java
|
||||
CallRecording callRecording = client.getCallRecording();
|
||||
|
||||
// Start recording
|
||||
StartRecordingOptions recordingOptions = new StartRecordingOptions(
|
||||
new ServerCallLocator("<server-call-id>"))
|
||||
.setRecordingChannel(RecordingChannel.MIXED)
|
||||
.setRecordingContent(RecordingContent.AUDIO_VIDEO)
|
||||
.setRecordingFormat(RecordingFormat.MP4);
|
||||
|
||||
RecordingStateResult recordingResult = callRecording.start(recordingOptions);
|
||||
String recordingId = recordingResult.getRecordingId();
|
||||
|
||||
// Pause/resume/stop
|
||||
callRecording.pause(recordingId);
|
||||
callRecording.resume(recordingId);
|
||||
callRecording.stop(recordingId);
|
||||
|
||||
// Download recording (after RecordingFileStatusUpdated event)
|
||||
callRecording.downloadTo(recordingUrl, Paths.get("recording.mp4"));
|
||||
```
|
||||
|
||||
## Add Participant to Call
|
||||
|
||||
```java
|
||||
CallConnection callConnection = client.getCallConnection(callConnectionId);
|
||||
|
||||
CommunicationUserIdentifier participant = new CommunicationUserIdentifier("<user-id>");
|
||||
AddParticipantOptions addOptions = new AddParticipantOptions(participant)
|
||||
.setInvitationTimeout(Duration.ofSeconds(30));
|
||||
|
||||
AddParticipantResult result = callConnection.addParticipant(addOptions);
|
||||
```
|
||||
|
||||
## Transfer Call
|
||||
|
||||
```java
|
||||
// Blind transfer
|
||||
PhoneNumberIdentifier transferTarget = new PhoneNumberIdentifier("+14255559999");
|
||||
TransferCallToParticipantResult result = callConnection.transferCallToParticipant(transferTarget);
|
||||
```
|
||||
|
||||
## Handle Events (Webhook)
|
||||
|
||||
```java
|
||||
import com.azure.communication.callautomation.CallAutomationEventParser;
|
||||
import com.azure.communication.callautomation.models.events.*;
|
||||
|
||||
// In your webhook endpoint
|
||||
public void handleCallback(String requestBody) {
|
||||
List<CallAutomationEventBase> events = CallAutomationEventParser.parseEvents(requestBody);
|
||||
|
||||
for (CallAutomationEventBase event : events) {
|
||||
if (event instanceof CallConnected) {
|
||||
CallConnected connected = (CallConnected) event;
|
||||
System.out.println("Call connected: " + connected.getCallConnectionId());
|
||||
} else if (event instanceof RecognizeCompleted) {
|
||||
RecognizeCompleted recognized = (RecognizeCompleted) event;
|
||||
// Handle DTMF or speech recognition result
|
||||
DtmfResult dtmfResult = (DtmfResult) recognized.getRecognizeResult();
|
||||
String tones = dtmfResult.getTones().stream()
|
||||
.map(DtmfTone::toString)
|
||||
.collect(Collectors.joining());
|
||||
System.out.println("DTMF received: " + tones);
|
||||
} else if (event instanceof PlayCompleted) {
|
||||
System.out.println("Audio playback completed");
|
||||
} else if (event instanceof CallDisconnected) {
|
||||
System.out.println("Call ended");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Hang Up Call
|
||||
|
||||
```java
|
||||
// Hang up for all participants
|
||||
callConnection.hangUp(true);
|
||||
|
||||
// Hang up only this leg
|
||||
callConnection.hangUp(false);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
client.answerCall(options);
|
||||
} catch (HttpResponseException e) {
|
||||
if (e.getResponse().getStatusCode() == 404) {
|
||||
System.out.println("Call not found or already ended");
|
||||
} else if (e.getResponse().getStatusCode() == 400) {
|
||||
System.out.println("Invalid request: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_COMMUNICATION_ENDPOINT=https://<resource>.communication.azure.com
|
||||
AZURE_COMMUNICATION_CONNECTION_STRING=endpoint=https://...;accesskey=...
|
||||
CALLBACK_BASE_URL=https://your-app.com/api/callbacks
|
||||
```
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "call automation Java", "IVR Java", "interactive voice response"
|
||||
- "call recording Java", "DTMF recognition Java"
|
||||
- "text to speech call", "speech recognition call"
|
||||
- "answer incoming call", "transfer call Java"
|
||||
- "Azure Communication Services call automation"
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
name: azure-communication-callingserver-java
|
||||
description: Azure Communication Services CallingServer (legacy) Java SDK. Note - This SDK is deprecated. Use azure-communication-callautomation instead for new projects. Only use this skill when maintaining legacy code.
|
||||
package: com.azure:azure-communication-callingserver
|
||||
---
|
||||
|
||||
# Azure Communication CallingServer (Java) - DEPRECATED
|
||||
|
||||
> **⚠️ DEPRECATED**: This SDK has been renamed to **Call Automation**. For new projects, use `azure-communication-callautomation` instead. This skill is for maintaining legacy code only.
|
||||
|
||||
## Migration to Call Automation
|
||||
|
||||
```xml
|
||||
<!-- OLD (deprecated) -->
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-communication-callingserver</artifactId>
|
||||
<version>1.0.0-beta.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- NEW (use this instead) -->
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-communication-callautomation</artifactId>
|
||||
<version>1.6.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Class Name Changes
|
||||
|
||||
| CallingServer (Old) | Call Automation (New) |
|
||||
|---------------------|----------------------|
|
||||
| `CallingServerClient` | `CallAutomationClient` |
|
||||
| `CallingServerClientBuilder` | `CallAutomationClientBuilder` |
|
||||
| `CallConnection` | `CallConnection` (same) |
|
||||
| `ServerCall` | Removed - use `CallConnection` |
|
||||
|
||||
## Legacy Client Creation
|
||||
|
||||
```java
|
||||
// OLD WAY (deprecated)
|
||||
import com.azure.communication.callingserver.CallingServerClient;
|
||||
import com.azure.communication.callingserver.CallingServerClientBuilder;
|
||||
|
||||
CallingServerClient client = new CallingServerClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.buildClient();
|
||||
|
||||
// NEW WAY
|
||||
import com.azure.communication.callautomation.CallAutomationClient;
|
||||
import com.azure.communication.callautomation.CallAutomationClientBuilder;
|
||||
|
||||
CallAutomationClient client = new CallAutomationClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Legacy Recording
|
||||
|
||||
```java
|
||||
// OLD WAY
|
||||
StartRecordingOptions options = new StartRecordingOptions(serverCallId)
|
||||
.setRecordingStateCallbackUri(callbackUri);
|
||||
|
||||
StartCallRecordingResult result = client.startRecording(options);
|
||||
String recordingId = result.getRecordingId();
|
||||
|
||||
client.pauseRecording(recordingId);
|
||||
client.resumeRecording(recordingId);
|
||||
client.stopRecording(recordingId);
|
||||
|
||||
// NEW WAY - see azure-communication-callautomation skill
|
||||
```
|
||||
|
||||
## For New Development
|
||||
|
||||
**Do not use this SDK for new projects.**
|
||||
|
||||
See the `azure-communication-callautomation-java` skill for:
|
||||
- Making outbound calls
|
||||
- Answering incoming calls
|
||||
- Call recording
|
||||
- DTMF recognition
|
||||
- Text-to-speech / speech-to-text
|
||||
- Adding/removing participants
|
||||
- Call transfer
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "callingserver legacy", "deprecated calling SDK"
|
||||
- "migrate callingserver to callautomation"
|
||||
310
skills/official/microsoft/java/communication/chat/SKILL.md
Normal file
310
skills/official/microsoft/java/communication/chat/SKILL.md
Normal file
@@ -0,0 +1,310 @@
|
||||
---
|
||||
name: azure-communication-chat-java
|
||||
description: Build real-time chat applications with Azure Communication Services Chat Java SDK. Use when implementing chat threads, messaging, participants, read receipts, typing notifications, or real-time chat features.
|
||||
package: com.azure:azure-communication-chat
|
||||
---
|
||||
|
||||
# Azure Communication Chat (Java)
|
||||
|
||||
Build real-time chat applications with thread management, messaging, participants, and read receipts.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-communication-chat</artifactId>
|
||||
<version>1.6.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
```java
|
||||
import com.azure.communication.chat.ChatClient;
|
||||
import com.azure.communication.chat.ChatClientBuilder;
|
||||
import com.azure.communication.chat.ChatThreadClient;
|
||||
import com.azure.communication.common.CommunicationTokenCredential;
|
||||
|
||||
// ChatClient requires a CommunicationTokenCredential (user access token)
|
||||
String endpoint = "https://<resource>.communication.azure.com";
|
||||
String userAccessToken = "<user-access-token>";
|
||||
|
||||
CommunicationTokenCredential credential = new CommunicationTokenCredential(userAccessToken);
|
||||
|
||||
ChatClient chatClient = new ChatClientBuilder()
|
||||
.endpoint(endpoint)
|
||||
.credential(credential)
|
||||
.buildClient();
|
||||
|
||||
// Async client
|
||||
ChatAsyncClient chatAsyncClient = new ChatClientBuilder()
|
||||
.endpoint(endpoint)
|
||||
.credential(credential)
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Class | Purpose |
|
||||
|-------|---------|
|
||||
| `ChatClient` | Create/delete chat threads, get thread clients |
|
||||
| `ChatThreadClient` | Operations within a thread (messages, participants, receipts) |
|
||||
| `ChatParticipant` | User in a chat thread with display name |
|
||||
| `ChatMessage` | Message content, type, sender info, timestamps |
|
||||
| `ChatMessageReadReceipt` | Read receipt tracking per participant |
|
||||
|
||||
## Create Chat Thread
|
||||
|
||||
```java
|
||||
import com.azure.communication.chat.models.*;
|
||||
import com.azure.communication.common.CommunicationUserIdentifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// Define participants
|
||||
List<ChatParticipant> participants = new ArrayList<>();
|
||||
|
||||
ChatParticipant participant1 = new ChatParticipant()
|
||||
.setCommunicationIdentifier(new CommunicationUserIdentifier("<user-id-1>"))
|
||||
.setDisplayName("Alice");
|
||||
|
||||
ChatParticipant participant2 = new ChatParticipant()
|
||||
.setCommunicationIdentifier(new CommunicationUserIdentifier("<user-id-2>"))
|
||||
.setDisplayName("Bob");
|
||||
|
||||
participants.add(participant1);
|
||||
participants.add(participant2);
|
||||
|
||||
// Create thread
|
||||
CreateChatThreadOptions options = new CreateChatThreadOptions("Project Discussion")
|
||||
.setParticipants(participants);
|
||||
|
||||
CreateChatThreadResult result = chatClient.createChatThread(options);
|
||||
String threadId = result.getChatThread().getId();
|
||||
|
||||
// Get thread client for operations
|
||||
ChatThreadClient threadClient = chatClient.getChatThreadClient(threadId);
|
||||
```
|
||||
|
||||
## Send Messages
|
||||
|
||||
```java
|
||||
// Send text message
|
||||
SendChatMessageOptions messageOptions = new SendChatMessageOptions()
|
||||
.setContent("Hello, team!")
|
||||
.setSenderDisplayName("Alice")
|
||||
.setType(ChatMessageType.TEXT);
|
||||
|
||||
SendChatMessageResult sendResult = threadClient.sendMessage(messageOptions);
|
||||
String messageId = sendResult.getId();
|
||||
|
||||
// Send HTML message
|
||||
SendChatMessageOptions htmlOptions = new SendChatMessageOptions()
|
||||
.setContent("<strong>Important:</strong> Meeting at 3pm")
|
||||
.setType(ChatMessageType.HTML);
|
||||
|
||||
threadClient.sendMessage(htmlOptions);
|
||||
```
|
||||
|
||||
## Get Messages
|
||||
|
||||
```java
|
||||
import com.azure.core.util.paging.PagedIterable;
|
||||
|
||||
// List all messages
|
||||
PagedIterable<ChatMessage> messages = threadClient.listMessages();
|
||||
|
||||
for (ChatMessage message : messages) {
|
||||
System.out.println("ID: " + message.getId());
|
||||
System.out.println("Type: " + message.getType());
|
||||
System.out.println("Content: " + message.getContent().getMessage());
|
||||
System.out.println("Sender: " + message.getSenderDisplayName());
|
||||
System.out.println("Created: " + message.getCreatedOn());
|
||||
|
||||
// Check if edited or deleted
|
||||
if (message.getEditedOn() != null) {
|
||||
System.out.println("Edited: " + message.getEditedOn());
|
||||
}
|
||||
if (message.getDeletedOn() != null) {
|
||||
System.out.println("Deleted: " + message.getDeletedOn());
|
||||
}
|
||||
}
|
||||
|
||||
// Get specific message
|
||||
ChatMessage message = threadClient.getMessage(messageId);
|
||||
```
|
||||
|
||||
## Update and Delete Messages
|
||||
|
||||
```java
|
||||
// Update message
|
||||
UpdateChatMessageOptions updateOptions = new UpdateChatMessageOptions()
|
||||
.setContent("Updated message content");
|
||||
|
||||
threadClient.updateMessage(messageId, updateOptions);
|
||||
|
||||
// Delete message
|
||||
threadClient.deleteMessage(messageId);
|
||||
```
|
||||
|
||||
## Manage Participants
|
||||
|
||||
```java
|
||||
// List participants
|
||||
PagedIterable<ChatParticipant> participants = threadClient.listParticipants();
|
||||
|
||||
for (ChatParticipant participant : participants) {
|
||||
CommunicationUserIdentifier user =
|
||||
(CommunicationUserIdentifier) participant.getCommunicationIdentifier();
|
||||
System.out.println("User: " + user.getId());
|
||||
System.out.println("Display Name: " + participant.getDisplayName());
|
||||
}
|
||||
|
||||
// Add participants
|
||||
List<ChatParticipant> newParticipants = new ArrayList<>();
|
||||
newParticipants.add(new ChatParticipant()
|
||||
.setCommunicationIdentifier(new CommunicationUserIdentifier("<new-user-id>"))
|
||||
.setDisplayName("Charlie")
|
||||
.setShareHistoryTime(OffsetDateTime.now().minusDays(7))); // Share last 7 days
|
||||
|
||||
threadClient.addParticipants(newParticipants);
|
||||
|
||||
// Remove participant
|
||||
CommunicationUserIdentifier userToRemove = new CommunicationUserIdentifier("<user-id>");
|
||||
threadClient.removeParticipant(userToRemove);
|
||||
```
|
||||
|
||||
## Read Receipts
|
||||
|
||||
```java
|
||||
// Send read receipt
|
||||
threadClient.sendReadReceipt(messageId);
|
||||
|
||||
// Get read receipts
|
||||
PagedIterable<ChatMessageReadReceipt> receipts = threadClient.listReadReceipts();
|
||||
|
||||
for (ChatMessageReadReceipt receipt : receipts) {
|
||||
System.out.println("Message ID: " + receipt.getChatMessageId());
|
||||
System.out.println("Read by: " + receipt.getSenderCommunicationIdentifier());
|
||||
System.out.println("Read at: " + receipt.getReadOn());
|
||||
}
|
||||
```
|
||||
|
||||
## Typing Notifications
|
||||
|
||||
```java
|
||||
import com.azure.communication.chat.models.TypingNotificationOptions;
|
||||
|
||||
// Send typing notification
|
||||
TypingNotificationOptions typingOptions = new TypingNotificationOptions()
|
||||
.setSenderDisplayName("Alice");
|
||||
|
||||
threadClient.sendTypingNotificationWithResponse(typingOptions, Context.NONE);
|
||||
|
||||
// Simple typing notification
|
||||
threadClient.sendTypingNotification();
|
||||
```
|
||||
|
||||
## Thread Operations
|
||||
|
||||
```java
|
||||
// Get thread properties
|
||||
ChatThreadProperties properties = threadClient.getProperties();
|
||||
System.out.println("Topic: " + properties.getTopic());
|
||||
System.out.println("Created: " + properties.getCreatedOn());
|
||||
|
||||
// Update topic
|
||||
threadClient.updateTopic("New Project Discussion Topic");
|
||||
|
||||
// Delete thread
|
||||
chatClient.deleteChatThread(threadId);
|
||||
```
|
||||
|
||||
## List Threads
|
||||
|
||||
```java
|
||||
// List all chat threads for the user
|
||||
PagedIterable<ChatThreadItem> threads = chatClient.listChatThreads();
|
||||
|
||||
for (ChatThreadItem thread : threads) {
|
||||
System.out.println("Thread ID: " + thread.getId());
|
||||
System.out.println("Topic: " + thread.getTopic());
|
||||
System.out.println("Last message: " + thread.getLastMessageReceivedOn());
|
||||
}
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
```java
|
||||
import com.azure.core.http.rest.PagedResponse;
|
||||
|
||||
// Paginate through messages
|
||||
int maxPageSize = 10;
|
||||
ListChatMessagesOptions listOptions = new ListChatMessagesOptions()
|
||||
.setMaxPageSize(maxPageSize);
|
||||
|
||||
PagedIterable<ChatMessage> pagedMessages = threadClient.listMessages(listOptions);
|
||||
|
||||
pagedMessages.iterableByPage().forEach(page -> {
|
||||
System.out.println("Page status code: " + page.getStatusCode());
|
||||
page.getElements().forEach(msg ->
|
||||
System.out.println("Message: " + msg.getContent().getMessage()));
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
threadClient.sendMessage(messageOptions);
|
||||
} catch (HttpResponseException e) {
|
||||
switch (e.getResponse().getStatusCode()) {
|
||||
case 401:
|
||||
System.out.println("Unauthorized - check token");
|
||||
break;
|
||||
case 403:
|
||||
System.out.println("Forbidden - user not in thread");
|
||||
break;
|
||||
case 404:
|
||||
System.out.println("Thread not found");
|
||||
break;
|
||||
default:
|
||||
System.out.println("Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `TEXT` | Regular chat message |
|
||||
| `HTML` | HTML-formatted message |
|
||||
| `TOPIC_UPDATED` | System message - topic changed |
|
||||
| `PARTICIPANT_ADDED` | System message - participant joined |
|
||||
| `PARTICIPANT_REMOVED` | System message - participant left |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_COMMUNICATION_ENDPOINT=https://<resource>.communication.azure.com
|
||||
AZURE_COMMUNICATION_USER_TOKEN=<user-access-token>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Token Management** - User tokens expire; implement refresh logic with `CommunicationTokenRefreshOptions`
|
||||
2. **Pagination** - Use `listMessages(options)` with `maxPageSize` for large threads
|
||||
3. **Share History** - Set `shareHistoryTime` when adding participants to control message visibility
|
||||
4. **Message Types** - Filter system messages (`PARTICIPANT_ADDED`, etc.) from user messages
|
||||
5. **Read Receipts** - Send receipts only when messages are actually viewed by user
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "chat application Java", "real-time messaging Java"
|
||||
- "chat thread", "chat participants", "chat messages"
|
||||
- "read receipts", "typing notifications"
|
||||
- "Azure Communication Services chat"
|
||||
304
skills/official/microsoft/java/communication/common/SKILL.md
Normal file
304
skills/official/microsoft/java/communication/common/SKILL.md
Normal file
@@ -0,0 +1,304 @@
|
||||
---
|
||||
name: azure-communication-common-java
|
||||
description: Azure Communication Services common utilities for Java. Use when working with CommunicationTokenCredential, user identifiers, token refresh, or shared authentication across ACS services.
|
||||
package: com.azure:azure-communication-common
|
||||
---
|
||||
|
||||
# Azure Communication Common (Java)
|
||||
|
||||
Shared authentication utilities and data structures for Azure Communication Services.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-communication-common</artifactId>
|
||||
<version>1.4.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Class | Purpose |
|
||||
|-------|---------|
|
||||
| `CommunicationTokenCredential` | Authenticate users with ACS services |
|
||||
| `CommunicationTokenRefreshOptions` | Configure automatic token refresh |
|
||||
| `CommunicationUserIdentifier` | Identify ACS users |
|
||||
| `PhoneNumberIdentifier` | Identify PSTN phone numbers |
|
||||
| `MicrosoftTeamsUserIdentifier` | Identify Teams users |
|
||||
| `UnknownIdentifier` | Generic identifier for unknown types |
|
||||
|
||||
## CommunicationTokenCredential
|
||||
|
||||
### Static Token (Short-lived Clients)
|
||||
|
||||
```java
|
||||
import com.azure.communication.common.CommunicationTokenCredential;
|
||||
|
||||
// Simple static token - no refresh
|
||||
String userToken = "<user-access-token>";
|
||||
CommunicationTokenCredential credential = new CommunicationTokenCredential(userToken);
|
||||
|
||||
// Use with Chat, Calling, etc.
|
||||
ChatClient chatClient = new ChatClientBuilder()
|
||||
.endpoint("https://<resource>.communication.azure.com")
|
||||
.credential(credential)
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### Proactive Token Refresh (Long-lived Clients)
|
||||
|
||||
```java
|
||||
import com.azure.communication.common.CommunicationTokenRefreshOptions;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
// Token refresher callback - called when token is about to expire
|
||||
Callable<String> tokenRefresher = () -> {
|
||||
// Call your server to get a fresh token
|
||||
return fetchNewTokenFromServer();
|
||||
};
|
||||
|
||||
// With proactive refresh
|
||||
CommunicationTokenRefreshOptions refreshOptions = new CommunicationTokenRefreshOptions(tokenRefresher)
|
||||
.setRefreshProactively(true) // Refresh before expiry
|
||||
.setInitialToken(currentToken); // Optional initial token
|
||||
|
||||
CommunicationTokenCredential credential = new CommunicationTokenCredential(refreshOptions);
|
||||
```
|
||||
|
||||
### Async Token Refresh
|
||||
|
||||
```java
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
// Async token fetcher
|
||||
Callable<String> asyncRefresher = () -> {
|
||||
CompletableFuture<String> future = fetchTokenAsync();
|
||||
return future.get(); // Block until token is available
|
||||
};
|
||||
|
||||
CommunicationTokenRefreshOptions options = new CommunicationTokenRefreshOptions(asyncRefresher)
|
||||
.setRefreshProactively(true);
|
||||
|
||||
CommunicationTokenCredential credential = new CommunicationTokenCredential(options);
|
||||
```
|
||||
|
||||
## Entra ID (Azure AD) Authentication
|
||||
|
||||
```java
|
||||
import com.azure.identity.InteractiveBrowserCredentialBuilder;
|
||||
import com.azure.communication.common.EntraCommunicationTokenCredentialOptions;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
// For Teams Phone Extensibility
|
||||
InteractiveBrowserCredential entraCredential = new InteractiveBrowserCredentialBuilder()
|
||||
.clientId("<your-client-id>")
|
||||
.tenantId("<your-tenant-id>")
|
||||
.redirectUrl("<your-redirect-uri>")
|
||||
.build();
|
||||
|
||||
String resourceEndpoint = "https://<resource>.communication.azure.com";
|
||||
List<String> scopes = Arrays.asList(
|
||||
"https://auth.msft.communication.azure.com/TeamsExtension.ManageCalls"
|
||||
);
|
||||
|
||||
EntraCommunicationTokenCredentialOptions entraOptions =
|
||||
new EntraCommunicationTokenCredentialOptions(entraCredential, resourceEndpoint)
|
||||
.setScopes(scopes);
|
||||
|
||||
CommunicationTokenCredential credential = new CommunicationTokenCredential(entraOptions);
|
||||
```
|
||||
|
||||
## Communication Identifiers
|
||||
|
||||
### CommunicationUserIdentifier
|
||||
|
||||
```java
|
||||
import com.azure.communication.common.CommunicationUserIdentifier;
|
||||
|
||||
// Create identifier for ACS user
|
||||
CommunicationUserIdentifier user = new CommunicationUserIdentifier("8:acs:resource-id_user-id");
|
||||
|
||||
// Get raw ID
|
||||
String rawId = user.getId();
|
||||
```
|
||||
|
||||
### PhoneNumberIdentifier
|
||||
|
||||
```java
|
||||
import com.azure.communication.common.PhoneNumberIdentifier;
|
||||
|
||||
// E.164 format phone number
|
||||
PhoneNumberIdentifier phone = new PhoneNumberIdentifier("+14255551234");
|
||||
|
||||
String phoneNumber = phone.getPhoneNumber(); // "+14255551234"
|
||||
String rawId = phone.getRawId(); // "4:+14255551234"
|
||||
```
|
||||
|
||||
### MicrosoftTeamsUserIdentifier
|
||||
|
||||
```java
|
||||
import com.azure.communication.common.MicrosoftTeamsUserIdentifier;
|
||||
|
||||
// Teams user identifier
|
||||
MicrosoftTeamsUserIdentifier teamsUser = new MicrosoftTeamsUserIdentifier("<teams-user-id>")
|
||||
.setCloudEnvironment(CommunicationCloudEnvironment.PUBLIC);
|
||||
|
||||
// For anonymous Teams users
|
||||
MicrosoftTeamsUserIdentifier anonymousTeamsUser = new MicrosoftTeamsUserIdentifier("<teams-user-id>")
|
||||
.setAnonymous(true);
|
||||
```
|
||||
|
||||
### UnknownIdentifier
|
||||
|
||||
```java
|
||||
import com.azure.communication.common.UnknownIdentifier;
|
||||
|
||||
// For identifiers of unknown type
|
||||
UnknownIdentifier unknown = new UnknownIdentifier("some-raw-id");
|
||||
```
|
||||
|
||||
## Identifier Parsing
|
||||
|
||||
```java
|
||||
import com.azure.communication.common.CommunicationIdentifier;
|
||||
import com.azure.communication.common.CommunicationIdentifierModel;
|
||||
|
||||
// Parse raw ID to appropriate type
|
||||
public CommunicationIdentifier parseIdentifier(String rawId) {
|
||||
if (rawId.startsWith("8:acs:")) {
|
||||
return new CommunicationUserIdentifier(rawId);
|
||||
} else if (rawId.startsWith("4:")) {
|
||||
String phone = rawId.substring(2);
|
||||
return new PhoneNumberIdentifier(phone);
|
||||
} else if (rawId.startsWith("8:orgid:")) {
|
||||
String teamsId = rawId.substring(8);
|
||||
return new MicrosoftTeamsUserIdentifier(teamsId);
|
||||
} else {
|
||||
return new UnknownIdentifier(rawId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Type Checking Identifiers
|
||||
|
||||
```java
|
||||
import com.azure.communication.common.CommunicationIdentifier;
|
||||
|
||||
public void processIdentifier(CommunicationIdentifier identifier) {
|
||||
if (identifier instanceof CommunicationUserIdentifier) {
|
||||
CommunicationUserIdentifier user = (CommunicationUserIdentifier) identifier;
|
||||
System.out.println("ACS User: " + user.getId());
|
||||
|
||||
} else if (identifier instanceof PhoneNumberIdentifier) {
|
||||
PhoneNumberIdentifier phone = (PhoneNumberIdentifier) identifier;
|
||||
System.out.println("Phone: " + phone.getPhoneNumber());
|
||||
|
||||
} else if (identifier instanceof MicrosoftTeamsUserIdentifier) {
|
||||
MicrosoftTeamsUserIdentifier teams = (MicrosoftTeamsUserIdentifier) identifier;
|
||||
System.out.println("Teams User: " + teams.getUserId());
|
||||
System.out.println("Anonymous: " + teams.isAnonymous());
|
||||
|
||||
} else if (identifier instanceof UnknownIdentifier) {
|
||||
UnknownIdentifier unknown = (UnknownIdentifier) identifier;
|
||||
System.out.println("Unknown: " + unknown.getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Token Access
|
||||
|
||||
```java
|
||||
import com.azure.core.credential.AccessToken;
|
||||
|
||||
// Get current token (for debugging/logging - don't expose!)
|
||||
CommunicationTokenCredential credential = new CommunicationTokenCredential(token);
|
||||
|
||||
// Sync access
|
||||
AccessToken accessToken = credential.getToken();
|
||||
System.out.println("Token expires: " + accessToken.getExpiresAt());
|
||||
|
||||
// Async access
|
||||
credential.getTokenAsync()
|
||||
.subscribe(token -> {
|
||||
System.out.println("Token: " + token.getToken().substring(0, 20) + "...");
|
||||
System.out.println("Expires: " + token.getExpiresAt());
|
||||
});
|
||||
```
|
||||
|
||||
## Dispose Credential
|
||||
|
||||
```java
|
||||
// Clean up when done
|
||||
credential.close();
|
||||
|
||||
// Or use try-with-resources
|
||||
try (CommunicationTokenCredential cred = new CommunicationTokenCredential(options)) {
|
||||
// Use credential
|
||||
chatClient.doSomething();
|
||||
}
|
||||
```
|
||||
|
||||
## Cloud Environments
|
||||
|
||||
```java
|
||||
import com.azure.communication.common.CommunicationCloudEnvironment;
|
||||
|
||||
// Available environments
|
||||
CommunicationCloudEnvironment publicCloud = CommunicationCloudEnvironment.PUBLIC;
|
||||
CommunicationCloudEnvironment govCloud = CommunicationCloudEnvironment.GCCH;
|
||||
CommunicationCloudEnvironment dodCloud = CommunicationCloudEnvironment.DOD;
|
||||
|
||||
// Set on Teams identifier
|
||||
MicrosoftTeamsUserIdentifier teamsUser = new MicrosoftTeamsUserIdentifier("<user-id>")
|
||||
.setCloudEnvironment(CommunicationCloudEnvironment.GCCH);
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_COMMUNICATION_ENDPOINT=https://<resource>.communication.azure.com
|
||||
AZURE_COMMUNICATION_USER_TOKEN=<user-access-token>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Proactive Refresh** - Always use `setRefreshProactively(true)` for long-lived clients
|
||||
2. **Token Security** - Never log or expose full tokens
|
||||
3. **Close Credentials** - Dispose of credentials when no longer needed
|
||||
4. **Error Handling** - Handle token refresh failures gracefully
|
||||
5. **Identifier Types** - Use specific identifier types, not raw strings
|
||||
|
||||
## Common Usage Patterns
|
||||
|
||||
```java
|
||||
// Pattern: Create credential for Chat/Calling client
|
||||
public ChatClient createChatClient(String token, String endpoint) {
|
||||
CommunicationTokenRefreshOptions refreshOptions =
|
||||
new CommunicationTokenRefreshOptions(this::refreshToken)
|
||||
.setRefreshProactively(true)
|
||||
.setInitialToken(token);
|
||||
|
||||
CommunicationTokenCredential credential =
|
||||
new CommunicationTokenCredential(refreshOptions);
|
||||
|
||||
return new ChatClientBuilder()
|
||||
.endpoint(endpoint)
|
||||
.credential(credential)
|
||||
.buildClient();
|
||||
}
|
||||
|
||||
private String refreshToken() {
|
||||
// Call your token endpoint
|
||||
return tokenService.getNewToken();
|
||||
}
|
||||
```
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "ACS authentication", "communication token credential"
|
||||
- "user access token", "token refresh"
|
||||
- "CommunicationUserIdentifier", "PhoneNumberIdentifier"
|
||||
- "Azure Communication Services authentication"
|
||||
274
skills/official/microsoft/java/communication/sms/SKILL.md
Normal file
274
skills/official/microsoft/java/communication/sms/SKILL.md
Normal file
@@ -0,0 +1,274 @@
|
||||
---
|
||||
name: azure-communication-sms-java
|
||||
description: Send SMS messages with Azure Communication Services SMS Java SDK. Use when implementing SMS notifications, alerts, OTP delivery, bulk messaging, or delivery reports.
|
||||
package: com.azure:azure-communication-sms
|
||||
---
|
||||
|
||||
# Azure Communication SMS (Java)
|
||||
|
||||
Send SMS messages to single or multiple recipients with delivery reporting.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-communication-sms</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
```java
|
||||
import com.azure.communication.sms.SmsClient;
|
||||
import com.azure.communication.sms.SmsClientBuilder;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
// With DefaultAzureCredential (recommended)
|
||||
SmsClient smsClient = new SmsClientBuilder()
|
||||
.endpoint("https://<resource>.communication.azure.com")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
|
||||
// With connection string
|
||||
SmsClient smsClient = new SmsClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.buildClient();
|
||||
|
||||
// With AzureKeyCredential
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
|
||||
SmsClient smsClient = new SmsClientBuilder()
|
||||
.endpoint("https://<resource>.communication.azure.com")
|
||||
.credential(new AzureKeyCredential("<access-key>"))
|
||||
.buildClient();
|
||||
|
||||
// Async client
|
||||
SmsAsyncClient smsAsyncClient = new SmsClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
## Send SMS to Single Recipient
|
||||
|
||||
```java
|
||||
import com.azure.communication.sms.models.SmsSendResult;
|
||||
|
||||
// Simple send
|
||||
SmsSendResult result = smsClient.send(
|
||||
"+14255550100", // From (your ACS phone number)
|
||||
"+14255551234", // To
|
||||
"Your verification code is 123456");
|
||||
|
||||
System.out.println("Message ID: " + result.getMessageId());
|
||||
System.out.println("To: " + result.getTo());
|
||||
System.out.println("Success: " + result.isSuccessful());
|
||||
|
||||
if (!result.isSuccessful()) {
|
||||
System.out.println("Error: " + result.getErrorMessage());
|
||||
System.out.println("Status: " + result.getHttpStatusCode());
|
||||
}
|
||||
```
|
||||
|
||||
## Send SMS to Multiple Recipients
|
||||
|
||||
```java
|
||||
import com.azure.communication.sms.models.SmsSendOptions;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
List<String> recipients = Arrays.asList(
|
||||
"+14255551111",
|
||||
"+14255552222",
|
||||
"+14255553333"
|
||||
);
|
||||
|
||||
// With options
|
||||
SmsSendOptions options = new SmsSendOptions()
|
||||
.setDeliveryReportEnabled(true)
|
||||
.setTag("marketing-campaign-001");
|
||||
|
||||
Iterable<SmsSendResult> results = smsClient.sendWithResponse(
|
||||
"+14255550100", // From
|
||||
recipients, // To list
|
||||
"Flash sale! 50% off today only.",
|
||||
options,
|
||||
Context.NONE
|
||||
).getValue();
|
||||
|
||||
for (SmsSendResult result : results) {
|
||||
if (result.isSuccessful()) {
|
||||
System.out.println("Sent to " + result.getTo() + ": " + result.getMessageId());
|
||||
} else {
|
||||
System.out.println("Failed to " + result.getTo() + ": " + result.getErrorMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Send Options
|
||||
|
||||
```java
|
||||
SmsSendOptions options = new SmsSendOptions();
|
||||
|
||||
// Enable delivery reports (sent via Event Grid)
|
||||
options.setDeliveryReportEnabled(true);
|
||||
|
||||
// Add custom tag for tracking
|
||||
options.setTag("order-confirmation-12345");
|
||||
```
|
||||
|
||||
## Response Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.http.rest.Response;
|
||||
|
||||
Response<Iterable<SmsSendResult>> response = smsClient.sendWithResponse(
|
||||
"+14255550100",
|
||||
Arrays.asList("+14255551234"),
|
||||
"Hello!",
|
||||
new SmsSendOptions().setDeliveryReportEnabled(true),
|
||||
Context.NONE
|
||||
);
|
||||
|
||||
// Check HTTP response
|
||||
System.out.println("Status code: " + response.getStatusCode());
|
||||
System.out.println("Headers: " + response.getHeaders());
|
||||
|
||||
// Process results
|
||||
for (SmsSendResult result : response.getValue()) {
|
||||
System.out.println("Message ID: " + result.getMessageId());
|
||||
System.out.println("Successful: " + result.isSuccessful());
|
||||
|
||||
if (!result.isSuccessful()) {
|
||||
System.out.println("HTTP Status: " + result.getHttpStatusCode());
|
||||
System.out.println("Error: " + result.getErrorMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Async Operations
|
||||
|
||||
```java
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
SmsAsyncClient asyncClient = new SmsClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.buildAsyncClient();
|
||||
|
||||
// Send single message
|
||||
asyncClient.send("+14255550100", "+14255551234", "Async message!")
|
||||
.subscribe(
|
||||
result -> System.out.println("Sent: " + result.getMessageId()),
|
||||
error -> System.out.println("Error: " + error.getMessage())
|
||||
);
|
||||
|
||||
// Send to multiple with options
|
||||
SmsSendOptions options = new SmsSendOptions()
|
||||
.setDeliveryReportEnabled(true);
|
||||
|
||||
asyncClient.sendWithResponse(
|
||||
"+14255550100",
|
||||
Arrays.asList("+14255551111", "+14255552222"),
|
||||
"Bulk async message",
|
||||
options)
|
||||
.subscribe(response -> {
|
||||
for (SmsSendResult result : response.getValue()) {
|
||||
System.out.println("Result: " + result.getTo() + " - " + result.isSuccessful());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
SmsSendResult result = smsClient.send(
|
||||
"+14255550100",
|
||||
"+14255551234",
|
||||
"Test message"
|
||||
);
|
||||
|
||||
// Individual message errors don't throw exceptions
|
||||
if (!result.isSuccessful()) {
|
||||
handleMessageError(result);
|
||||
}
|
||||
|
||||
} catch (HttpResponseException e) {
|
||||
// Request-level failures (auth, network, etc.)
|
||||
System.out.println("Request failed: " + e.getMessage());
|
||||
System.out.println("Status: " + e.getResponse().getStatusCode());
|
||||
} catch (RuntimeException e) {
|
||||
System.out.println("Unexpected error: " + e.getMessage());
|
||||
}
|
||||
|
||||
private void handleMessageError(SmsSendResult result) {
|
||||
int status = result.getHttpStatusCode();
|
||||
String error = result.getErrorMessage();
|
||||
|
||||
if (status == 400) {
|
||||
System.out.println("Invalid phone number: " + result.getTo());
|
||||
} else if (status == 429) {
|
||||
System.out.println("Rate limited - retry later");
|
||||
} else {
|
||||
System.out.println("Error " + status + ": " + error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Delivery Reports
|
||||
|
||||
Delivery reports are sent via Azure Event Grid. Configure an Event Grid subscription for your ACS resource.
|
||||
|
||||
```java
|
||||
// Event Grid webhook handler (in your endpoint)
|
||||
public void handleDeliveryReport(String eventJson) {
|
||||
// Parse Event Grid event
|
||||
// Event type: Microsoft.Communication.SMSDeliveryReportReceived
|
||||
|
||||
// Event data contains:
|
||||
// - messageId: correlates to SmsSendResult.getMessageId()
|
||||
// - from: sender number
|
||||
// - to: recipient number
|
||||
// - deliveryStatus: "Delivered", "Failed", etc.
|
||||
// - deliveryStatusDetails: detailed status
|
||||
// - receivedTimestamp: when status was received
|
||||
// - tag: your custom tag from SmsSendOptions
|
||||
}
|
||||
```
|
||||
|
||||
## SmsSendResult Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `getMessageId()` | String | Unique message identifier |
|
||||
| `getTo()` | String | Recipient phone number |
|
||||
| `isSuccessful()` | boolean | Whether send succeeded |
|
||||
| `getHttpStatusCode()` | int | HTTP status for this recipient |
|
||||
| `getErrorMessage()` | String | Error details if failed |
|
||||
| `getRepeatabilityResult()` | RepeatabilityResult | Idempotency result |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_COMMUNICATION_ENDPOINT=https://<resource>.communication.azure.com
|
||||
AZURE_COMMUNICATION_CONNECTION_STRING=endpoint=https://...;accesskey=...
|
||||
SMS_FROM_NUMBER=+14255550100
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Phone Number Format** - Use E.164 format: `+[country code][number]`
|
||||
2. **Delivery Reports** - Enable for critical messages (OTP, alerts)
|
||||
3. **Tagging** - Use tags to correlate messages with business context
|
||||
4. **Error Handling** - Check `isSuccessful()` for each recipient individually
|
||||
5. **Rate Limiting** - Implement retry with backoff for 429 responses
|
||||
6. **Bulk Sending** - Use batch send for multiple recipients (more efficient)
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "send SMS Java", "text message Java"
|
||||
- "SMS notification", "OTP SMS", "bulk SMS"
|
||||
- "delivery report SMS", "Azure Communication Services SMS"
|
||||
379
skills/official/microsoft/java/compute/batch/SKILL.md
Normal file
379
skills/official/microsoft/java/compute/batch/SKILL.md
Normal file
@@ -0,0 +1,379 @@
|
||||
---
|
||||
name: azure-compute-batch-java
|
||||
description: |
|
||||
Azure Batch SDK for Java. Run large-scale parallel and HPC batch jobs with pools, jobs, tasks, and compute nodes.
|
||||
Triggers: "BatchClient java", "azure batch java", "batch pool java", "batch job java", "HPC java", "parallel computing java".
|
||||
---
|
||||
|
||||
# Azure Batch SDK for Java
|
||||
|
||||
Client library for running large-scale parallel and high-performance computing (HPC) batch jobs in Azure.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-compute-batch</artifactId>
|
||||
<version>1.0.0-beta.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Azure Batch account
|
||||
- Pool configured with compute nodes
|
||||
- Azure subscription
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_BATCH_ENDPOINT=https://<account>.<region>.batch.azure.com
|
||||
AZURE_BATCH_ACCOUNT=<account-name>
|
||||
AZURE_BATCH_ACCESS_KEY=<account-key>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### With Microsoft Entra ID (Recommended)
|
||||
|
||||
```java
|
||||
import com.azure.compute.batch.BatchClient;
|
||||
import com.azure.compute.batch.BatchClientBuilder;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
BatchClient batchClient = new BatchClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.endpoint(System.getenv("AZURE_BATCH_ENDPOINT"))
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### Async Client
|
||||
|
||||
```java
|
||||
import com.azure.compute.batch.BatchAsyncClient;
|
||||
|
||||
BatchAsyncClient batchAsyncClient = new BatchClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.endpoint(System.getenv("AZURE_BATCH_ENDPOINT"))
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
### With Shared Key Credentials
|
||||
|
||||
```java
|
||||
import com.azure.core.credential.AzureNamedKeyCredential;
|
||||
|
||||
String accountName = System.getenv("AZURE_BATCH_ACCOUNT");
|
||||
String accountKey = System.getenv("AZURE_BATCH_ACCESS_KEY");
|
||||
AzureNamedKeyCredential sharedKeyCreds = new AzureNamedKeyCredential(accountName, accountKey);
|
||||
|
||||
BatchClient batchClient = new BatchClientBuilder()
|
||||
.credential(sharedKeyCreds)
|
||||
.endpoint(System.getenv("AZURE_BATCH_ENDPOINT"))
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| Pool | Collection of compute nodes that run tasks |
|
||||
| Job | Logical grouping of tasks |
|
||||
| Task | Unit of computation (command/script) |
|
||||
| Node | VM that executes tasks |
|
||||
| Job Schedule | Recurring job creation |
|
||||
|
||||
## Pool Operations
|
||||
|
||||
### Create Pool
|
||||
|
||||
```java
|
||||
import com.azure.compute.batch.models.*;
|
||||
|
||||
batchClient.createPool(new BatchPoolCreateParameters("myPoolId", "STANDARD_DC2s_V2")
|
||||
.setVirtualMachineConfiguration(
|
||||
new VirtualMachineConfiguration(
|
||||
new BatchVmImageReference()
|
||||
.setPublisher("Canonical")
|
||||
.setOffer("UbuntuServer")
|
||||
.setSku("22_04-lts")
|
||||
.setVersion("latest"),
|
||||
"batch.node.ubuntu 22.04"))
|
||||
.setTargetDedicatedNodes(2)
|
||||
.setTargetLowPriorityNodes(0), null);
|
||||
```
|
||||
|
||||
### Get Pool
|
||||
|
||||
```java
|
||||
BatchPool pool = batchClient.getPool("myPoolId");
|
||||
System.out.println("Pool state: " + pool.getState());
|
||||
System.out.println("Current dedicated nodes: " + pool.getCurrentDedicatedNodes());
|
||||
```
|
||||
|
||||
### List Pools
|
||||
|
||||
```java
|
||||
import com.azure.core.http.rest.PagedIterable;
|
||||
|
||||
PagedIterable<BatchPool> pools = batchClient.listPools();
|
||||
for (BatchPool pool : pools) {
|
||||
System.out.println("Pool: " + pool.getId() + ", State: " + pool.getState());
|
||||
}
|
||||
```
|
||||
|
||||
### Resize Pool
|
||||
|
||||
```java
|
||||
import com.azure.core.util.polling.SyncPoller;
|
||||
|
||||
BatchPoolResizeParameters resizeParams = new BatchPoolResizeParameters()
|
||||
.setTargetDedicatedNodes(4)
|
||||
.setTargetLowPriorityNodes(2);
|
||||
|
||||
SyncPoller<BatchPool, BatchPool> poller = batchClient.beginResizePool("myPoolId", resizeParams);
|
||||
poller.waitForCompletion();
|
||||
BatchPool resizedPool = poller.getFinalResult();
|
||||
```
|
||||
|
||||
### Enable AutoScale
|
||||
|
||||
```java
|
||||
BatchPoolEnableAutoScaleParameters autoScaleParams = new BatchPoolEnableAutoScaleParameters()
|
||||
.setAutoScaleEvaluationInterval(Duration.ofMinutes(5))
|
||||
.setAutoScaleFormula("$TargetDedicatedNodes = min(10, $PendingTasks.GetSample(TimeInterval_Minute * 5));");
|
||||
|
||||
batchClient.enablePoolAutoScale("myPoolId", autoScaleParams);
|
||||
```
|
||||
|
||||
### Delete Pool
|
||||
|
||||
```java
|
||||
SyncPoller<BatchPool, Void> deletePoller = batchClient.beginDeletePool("myPoolId");
|
||||
deletePoller.waitForCompletion();
|
||||
```
|
||||
|
||||
## Job Operations
|
||||
|
||||
### Create Job
|
||||
|
||||
```java
|
||||
batchClient.createJob(
|
||||
new BatchJobCreateParameters("myJobId", new BatchPoolInfo().setPoolId("myPoolId"))
|
||||
.setPriority(100)
|
||||
.setConstraints(new BatchJobConstraints()
|
||||
.setMaxWallClockTime(Duration.ofHours(24))
|
||||
.setMaxTaskRetryCount(3)),
|
||||
null);
|
||||
```
|
||||
|
||||
### Get Job
|
||||
|
||||
```java
|
||||
BatchJob job = batchClient.getJob("myJobId", null, null);
|
||||
System.out.println("Job state: " + job.getState());
|
||||
```
|
||||
|
||||
### List Jobs
|
||||
|
||||
```java
|
||||
PagedIterable<BatchJob> jobs = batchClient.listJobs(new BatchJobsListOptions());
|
||||
for (BatchJob job : jobs) {
|
||||
System.out.println("Job: " + job.getId() + ", State: " + job.getState());
|
||||
}
|
||||
```
|
||||
|
||||
### Get Task Counts
|
||||
|
||||
```java
|
||||
BatchTaskCountsResult counts = batchClient.getJobTaskCounts("myJobId");
|
||||
System.out.println("Active: " + counts.getTaskCounts().getActive());
|
||||
System.out.println("Running: " + counts.getTaskCounts().getRunning());
|
||||
System.out.println("Completed: " + counts.getTaskCounts().getCompleted());
|
||||
```
|
||||
|
||||
### Terminate Job
|
||||
|
||||
```java
|
||||
BatchJobTerminateParameters terminateParams = new BatchJobTerminateParameters()
|
||||
.setTerminationReason("Manual termination");
|
||||
BatchJobTerminateOptions options = new BatchJobTerminateOptions().setParameters(terminateParams);
|
||||
|
||||
SyncPoller<BatchJob, BatchJob> poller = batchClient.beginTerminateJob("myJobId", options, null);
|
||||
poller.waitForCompletion();
|
||||
```
|
||||
|
||||
### Delete Job
|
||||
|
||||
```java
|
||||
SyncPoller<BatchJob, Void> deletePoller = batchClient.beginDeleteJob("myJobId");
|
||||
deletePoller.waitForCompletion();
|
||||
```
|
||||
|
||||
## Task Operations
|
||||
|
||||
### Create Single Task
|
||||
|
||||
```java
|
||||
BatchTaskCreateParameters task = new BatchTaskCreateParameters("task1", "echo 'Hello World'");
|
||||
batchClient.createTask("myJobId", task);
|
||||
```
|
||||
|
||||
### Create Task with Exit Conditions
|
||||
|
||||
```java
|
||||
batchClient.createTask("myJobId", new BatchTaskCreateParameters("task2", "cmd /c exit 3")
|
||||
.setExitConditions(new ExitConditions()
|
||||
.setExitCodeRanges(Arrays.asList(
|
||||
new ExitCodeRangeMapping(2, 4,
|
||||
new ExitOptions().setJobAction(BatchJobActionKind.TERMINATE)))))
|
||||
.setUserIdentity(new UserIdentity()
|
||||
.setAutoUser(new AutoUserSpecification()
|
||||
.setScope(AutoUserScope.TASK)
|
||||
.setElevationLevel(ElevationLevel.NON_ADMIN))),
|
||||
null);
|
||||
```
|
||||
|
||||
### Create Task Collection (up to 100)
|
||||
|
||||
```java
|
||||
List<BatchTaskCreateParameters> taskList = Arrays.asList(
|
||||
new BatchTaskCreateParameters("task1", "echo Task 1"),
|
||||
new BatchTaskCreateParameters("task2", "echo Task 2"),
|
||||
new BatchTaskCreateParameters("task3", "echo Task 3")
|
||||
);
|
||||
BatchTaskGroup taskGroup = new BatchTaskGroup(taskList);
|
||||
BatchCreateTaskCollectionResult result = batchClient.createTaskCollection("myJobId", taskGroup);
|
||||
```
|
||||
|
||||
### Create Many Tasks (no limit)
|
||||
|
||||
```java
|
||||
List<BatchTaskCreateParameters> tasks = new ArrayList<>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
tasks.add(new BatchTaskCreateParameters("task" + i, "echo Task " + i));
|
||||
}
|
||||
batchClient.createTasks("myJobId", tasks);
|
||||
```
|
||||
|
||||
### Get Task
|
||||
|
||||
```java
|
||||
BatchTask task = batchClient.getTask("myJobId", "task1");
|
||||
System.out.println("Task state: " + task.getState());
|
||||
System.out.println("Exit code: " + task.getExecutionInfo().getExitCode());
|
||||
```
|
||||
|
||||
### List Tasks
|
||||
|
||||
```java
|
||||
PagedIterable<BatchTask> tasks = batchClient.listTasks("myJobId");
|
||||
for (BatchTask task : tasks) {
|
||||
System.out.println("Task: " + task.getId() + ", State: " + task.getState());
|
||||
}
|
||||
```
|
||||
|
||||
### Get Task Output
|
||||
|
||||
```java
|
||||
import com.azure.core.util.BinaryData;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
BinaryData stdout = batchClient.getTaskFile("myJobId", "task1", "stdout.txt");
|
||||
System.out.println(new String(stdout.toBytes(), StandardCharsets.UTF_8));
|
||||
```
|
||||
|
||||
### Terminate Task
|
||||
|
||||
```java
|
||||
batchClient.terminateTask("myJobId", "task1", null, null);
|
||||
```
|
||||
|
||||
## Node Operations
|
||||
|
||||
### List Nodes
|
||||
|
||||
```java
|
||||
PagedIterable<BatchNode> nodes = batchClient.listNodes("myPoolId", new BatchNodesListOptions());
|
||||
for (BatchNode node : nodes) {
|
||||
System.out.println("Node: " + node.getId() + ", State: " + node.getState());
|
||||
}
|
||||
```
|
||||
|
||||
### Reboot Node
|
||||
|
||||
```java
|
||||
SyncPoller<BatchNode, BatchNode> rebootPoller = batchClient.beginRebootNode("myPoolId", "nodeId");
|
||||
rebootPoller.waitForCompletion();
|
||||
```
|
||||
|
||||
### Get Remote Login Settings
|
||||
|
||||
```java
|
||||
BatchNodeRemoteLoginSettings settings = batchClient.getNodeRemoteLoginSettings("myPoolId", "nodeId");
|
||||
System.out.println("IP: " + settings.getRemoteLoginIpAddress());
|
||||
System.out.println("Port: " + settings.getRemoteLoginPort());
|
||||
```
|
||||
|
||||
## Job Schedule Operations
|
||||
|
||||
### Create Job Schedule
|
||||
|
||||
```java
|
||||
batchClient.createJobSchedule(new BatchJobScheduleCreateParameters("myScheduleId",
|
||||
new BatchJobScheduleConfiguration()
|
||||
.setRecurrenceInterval(Duration.ofHours(6))
|
||||
.setDoNotRunUntil(OffsetDateTime.now().plusDays(1)),
|
||||
new BatchJobSpecification(new BatchPoolInfo().setPoolId("myPoolId"))
|
||||
.setPriority(50)),
|
||||
null);
|
||||
```
|
||||
|
||||
### Get Job Schedule
|
||||
|
||||
```java
|
||||
BatchJobSchedule schedule = batchClient.getJobSchedule("myScheduleId");
|
||||
System.out.println("Schedule state: " + schedule.getState());
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.compute.batch.models.BatchErrorException;
|
||||
import com.azure.compute.batch.models.BatchError;
|
||||
|
||||
try {
|
||||
batchClient.getPool("nonexistent-pool");
|
||||
} catch (BatchErrorException e) {
|
||||
BatchError error = e.getValue();
|
||||
System.err.println("Error code: " + error.getCode());
|
||||
System.err.println("Message: " + error.getMessage().getValue());
|
||||
|
||||
if ("PoolNotFound".equals(error.getCode())) {
|
||||
System.err.println("The specified pool does not exist.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Entra ID** — Preferred over shared key for authentication
|
||||
2. **Use management SDK for pools** — `azure-resourcemanager-batch` supports managed identities
|
||||
3. **Batch task creation** — Use `createTaskCollection` or `createTasks` for multiple tasks
|
||||
4. **Handle LRO properly** — Pool resize, delete operations are long-running
|
||||
5. **Monitor task counts** — Use `getJobTaskCounts` to track progress
|
||||
6. **Set constraints** — Configure `maxWallClockTime` and `maxTaskRetryCount`
|
||||
7. **Use low-priority nodes** — Cost savings for fault-tolerant workloads
|
||||
8. **Enable autoscale** — Dynamically adjust pool size based on workload
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| Maven Package | https://central.sonatype.com/artifact/com.azure/azure-compute-batch |
|
||||
| GitHub | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/batch/azure-compute-batch |
|
||||
| API Documentation | https://learn.microsoft.com/java/api/com.azure.compute.batch |
|
||||
| Product Docs | https://learn.microsoft.com/azure/batch/ |
|
||||
| REST API | https://learn.microsoft.com/rest/api/batchservice/ |
|
||||
| Samples | https://github.com/azure/azure-batch-samples |
|
||||
388
skills/official/microsoft/java/data/blob/SKILL.md
Normal file
388
skills/official/microsoft/java/data/blob/SKILL.md
Normal file
@@ -0,0 +1,388 @@
|
||||
---
|
||||
name: azure-storage-blob-java
|
||||
description: Build blob storage applications with Azure Storage Blob SDK for Java. Use when uploading, downloading, or managing files in Azure Blob Storage, working with containers, or implementing streaming data operations.
|
||||
package: com.azure:azure-storage-blob
|
||||
---
|
||||
|
||||
# Azure Storage Blob SDK for Java
|
||||
|
||||
Build blob storage applications using the Azure Storage Blob SDK for Java.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-storage-blob</artifactId>
|
||||
<version>12.33.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### BlobServiceClient
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.BlobServiceClient;
|
||||
import com.azure.storage.blob.BlobServiceClientBuilder;
|
||||
|
||||
// With SAS token
|
||||
BlobServiceClient serviceClient = new BlobServiceClientBuilder()
|
||||
.endpoint("<storage-account-url>")
|
||||
.sasToken("<sas-token>")
|
||||
.buildClient();
|
||||
|
||||
// With connection string
|
||||
BlobServiceClient serviceClient = new BlobServiceClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### With DefaultAzureCredential
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
BlobServiceClient serviceClient = new BlobServiceClientBuilder()
|
||||
.endpoint("<storage-account-url>")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### BlobContainerClient
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.BlobContainerClient;
|
||||
|
||||
// From service client
|
||||
BlobContainerClient containerClient = serviceClient.getBlobContainerClient("mycontainer");
|
||||
|
||||
// Direct construction
|
||||
BlobContainerClient containerClient = new BlobContainerClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.containerName("mycontainer")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### BlobClient
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.BlobClient;
|
||||
|
||||
// From container client
|
||||
BlobClient blobClient = containerClient.getBlobClient("myblob.txt");
|
||||
|
||||
// With directory structure
|
||||
BlobClient blobClient = containerClient.getBlobClient("folder/subfolder/myblob.txt");
|
||||
|
||||
// Direct construction
|
||||
BlobClient blobClient = new BlobClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.containerName("mycontainer")
|
||||
.blobName("myblob.txt")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Create Container
|
||||
|
||||
```java
|
||||
// Create container
|
||||
serviceClient.createBlobContainer("mycontainer");
|
||||
|
||||
// Create if not exists
|
||||
BlobContainerClient container = serviceClient.createBlobContainerIfNotExists("mycontainer");
|
||||
|
||||
// From container client
|
||||
containerClient.create();
|
||||
containerClient.createIfNotExists();
|
||||
```
|
||||
|
||||
### Upload Data
|
||||
|
||||
```java
|
||||
import com.azure.core.util.BinaryData;
|
||||
|
||||
// Upload string
|
||||
String data = "Hello, Azure Blob Storage!";
|
||||
blobClient.upload(BinaryData.fromString(data));
|
||||
|
||||
// Upload with overwrite
|
||||
blobClient.upload(BinaryData.fromString(data), true);
|
||||
```
|
||||
|
||||
### Upload from File
|
||||
|
||||
```java
|
||||
blobClient.uploadFromFile("local-file.txt");
|
||||
|
||||
// With overwrite
|
||||
blobClient.uploadFromFile("local-file.txt", true);
|
||||
```
|
||||
|
||||
### Upload from Stream
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.specialized.BlockBlobClient;
|
||||
|
||||
BlockBlobClient blockBlobClient = blobClient.getBlockBlobClient();
|
||||
|
||||
try (ByteArrayInputStream dataStream = new ByteArrayInputStream(data.getBytes())) {
|
||||
blockBlobClient.upload(dataStream, data.length());
|
||||
}
|
||||
```
|
||||
|
||||
### Upload with Options
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.models.BlobHttpHeaders;
|
||||
import com.azure.storage.blob.options.BlobParallelUploadOptions;
|
||||
|
||||
BlobHttpHeaders headers = new BlobHttpHeaders()
|
||||
.setContentType("text/plain")
|
||||
.setCacheControl("max-age=3600");
|
||||
|
||||
Map<String, String> metadata = Map.of("author", "john", "version", "1.0");
|
||||
|
||||
try (InputStream stream = new FileInputStream("large-file.bin")) {
|
||||
BlobParallelUploadOptions options = new BlobParallelUploadOptions(stream)
|
||||
.setHeaders(headers)
|
||||
.setMetadata(metadata);
|
||||
|
||||
blobClient.uploadWithResponse(options, null, Context.NONE);
|
||||
}
|
||||
```
|
||||
|
||||
### Upload if Not Exists
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.models.BlobRequestConditions;
|
||||
|
||||
BlobParallelUploadOptions options = new BlobParallelUploadOptions(inputStream, length)
|
||||
.setRequestConditions(new BlobRequestConditions().setIfNoneMatch("*"));
|
||||
|
||||
blobClient.uploadWithResponse(options, null, Context.NONE);
|
||||
```
|
||||
|
||||
### Download Data
|
||||
|
||||
```java
|
||||
// Download to BinaryData
|
||||
BinaryData content = blobClient.downloadContent();
|
||||
String text = content.toString();
|
||||
|
||||
// Download to file
|
||||
blobClient.downloadToFile("downloaded-file.txt");
|
||||
```
|
||||
|
||||
### Download to Stream
|
||||
|
||||
```java
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
blobClient.downloadStream(outputStream);
|
||||
byte[] data = outputStream.toByteArray();
|
||||
}
|
||||
```
|
||||
|
||||
### Download with InputStream
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.specialized.BlobInputStream;
|
||||
|
||||
try (BlobInputStream blobIS = blobClient.openInputStream()) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = blobIS.read(buffer)) != -1) {
|
||||
// Process buffer
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Upload via OutputStream
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.specialized.BlobOutputStream;
|
||||
|
||||
try (BlobOutputStream blobOS = blobClient.getBlockBlobClient().getBlobOutputStream()) {
|
||||
blobOS.write("Data to upload".getBytes());
|
||||
}
|
||||
```
|
||||
|
||||
### List Blobs
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.models.BlobItem;
|
||||
|
||||
// List all blobs
|
||||
for (BlobItem blobItem : containerClient.listBlobs()) {
|
||||
System.out.println("Blob: " + blobItem.getName());
|
||||
}
|
||||
|
||||
// List with prefix (virtual directory)
|
||||
import com.azure.storage.blob.models.ListBlobsOptions;
|
||||
|
||||
ListBlobsOptions options = new ListBlobsOptions().setPrefix("folder/");
|
||||
for (BlobItem blobItem : containerClient.listBlobs(options, null)) {
|
||||
System.out.println("Blob: " + blobItem.getName());
|
||||
}
|
||||
```
|
||||
|
||||
### List Blobs by Hierarchy
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.models.BlobListDetails;
|
||||
|
||||
String delimiter = "/";
|
||||
ListBlobsOptions options = new ListBlobsOptions()
|
||||
.setPrefix("data/")
|
||||
.setDetails(new BlobListDetails().setRetrieveMetadata(true));
|
||||
|
||||
for (BlobItem item : containerClient.listBlobsByHierarchy(delimiter, options, null)) {
|
||||
if (item.isPrefix()) {
|
||||
System.out.println("Directory: " + item.getName());
|
||||
} else {
|
||||
System.out.println("Blob: " + item.getName());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Blob
|
||||
|
||||
```java
|
||||
blobClient.delete();
|
||||
|
||||
// Delete if exists
|
||||
blobClient.deleteIfExists();
|
||||
|
||||
// Delete with snapshots
|
||||
import com.azure.storage.blob.models.DeleteSnapshotsOptionType;
|
||||
blobClient.deleteWithResponse(DeleteSnapshotsOptionType.INCLUDE, null, null, Context.NONE);
|
||||
```
|
||||
|
||||
### Copy Blob
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.models.BlobCopyInfo;
|
||||
import com.azure.core.util.polling.SyncPoller;
|
||||
|
||||
// Async copy (for large blobs or cross-account)
|
||||
SyncPoller<BlobCopyInfo, Void> poller = blobClient.beginCopy("<source-blob-url>", Duration.ofSeconds(1));
|
||||
poller.waitForCompletion();
|
||||
|
||||
// Sync copy from URL (for same account)
|
||||
blobClient.copyFromUrl("<source-blob-url>");
|
||||
```
|
||||
|
||||
### Generate SAS Token
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.sas.*;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
// Blob-level SAS
|
||||
BlobSasPermission permissions = new BlobSasPermission().setReadPermission(true);
|
||||
OffsetDateTime expiry = OffsetDateTime.now().plusDays(1);
|
||||
|
||||
BlobServiceSasSignatureValues sasValues = new BlobServiceSasSignatureValues(expiry, permissions);
|
||||
String sasToken = blobClient.generateSas(sasValues);
|
||||
|
||||
// Container-level SAS
|
||||
BlobContainerSasPermission containerPermissions = new BlobContainerSasPermission()
|
||||
.setReadPermission(true)
|
||||
.setListPermission(true);
|
||||
|
||||
BlobServiceSasSignatureValues containerSasValues = new BlobServiceSasSignatureValues(expiry, containerPermissions);
|
||||
String containerSas = containerClient.generateSas(containerSasValues);
|
||||
```
|
||||
|
||||
### Blob Properties and Metadata
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.models.BlobProperties;
|
||||
|
||||
// Get properties
|
||||
BlobProperties properties = blobClient.getProperties();
|
||||
System.out.println("Size: " + properties.getBlobSize());
|
||||
System.out.println("Content-Type: " + properties.getContentType());
|
||||
System.out.println("Last Modified: " + properties.getLastModified());
|
||||
|
||||
// Set metadata
|
||||
Map<String, String> metadata = Map.of("key1", "value1", "key2", "value2");
|
||||
blobClient.setMetadata(metadata);
|
||||
|
||||
// Set HTTP headers
|
||||
BlobHttpHeaders headers = new BlobHttpHeaders()
|
||||
.setContentType("application/json")
|
||||
.setCacheControl("max-age=86400");
|
||||
blobClient.setHttpHeaders(headers);
|
||||
```
|
||||
|
||||
### Lease Blob
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.specialized.BlobLeaseClient;
|
||||
import com.azure.storage.blob.specialized.BlobLeaseClientBuilder;
|
||||
|
||||
BlobLeaseClient leaseClient = new BlobLeaseClientBuilder()
|
||||
.blobClient(blobClient)
|
||||
.buildClient();
|
||||
|
||||
// Acquire lease (-1 for infinite)
|
||||
String leaseId = leaseClient.acquireLease(60);
|
||||
|
||||
// Renew lease
|
||||
leaseClient.renewLease();
|
||||
|
||||
// Release lease
|
||||
leaseClient.releaseLease();
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.storage.blob.models.BlobStorageException;
|
||||
|
||||
try {
|
||||
blobClient.download(outputStream);
|
||||
} catch (BlobStorageException e) {
|
||||
System.out.println("Status: " + e.getStatusCode());
|
||||
System.out.println("Error code: " + e.getErrorCode());
|
||||
// 404 = Blob not found
|
||||
// 409 = Conflict (lease, etc.)
|
||||
}
|
||||
```
|
||||
|
||||
## Proxy Configuration
|
||||
|
||||
```java
|
||||
import com.azure.core.http.ProxyOptions;
|
||||
import com.azure.core.http.netty.NettyAsyncHttpClientBuilder;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
ProxyOptions proxyOptions = new ProxyOptions(
|
||||
ProxyOptions.Type.HTTP,
|
||||
new InetSocketAddress("localhost", 8888));
|
||||
|
||||
BlobServiceClient client = new BlobServiceClientBuilder()
|
||||
.endpoint("<endpoint>")
|
||||
.sasToken("<sas-token>")
|
||||
.httpClient(new NettyAsyncHttpClientBuilder().proxy(proxyOptions).build())
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
|
||||
AZURE_STORAGE_ACCOUNT_URL=https://<account>.blob.core.windows.net
|
||||
```
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "Azure Blob Storage Java"
|
||||
- "upload download blob"
|
||||
- "blob container SDK"
|
||||
- "storage streaming"
|
||||
- "SAS token generation"
|
||||
- "blob metadata properties"
|
||||
258
skills/official/microsoft/java/data/cosmos/SKILL.md
Normal file
258
skills/official/microsoft/java/data/cosmos/SKILL.md
Normal file
@@ -0,0 +1,258 @@
|
||||
---
|
||||
name: azure-cosmos-java
|
||||
description: |
|
||||
Azure Cosmos DB SDK for Java. NoSQL database operations with global distribution, multi-model support, and reactive patterns.
|
||||
Triggers: "CosmosClient java", "CosmosAsyncClient", "cosmos database java", "cosmosdb java", "document database java".
|
||||
package: azure-cosmos
|
||||
---
|
||||
|
||||
# Azure Cosmos DB SDK for Java
|
||||
|
||||
Client library for Azure Cosmos DB NoSQL API with global distribution and reactive patterns.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-cosmos</artifactId>
|
||||
<version>LATEST</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Or use Azure SDK BOM:
|
||||
|
||||
```xml
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-sdk-bom</artifactId>
|
||||
<version>{bom_version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-cosmos</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
COSMOS_ENDPOINT=https://<account>.documents.azure.com:443/
|
||||
COSMOS_KEY=<your-primary-key>
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### Key-based Authentication
|
||||
|
||||
```java
|
||||
import com.azure.cosmos.CosmosClient;
|
||||
import com.azure.cosmos.CosmosClientBuilder;
|
||||
|
||||
CosmosClient client = new CosmosClientBuilder()
|
||||
.endpoint(System.getenv("COSMOS_ENDPOINT"))
|
||||
.key(System.getenv("COSMOS_KEY"))
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### Async Client
|
||||
|
||||
```java
|
||||
import com.azure.cosmos.CosmosAsyncClient;
|
||||
|
||||
CosmosAsyncClient asyncClient = new CosmosClientBuilder()
|
||||
.endpoint(serviceEndpoint)
|
||||
.key(key)
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
### With Customizations
|
||||
|
||||
```java
|
||||
import com.azure.cosmos.ConsistencyLevel;
|
||||
import java.util.Arrays;
|
||||
|
||||
CosmosClient client = new CosmosClientBuilder()
|
||||
.endpoint(serviceEndpoint)
|
||||
.key(key)
|
||||
.directMode(directConnectionConfig, gatewayConnectionConfig)
|
||||
.consistencyLevel(ConsistencyLevel.SESSION)
|
||||
.connectionSharingAcrossClientsEnabled(true)
|
||||
.contentResponseOnWriteEnabled(true)
|
||||
.userAgentSuffix("my-application")
|
||||
.preferredRegions(Arrays.asList("West US", "East US"))
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Client Hierarchy
|
||||
|
||||
| Class | Purpose |
|
||||
|-------|---------|
|
||||
| `CosmosClient` / `CosmosAsyncClient` | Account-level operations |
|
||||
| `CosmosDatabase` / `CosmosAsyncDatabase` | Database operations |
|
||||
| `CosmosContainer` / `CosmosAsyncContainer` | Container/item operations |
|
||||
|
||||
## Core Workflow
|
||||
|
||||
### Create Database
|
||||
|
||||
```java
|
||||
// Sync
|
||||
client.createDatabaseIfNotExists("myDatabase")
|
||||
.map(response -> client.getDatabase(response.getProperties().getId()));
|
||||
|
||||
// Async with chaining
|
||||
asyncClient.createDatabaseIfNotExists("myDatabase")
|
||||
.map(response -> asyncClient.getDatabase(response.getProperties().getId()))
|
||||
.subscribe(database -> System.out.println("Created: " + database.getId()));
|
||||
```
|
||||
|
||||
### Create Container
|
||||
|
||||
```java
|
||||
asyncClient.createDatabaseIfNotExists("myDatabase")
|
||||
.flatMap(dbResponse -> {
|
||||
String databaseId = dbResponse.getProperties().getId();
|
||||
return asyncClient.getDatabase(databaseId)
|
||||
.createContainerIfNotExists("myContainer", "/partitionKey")
|
||||
.map(containerResponse -> asyncClient.getDatabase(databaseId)
|
||||
.getContainer(containerResponse.getProperties().getId()));
|
||||
})
|
||||
.subscribe(container -> System.out.println("Container: " + container.getId()));
|
||||
```
|
||||
|
||||
### CRUD Operations
|
||||
|
||||
```java
|
||||
import com.azure.cosmos.models.PartitionKey;
|
||||
|
||||
CosmosAsyncContainer container = asyncClient
|
||||
.getDatabase("myDatabase")
|
||||
.getContainer("myContainer");
|
||||
|
||||
// Create
|
||||
container.createItem(new User("1", "John Doe", "john@example.com"))
|
||||
.flatMap(response -> {
|
||||
System.out.println("Created: " + response.getItem());
|
||||
// Read
|
||||
return container.readItem(
|
||||
response.getItem().getId(),
|
||||
new PartitionKey(response.getItem().getId()),
|
||||
User.class);
|
||||
})
|
||||
.flatMap(response -> {
|
||||
System.out.println("Read: " + response.getItem());
|
||||
// Update
|
||||
User user = response.getItem();
|
||||
user.setEmail("john.doe@example.com");
|
||||
return container.replaceItem(
|
||||
user,
|
||||
user.getId(),
|
||||
new PartitionKey(user.getId()));
|
||||
})
|
||||
.flatMap(response -> {
|
||||
// Delete
|
||||
return container.deleteItem(
|
||||
response.getItem().getId(),
|
||||
new PartitionKey(response.getItem().getId()));
|
||||
})
|
||||
.block();
|
||||
```
|
||||
|
||||
### Query Documents
|
||||
|
||||
```java
|
||||
import com.azure.cosmos.models.CosmosQueryRequestOptions;
|
||||
import com.azure.cosmos.util.CosmosPagedIterable;
|
||||
|
||||
CosmosContainer container = client.getDatabase("myDatabase").getContainer("myContainer");
|
||||
|
||||
String query = "SELECT * FROM c WHERE c.status = @status";
|
||||
CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
|
||||
|
||||
CosmosPagedIterable<User> results = container.queryItems(
|
||||
query,
|
||||
options,
|
||||
User.class
|
||||
);
|
||||
|
||||
results.forEach(user -> System.out.println("User: " + user.getName()));
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Partition Keys
|
||||
|
||||
Choose a partition key with:
|
||||
- High cardinality (many distinct values)
|
||||
- Even distribution of data and requests
|
||||
- Frequently used in queries
|
||||
|
||||
### Consistency Levels
|
||||
|
||||
| Level | Guarantee |
|
||||
|-------|-----------|
|
||||
| Strong | Linearizability |
|
||||
| Bounded Staleness | Consistent prefix with bounded lag |
|
||||
| Session | Consistent prefix within session |
|
||||
| Consistent Prefix | Reads never see out-of-order writes |
|
||||
| Eventual | No ordering guarantee |
|
||||
|
||||
### Request Units (RUs)
|
||||
|
||||
All operations consume RUs. Check response headers:
|
||||
|
||||
```java
|
||||
CosmosItemResponse<User> response = container.createItem(user);
|
||||
System.out.println("RU charge: " + response.getRequestCharge());
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Reuse CosmosClient** — Create once, reuse throughout application
|
||||
2. **Use async client** for high-throughput scenarios
|
||||
3. **Choose partition key carefully** — Affects performance and scalability
|
||||
4. **Enable content response on write** for immediate access to created items
|
||||
5. **Configure preferred regions** for geo-distributed applications
|
||||
6. **Handle 429 errors** with retry policies (built-in by default)
|
||||
7. **Use direct mode** for lowest latency in production
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.cosmos.CosmosException;
|
||||
|
||||
try {
|
||||
container.createItem(item);
|
||||
} catch (CosmosException e) {
|
||||
System.err.println("Status: " + e.getStatusCode());
|
||||
System.err.println("Message: " + e.getMessage());
|
||||
System.err.println("Request charge: " + e.getRequestCharge());
|
||||
|
||||
if (e.getStatusCode() == 409) {
|
||||
System.err.println("Item already exists");
|
||||
} else if (e.getStatusCode() == 429) {
|
||||
System.err.println("Rate limited, retry after: " + e.getRetryAfterDuration());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| Maven Package | https://central.sonatype.com/artifact/com.azure/azure-cosmos |
|
||||
| API Documentation | https://azuresdkdocs.z19.web.core.windows.net/java/azure-cosmos/latest/index.html |
|
||||
| Product Docs | https://learn.microsoft.com/azure/cosmos-db/ |
|
||||
| Samples | https://github.com/Azure-Samples/azure-cosmos-java-sql-api-samples |
|
||||
| Performance Guide | https://learn.microsoft.com/azure/cosmos-db/performance-tips-java-sdk-v4-sql |
|
||||
| Troubleshooting | https://learn.microsoft.com/azure/cosmos-db/troubleshoot-java-sdk-v4-sql |
|
||||
334
skills/official/microsoft/java/data/tables/SKILL.md
Normal file
334
skills/official/microsoft/java/data/tables/SKILL.md
Normal file
@@ -0,0 +1,334 @@
|
||||
---
|
||||
name: azure-data-tables-java
|
||||
description: Build table storage applications with Azure Tables SDK for Java. Use when working with Azure Table Storage or Cosmos DB Table API for NoSQL key-value data, schemaless storage, or structured data at scale.
|
||||
package: com.azure:azure-data-tables
|
||||
---
|
||||
|
||||
# Azure Tables SDK for Java
|
||||
|
||||
Build table storage applications using the Azure Tables SDK for Java. Works with both Azure Table Storage and Cosmos DB Table API.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-data-tables</artifactId>
|
||||
<version>12.6.0-beta.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### With Connection String
|
||||
|
||||
```java
|
||||
import com.azure.data.tables.TableServiceClient;
|
||||
import com.azure.data.tables.TableServiceClientBuilder;
|
||||
import com.azure.data.tables.TableClient;
|
||||
|
||||
TableServiceClient serviceClient = new TableServiceClientBuilder()
|
||||
.connectionString("<your-connection-string>")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### With Shared Key
|
||||
|
||||
```java
|
||||
import com.azure.core.credential.AzureNamedKeyCredential;
|
||||
|
||||
AzureNamedKeyCredential credential = new AzureNamedKeyCredential(
|
||||
"<account-name>",
|
||||
"<account-key>");
|
||||
|
||||
TableServiceClient serviceClient = new TableServiceClientBuilder()
|
||||
.endpoint("<your-table-account-url>")
|
||||
.credential(credential)
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### With SAS Token
|
||||
|
||||
```java
|
||||
TableServiceClient serviceClient = new TableServiceClientBuilder()
|
||||
.endpoint("<your-table-account-url>")
|
||||
.sasToken("<sas-token>")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### With DefaultAzureCredential (Storage only)
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
TableServiceClient serviceClient = new TableServiceClientBuilder()
|
||||
.endpoint("<your-table-account-url>")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **TableServiceClient**: Manage tables (create, list, delete)
|
||||
- **TableClient**: Manage entities within a table (CRUD)
|
||||
- **Partition Key**: Groups entities for efficient queries
|
||||
- **Row Key**: Unique identifier within a partition
|
||||
- **Entity**: A row with up to 252 properties (1MB Storage, 2MB Cosmos)
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Create Table
|
||||
|
||||
```java
|
||||
// Create table (throws if exists)
|
||||
TableClient tableClient = serviceClient.createTable("mytable");
|
||||
|
||||
// Create if not exists (no exception)
|
||||
TableClient tableClient = serviceClient.createTableIfNotExists("mytable");
|
||||
```
|
||||
|
||||
### Get Table Client
|
||||
|
||||
```java
|
||||
// From service client
|
||||
TableClient tableClient = serviceClient.getTableClient("mytable");
|
||||
|
||||
// Direct construction
|
||||
TableClient tableClient = new TableClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.tableName("mytable")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### Create Entity
|
||||
|
||||
```java
|
||||
import com.azure.data.tables.models.TableEntity;
|
||||
|
||||
TableEntity entity = new TableEntity("partitionKey", "rowKey")
|
||||
.addProperty("Name", "Product A")
|
||||
.addProperty("Price", 29.99)
|
||||
.addProperty("Quantity", 100)
|
||||
.addProperty("IsAvailable", true);
|
||||
|
||||
tableClient.createEntity(entity);
|
||||
```
|
||||
|
||||
### Get Entity
|
||||
|
||||
```java
|
||||
TableEntity entity = tableClient.getEntity("partitionKey", "rowKey");
|
||||
|
||||
String name = (String) entity.getProperty("Name");
|
||||
Double price = (Double) entity.getProperty("Price");
|
||||
System.out.printf("Product: %s, Price: %.2f%n", name, price);
|
||||
```
|
||||
|
||||
### Update Entity
|
||||
|
||||
```java
|
||||
import com.azure.data.tables.models.TableEntityUpdateMode;
|
||||
|
||||
// Merge (update only specified properties)
|
||||
TableEntity updateEntity = new TableEntity("partitionKey", "rowKey")
|
||||
.addProperty("Price", 24.99);
|
||||
tableClient.updateEntity(updateEntity, TableEntityUpdateMode.MERGE);
|
||||
|
||||
// Replace (replace entire entity)
|
||||
TableEntity replaceEntity = new TableEntity("partitionKey", "rowKey")
|
||||
.addProperty("Name", "Product A Updated")
|
||||
.addProperty("Price", 24.99)
|
||||
.addProperty("Quantity", 150);
|
||||
tableClient.updateEntity(replaceEntity, TableEntityUpdateMode.REPLACE);
|
||||
```
|
||||
|
||||
### Upsert Entity
|
||||
|
||||
```java
|
||||
// Insert or update (merge mode)
|
||||
tableClient.upsertEntity(entity, TableEntityUpdateMode.MERGE);
|
||||
|
||||
// Insert or replace
|
||||
tableClient.upsertEntity(entity, TableEntityUpdateMode.REPLACE);
|
||||
```
|
||||
|
||||
### Delete Entity
|
||||
|
||||
```java
|
||||
tableClient.deleteEntity("partitionKey", "rowKey");
|
||||
```
|
||||
|
||||
### List Entities
|
||||
|
||||
```java
|
||||
import com.azure.data.tables.models.ListEntitiesOptions;
|
||||
|
||||
// List all entities
|
||||
for (TableEntity entity : tableClient.listEntities()) {
|
||||
System.out.printf("%s - %s%n",
|
||||
entity.getPartitionKey(),
|
||||
entity.getRowKey());
|
||||
}
|
||||
|
||||
// With filtering and selection
|
||||
ListEntitiesOptions options = new ListEntitiesOptions()
|
||||
.setFilter("PartitionKey eq 'sales'")
|
||||
.setSelect("Name", "Price");
|
||||
|
||||
for (TableEntity entity : tableClient.listEntities(options, null, null)) {
|
||||
System.out.printf("%s: %.2f%n",
|
||||
entity.getProperty("Name"),
|
||||
entity.getProperty("Price"));
|
||||
}
|
||||
```
|
||||
|
||||
### Query with OData Filter
|
||||
|
||||
```java
|
||||
// Filter by partition key
|
||||
ListEntitiesOptions options = new ListEntitiesOptions()
|
||||
.setFilter("PartitionKey eq 'electronics'");
|
||||
|
||||
// Filter with multiple conditions
|
||||
options.setFilter("PartitionKey eq 'electronics' and Price gt 100");
|
||||
|
||||
// Filter with comparison operators
|
||||
options.setFilter("Quantity ge 10 and Quantity le 100");
|
||||
|
||||
// Top N results
|
||||
options.setTop(10);
|
||||
|
||||
for (TableEntity entity : tableClient.listEntities(options, null, null)) {
|
||||
System.out.println(entity.getRowKey());
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Operations (Transactions)
|
||||
|
||||
```java
|
||||
import com.azure.data.tables.models.TableTransactionAction;
|
||||
import com.azure.data.tables.models.TableTransactionActionType;
|
||||
import java.util.Arrays;
|
||||
|
||||
// All entities must have same partition key
|
||||
List<TableTransactionAction> actions = Arrays.asList(
|
||||
new TableTransactionAction(
|
||||
TableTransactionActionType.CREATE,
|
||||
new TableEntity("batch", "row1").addProperty("Name", "Item 1")),
|
||||
new TableTransactionAction(
|
||||
TableTransactionActionType.CREATE,
|
||||
new TableEntity("batch", "row2").addProperty("Name", "Item 2")),
|
||||
new TableTransactionAction(
|
||||
TableTransactionActionType.UPSERT_MERGE,
|
||||
new TableEntity("batch", "row3").addProperty("Name", "Item 3"))
|
||||
);
|
||||
|
||||
tableClient.submitTransaction(actions);
|
||||
```
|
||||
|
||||
### List Tables
|
||||
|
||||
```java
|
||||
import com.azure.data.tables.models.TableItem;
|
||||
import com.azure.data.tables.models.ListTablesOptions;
|
||||
|
||||
// List all tables
|
||||
for (TableItem table : serviceClient.listTables()) {
|
||||
System.out.println(table.getName());
|
||||
}
|
||||
|
||||
// Filter tables
|
||||
ListTablesOptions options = new ListTablesOptions()
|
||||
.setFilter("TableName eq 'mytable'");
|
||||
|
||||
for (TableItem table : serviceClient.listTables(options, null, null)) {
|
||||
System.out.println(table.getName());
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Table
|
||||
|
||||
```java
|
||||
serviceClient.deleteTable("mytable");
|
||||
```
|
||||
|
||||
## Typed Entities
|
||||
|
||||
```java
|
||||
public class Product implements TableEntity {
|
||||
private String partitionKey;
|
||||
private String rowKey;
|
||||
private OffsetDateTime timestamp;
|
||||
private String eTag;
|
||||
private String name;
|
||||
private double price;
|
||||
|
||||
// Getters and setters for all fields
|
||||
@Override
|
||||
public String getPartitionKey() { return partitionKey; }
|
||||
@Override
|
||||
public void setPartitionKey(String partitionKey) { this.partitionKey = partitionKey; }
|
||||
@Override
|
||||
public String getRowKey() { return rowKey; }
|
||||
@Override
|
||||
public void setRowKey(String rowKey) { this.rowKey = rowKey; }
|
||||
// ... other getters/setters
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public double getPrice() { return price; }
|
||||
public void setPrice(double price) { this.price = price; }
|
||||
}
|
||||
|
||||
// Usage
|
||||
Product product = new Product();
|
||||
product.setPartitionKey("electronics");
|
||||
product.setRowKey("laptop-001");
|
||||
product.setName("Laptop");
|
||||
product.setPrice(999.99);
|
||||
|
||||
tableClient.createEntity(product);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.data.tables.models.TableServiceException;
|
||||
|
||||
try {
|
||||
tableClient.createEntity(entity);
|
||||
} catch (TableServiceException e) {
|
||||
System.out.println("Status: " + e.getResponse().getStatusCode());
|
||||
System.out.println("Error: " + e.getMessage());
|
||||
// 409 = Conflict (entity exists)
|
||||
// 404 = Not Found
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# Storage Account
|
||||
AZURE_TABLES_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
|
||||
AZURE_TABLES_ENDPOINT=https://<account>.table.core.windows.net
|
||||
|
||||
# Cosmos DB Table API
|
||||
COSMOS_TABLE_ENDPOINT=https://<account>.table.cosmosdb.azure.com
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Partition Key Design**: Choose keys that distribute load evenly
|
||||
2. **Batch Operations**: Use transactions for atomic multi-entity updates
|
||||
3. **Query Optimization**: Always filter by PartitionKey when possible
|
||||
4. **Select Projection**: Only select needed properties for performance
|
||||
5. **Entity Size**: Keep entities under 1MB (Storage) or 2MB (Cosmos)
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "Azure Tables Java"
|
||||
- "table storage SDK"
|
||||
- "Cosmos DB Table API"
|
||||
- "NoSQL key-value storage"
|
||||
- "partition key row key"
|
||||
- "table entity CRUD"
|
||||
366
skills/official/microsoft/java/entra/azure-identity/SKILL.md
Normal file
366
skills/official/microsoft/java/entra/azure-identity/SKILL.md
Normal file
@@ -0,0 +1,366 @@
|
||||
---
|
||||
name: azure-identity-java
|
||||
description: Azure Identity Java SDK for authentication with Azure services. Use when implementing DefaultAzureCredential, managed identity, service principal, or any Azure authentication pattern in Java applications.
|
||||
package: com.azure:azure-identity
|
||||
---
|
||||
|
||||
# Azure Identity (Java)
|
||||
|
||||
Authenticate Java applications with Azure services using Microsoft Entra ID (Azure AD).
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-identity</artifactId>
|
||||
<version>1.15.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Credential | Use Case |
|
||||
|------------|----------|
|
||||
| `DefaultAzureCredential` | **Recommended** - Works in dev and production |
|
||||
| `ManagedIdentityCredential` | Azure-hosted apps (App Service, Functions, VMs) |
|
||||
| `EnvironmentCredential` | CI/CD pipelines with env vars |
|
||||
| `ClientSecretCredential` | Service principals with secret |
|
||||
| `ClientCertificateCredential` | Service principals with certificate |
|
||||
| `AzureCliCredential` | Local dev using `az login` |
|
||||
| `InteractiveBrowserCredential` | Interactive login flow |
|
||||
| `DeviceCodeCredential` | Headless device authentication |
|
||||
|
||||
## DefaultAzureCredential (Recommended)
|
||||
|
||||
The `DefaultAzureCredential` tries multiple authentication methods in order:
|
||||
|
||||
1. Environment variables
|
||||
2. Workload Identity
|
||||
3. Managed Identity
|
||||
4. Azure CLI
|
||||
5. Azure PowerShell
|
||||
6. Azure Developer CLI
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredential;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
// Simple usage
|
||||
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
|
||||
|
||||
// Use with any Azure client
|
||||
BlobServiceClient blobClient = new BlobServiceClientBuilder()
|
||||
.endpoint("https://<storage-account>.blob.core.windows.net")
|
||||
.credential(credential)
|
||||
.buildClient();
|
||||
|
||||
KeyClient keyClient = new KeyClientBuilder()
|
||||
.vaultUrl("https://<vault-name>.vault.azure.net")
|
||||
.credential(credential)
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### Configure DefaultAzureCredential
|
||||
|
||||
```java
|
||||
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder()
|
||||
.managedIdentityClientId("<user-assigned-identity-client-id>") // For user-assigned MI
|
||||
.tenantId("<tenant-id>") // Limit to specific tenant
|
||||
.excludeEnvironmentCredential() // Skip env vars
|
||||
.excludeAzureCliCredential() // Skip Azure CLI
|
||||
.build();
|
||||
```
|
||||
|
||||
## Managed Identity
|
||||
|
||||
For Azure-hosted applications (App Service, Functions, AKS, VMs).
|
||||
|
||||
```java
|
||||
import com.azure.identity.ManagedIdentityCredential;
|
||||
import com.azure.identity.ManagedIdentityCredentialBuilder;
|
||||
|
||||
// System-assigned managed identity
|
||||
ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder()
|
||||
.build();
|
||||
|
||||
// User-assigned managed identity (by client ID)
|
||||
ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder()
|
||||
.clientId("<user-assigned-client-id>")
|
||||
.build();
|
||||
|
||||
// User-assigned managed identity (by resource ID)
|
||||
ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder()
|
||||
.resourceId("/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<name>")
|
||||
.build();
|
||||
```
|
||||
|
||||
## Service Principal with Secret
|
||||
|
||||
```java
|
||||
import com.azure.identity.ClientSecretCredential;
|
||||
import com.azure.identity.ClientSecretCredentialBuilder;
|
||||
|
||||
ClientSecretCredential credential = new ClientSecretCredentialBuilder()
|
||||
.tenantId("<tenant-id>")
|
||||
.clientId("<client-id>")
|
||||
.clientSecret("<client-secret>")
|
||||
.build();
|
||||
```
|
||||
|
||||
## Service Principal with Certificate
|
||||
|
||||
```java
|
||||
import com.azure.identity.ClientCertificateCredential;
|
||||
import com.azure.identity.ClientCertificateCredentialBuilder;
|
||||
|
||||
// From PEM file
|
||||
ClientCertificateCredential credential = new ClientCertificateCredentialBuilder()
|
||||
.tenantId("<tenant-id>")
|
||||
.clientId("<client-id>")
|
||||
.pemCertificate("<path-to-cert.pem>")
|
||||
.build();
|
||||
|
||||
// From PFX file with password
|
||||
ClientCertificateCredential credential = new ClientCertificateCredentialBuilder()
|
||||
.tenantId("<tenant-id>")
|
||||
.clientId("<client-id>")
|
||||
.pfxCertificate("<path-to-cert.pfx>", "<pfx-password>")
|
||||
.build();
|
||||
|
||||
// Send certificate chain for SNI
|
||||
ClientCertificateCredential credential = new ClientCertificateCredentialBuilder()
|
||||
.tenantId("<tenant-id>")
|
||||
.clientId("<client-id>")
|
||||
.pemCertificate("<path-to-cert.pem>")
|
||||
.sendCertificateChain(true)
|
||||
.build();
|
||||
```
|
||||
|
||||
## Environment Credential
|
||||
|
||||
Reads credentials from environment variables.
|
||||
|
||||
```java
|
||||
import com.azure.identity.EnvironmentCredential;
|
||||
import com.azure.identity.EnvironmentCredentialBuilder;
|
||||
|
||||
EnvironmentCredential credential = new EnvironmentCredentialBuilder().build();
|
||||
```
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
**For service principal with secret:**
|
||||
```bash
|
||||
AZURE_TENANT_ID=<tenant-id>
|
||||
AZURE_CLIENT_ID=<client-id>
|
||||
AZURE_CLIENT_SECRET=<client-secret>
|
||||
```
|
||||
|
||||
**For service principal with certificate:**
|
||||
```bash
|
||||
AZURE_TENANT_ID=<tenant-id>
|
||||
AZURE_CLIENT_ID=<client-id>
|
||||
AZURE_CLIENT_CERTIFICATE_PATH=/path/to/cert.pem
|
||||
AZURE_CLIENT_CERTIFICATE_PASSWORD=<optional-password>
|
||||
```
|
||||
|
||||
**For username/password:**
|
||||
```bash
|
||||
AZURE_TENANT_ID=<tenant-id>
|
||||
AZURE_CLIENT_ID=<client-id>
|
||||
AZURE_USERNAME=<username>
|
||||
AZURE_PASSWORD=<password>
|
||||
```
|
||||
|
||||
## Azure CLI Credential
|
||||
|
||||
For local development using `az login`.
|
||||
|
||||
```java
|
||||
import com.azure.identity.AzureCliCredential;
|
||||
import com.azure.identity.AzureCliCredentialBuilder;
|
||||
|
||||
AzureCliCredential credential = new AzureCliCredentialBuilder()
|
||||
.tenantId("<tenant-id>") // Optional: specific tenant
|
||||
.build();
|
||||
```
|
||||
|
||||
## Interactive Browser
|
||||
|
||||
For desktop applications requiring user login.
|
||||
|
||||
```java
|
||||
import com.azure.identity.InteractiveBrowserCredential;
|
||||
import com.azure.identity.InteractiveBrowserCredentialBuilder;
|
||||
|
||||
InteractiveBrowserCredential credential = new InteractiveBrowserCredentialBuilder()
|
||||
.clientId("<client-id>")
|
||||
.redirectUrl("http://localhost:8080") // Must match app registration
|
||||
.build();
|
||||
```
|
||||
|
||||
## Device Code
|
||||
|
||||
For headless devices (IoT, CLI tools).
|
||||
|
||||
```java
|
||||
import com.azure.identity.DeviceCodeCredential;
|
||||
import com.azure.identity.DeviceCodeCredentialBuilder;
|
||||
|
||||
DeviceCodeCredential credential = new DeviceCodeCredentialBuilder()
|
||||
.clientId("<client-id>")
|
||||
.challengeConsumer(challenge -> {
|
||||
// Display to user
|
||||
System.out.println(challenge.getMessage());
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
## Chained Credential
|
||||
|
||||
Create custom authentication chains.
|
||||
|
||||
```java
|
||||
import com.azure.identity.ChainedTokenCredential;
|
||||
import com.azure.identity.ChainedTokenCredentialBuilder;
|
||||
|
||||
ChainedTokenCredential credential = new ChainedTokenCredentialBuilder()
|
||||
.addFirst(new ManagedIdentityCredentialBuilder().build())
|
||||
.addLast(new AzureCliCredentialBuilder().build())
|
||||
.build();
|
||||
```
|
||||
|
||||
## Workload Identity (AKS)
|
||||
|
||||
For Azure Kubernetes Service with workload identity.
|
||||
|
||||
```java
|
||||
import com.azure.identity.WorkloadIdentityCredential;
|
||||
import com.azure.identity.WorkloadIdentityCredentialBuilder;
|
||||
|
||||
// Reads from AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE
|
||||
WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().build();
|
||||
|
||||
// Or explicit configuration
|
||||
WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder()
|
||||
.tenantId("<tenant-id>")
|
||||
.clientId("<client-id>")
|
||||
.tokenFilePath("/var/run/secrets/azure/tokens/azure-identity-token")
|
||||
.build();
|
||||
```
|
||||
|
||||
## Token Caching
|
||||
|
||||
Enable persistent token caching for better performance.
|
||||
|
||||
```java
|
||||
// Enable token caching (in-memory by default)
|
||||
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder()
|
||||
.enableAccountIdentifierLogging()
|
||||
.build();
|
||||
|
||||
// With shared token cache (for multi-credential scenarios)
|
||||
SharedTokenCacheCredential credential = new SharedTokenCacheCredentialBuilder()
|
||||
.clientId("<client-id>")
|
||||
.build();
|
||||
```
|
||||
|
||||
## Sovereign Clouds
|
||||
|
||||
```java
|
||||
import com.azure.identity.AzureAuthorityHosts;
|
||||
|
||||
// Azure Government
|
||||
DefaultAzureCredential govCredential = new DefaultAzureCredentialBuilder()
|
||||
.authorityHost(AzureAuthorityHosts.AZURE_GOVERNMENT)
|
||||
.build();
|
||||
|
||||
// Azure China
|
||||
DefaultAzureCredential chinaCredential = new DefaultAzureCredentialBuilder()
|
||||
.authorityHost(AzureAuthorityHosts.AZURE_CHINA)
|
||||
.build();
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.identity.CredentialUnavailableException;
|
||||
import com.azure.core.exception.ClientAuthenticationException;
|
||||
|
||||
try {
|
||||
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
|
||||
AccessToken token = credential.getToken(new TokenRequestContext()
|
||||
.addScopes("https://management.azure.com/.default"));
|
||||
} catch (CredentialUnavailableException e) {
|
||||
// No credential could authenticate
|
||||
System.out.println("Authentication failed: " + e.getMessage());
|
||||
} catch (ClientAuthenticationException e) {
|
||||
// Authentication error (wrong credentials, expired, etc.)
|
||||
System.out.println("Auth error: " + e.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
Enable authentication logging for debugging.
|
||||
|
||||
```java
|
||||
// Via environment variable
|
||||
// AZURE_LOG_LEVEL=verbose
|
||||
|
||||
// Or programmatically
|
||||
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder()
|
||||
.enableAccountIdentifierLogging() // Log account info
|
||||
.build();
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# DefaultAzureCredential configuration
|
||||
AZURE_TENANT_ID=<tenant-id>
|
||||
AZURE_CLIENT_ID=<client-id>
|
||||
AZURE_CLIENT_SECRET=<client-secret>
|
||||
|
||||
# Managed Identity
|
||||
AZURE_CLIENT_ID=<user-assigned-mi-client-id>
|
||||
|
||||
# Workload Identity (AKS)
|
||||
AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token
|
||||
|
||||
# Logging
|
||||
AZURE_LOG_LEVEL=verbose
|
||||
|
||||
# Authority host
|
||||
AZURE_AUTHORITY_HOST=https://login.microsoftonline.com/
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use DefaultAzureCredential** - Works seamlessly from dev to production
|
||||
2. **Managed Identity in Production** - No secrets to manage, automatic rotation
|
||||
3. **Azure CLI for Local Dev** - Run `az login` before running your app
|
||||
4. **Least Privilege** - Grant only required permissions to service principals
|
||||
5. **Token Caching** - Enabled by default, reduces auth round-trips
|
||||
6. **Environment Variables** - Use for CI/CD, not hardcoded secrets
|
||||
|
||||
## Credential Selection Matrix
|
||||
|
||||
| Environment | Recommended Credential |
|
||||
|-------------|----------------------|
|
||||
| Local Development | `DefaultAzureCredential` (uses Azure CLI) |
|
||||
| Azure App Service | `DefaultAzureCredential` (uses Managed Identity) |
|
||||
| Azure Functions | `DefaultAzureCredential` (uses Managed Identity) |
|
||||
| Azure Kubernetes Service | `WorkloadIdentityCredential` |
|
||||
| Azure VMs | `DefaultAzureCredential` (uses Managed Identity) |
|
||||
| CI/CD Pipeline | `EnvironmentCredential` |
|
||||
| Desktop App | `InteractiveBrowserCredential` |
|
||||
| CLI Tool | `DeviceCodeCredential` |
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "Azure authentication Java", "DefaultAzureCredential Java"
|
||||
- "managed identity Java", "service principal Java"
|
||||
- "Azure login Java", "Azure credentials Java"
|
||||
- "AZURE_CLIENT_ID", "AZURE_TENANT_ID"
|
||||
362
skills/official/microsoft/java/entra/keyvault-keys/SKILL.md
Normal file
362
skills/official/microsoft/java/entra/keyvault-keys/SKILL.md
Normal file
@@ -0,0 +1,362 @@
|
||||
---
|
||||
name: azure-security-keyvault-keys-java
|
||||
description: Azure Key Vault Keys Java SDK for cryptographic key management. Use when creating, managing, or using RSA/EC keys, performing encrypt/decrypt/sign/verify operations, or working with HSM-backed keys.
|
||||
package: com.azure:azure-security-keyvault-keys
|
||||
---
|
||||
|
||||
# Azure Key Vault Keys (Java)
|
||||
|
||||
Manage cryptographic keys and perform cryptographic operations in Azure Key Vault and Managed HSM.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-security-keyvault-keys</artifactId>
|
||||
<version>4.9.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
```java
|
||||
import com.azure.security.keyvault.keys.KeyClient;
|
||||
import com.azure.security.keyvault.keys.KeyClientBuilder;
|
||||
import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
|
||||
import com.azure.security.keyvault.keys.cryptography.CryptographyClientBuilder;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
// Key management client
|
||||
KeyClient keyClient = new KeyClientBuilder()
|
||||
.vaultUrl("https://<vault-name>.vault.azure.net")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
|
||||
// Async client
|
||||
KeyAsyncClient keyAsyncClient = new KeyClientBuilder()
|
||||
.vaultUrl("https://<vault-name>.vault.azure.net")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildAsyncClient();
|
||||
|
||||
// Cryptography client (for encrypt/decrypt/sign/verify)
|
||||
CryptographyClient cryptoClient = new CryptographyClientBuilder()
|
||||
.keyIdentifier("https://<vault-name>.vault.azure.net/keys/<key-name>/<key-version>")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Key Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `RSA` | RSA key (2048, 3072, 4096 bits) |
|
||||
| `RSA_HSM` | RSA key in HSM |
|
||||
| `EC` | Elliptic Curve key |
|
||||
| `EC_HSM` | Elliptic Curve key in HSM |
|
||||
| `OCT` | Symmetric key (Managed HSM only) |
|
||||
| `OCT_HSM` | Symmetric key in HSM |
|
||||
|
||||
## Create Keys
|
||||
|
||||
### Create RSA Key
|
||||
|
||||
```java
|
||||
import com.azure.security.keyvault.keys.models.*;
|
||||
|
||||
// Simple RSA key
|
||||
KeyVaultKey rsaKey = keyClient.createRsaKey(new CreateRsaKeyOptions("my-rsa-key")
|
||||
.setKeySize(2048));
|
||||
|
||||
System.out.println("Key name: " + rsaKey.getName());
|
||||
System.out.println("Key ID: " + rsaKey.getId());
|
||||
System.out.println("Key type: " + rsaKey.getKeyType());
|
||||
|
||||
// RSA key with options
|
||||
KeyVaultKey rsaKeyWithOptions = keyClient.createRsaKey(new CreateRsaKeyOptions("my-rsa-key-2")
|
||||
.setKeySize(4096)
|
||||
.setExpiresOn(OffsetDateTime.now().plusYears(1))
|
||||
.setNotBefore(OffsetDateTime.now())
|
||||
.setEnabled(true)
|
||||
.setKeyOperations(KeyOperation.ENCRYPT, KeyOperation.DECRYPT,
|
||||
KeyOperation.WRAP_KEY, KeyOperation.UNWRAP_KEY)
|
||||
.setTags(Map.of("environment", "production")));
|
||||
|
||||
// HSM-backed RSA key
|
||||
KeyVaultKey hsmKey = keyClient.createRsaKey(new CreateRsaKeyOptions("my-hsm-key")
|
||||
.setKeySize(2048)
|
||||
.setHardwareProtected(true));
|
||||
```
|
||||
|
||||
### Create EC Key
|
||||
|
||||
```java
|
||||
// EC key with P-256 curve
|
||||
KeyVaultKey ecKey = keyClient.createEcKey(new CreateEcKeyOptions("my-ec-key")
|
||||
.setCurveName(KeyCurveName.P_256));
|
||||
|
||||
// EC key with other curves
|
||||
KeyVaultKey ecKey384 = keyClient.createEcKey(new CreateEcKeyOptions("my-ec-key-384")
|
||||
.setCurveName(KeyCurveName.P_384));
|
||||
|
||||
KeyVaultKey ecKey521 = keyClient.createEcKey(new CreateEcKeyOptions("my-ec-key-521")
|
||||
.setCurveName(KeyCurveName.P_521));
|
||||
|
||||
// HSM-backed EC key
|
||||
KeyVaultKey ecHsmKey = keyClient.createEcKey(new CreateEcKeyOptions("my-ec-hsm-key")
|
||||
.setCurveName(KeyCurveName.P_256)
|
||||
.setHardwareProtected(true));
|
||||
```
|
||||
|
||||
### Create Symmetric Key (Managed HSM only)
|
||||
|
||||
```java
|
||||
KeyVaultKey octKey = keyClient.createOctKey(new CreateOctKeyOptions("my-symmetric-key")
|
||||
.setKeySize(256)
|
||||
.setHardwareProtected(true));
|
||||
```
|
||||
|
||||
## Get Key
|
||||
|
||||
```java
|
||||
// Get latest version
|
||||
KeyVaultKey key = keyClient.getKey("my-key");
|
||||
|
||||
// Get specific version
|
||||
KeyVaultKey keyVersion = keyClient.getKey("my-key", "<version-id>");
|
||||
|
||||
// Get only key properties (no key material)
|
||||
KeyProperties keyProps = keyClient.getKey("my-key").getProperties();
|
||||
```
|
||||
|
||||
## Update Key Properties
|
||||
|
||||
```java
|
||||
KeyVaultKey key = keyClient.getKey("my-key");
|
||||
|
||||
// Update properties
|
||||
key.getProperties()
|
||||
.setEnabled(false)
|
||||
.setExpiresOn(OffsetDateTime.now().plusMonths(6))
|
||||
.setTags(Map.of("status", "archived"));
|
||||
|
||||
KeyVaultKey updatedKey = keyClient.updateKeyProperties(key.getProperties(),
|
||||
KeyOperation.ENCRYPT, KeyOperation.DECRYPT);
|
||||
```
|
||||
|
||||
## List Keys
|
||||
|
||||
```java
|
||||
import com.azure.core.util.paging.PagedIterable;
|
||||
|
||||
// List all keys
|
||||
for (KeyProperties keyProps : keyClient.listPropertiesOfKeys()) {
|
||||
System.out.println("Key: " + keyProps.getName());
|
||||
System.out.println(" Enabled: " + keyProps.isEnabled());
|
||||
System.out.println(" Created: " + keyProps.getCreatedOn());
|
||||
}
|
||||
|
||||
// List key versions
|
||||
for (KeyProperties version : keyClient.listPropertiesOfKeyVersions("my-key")) {
|
||||
System.out.println("Version: " + version.getVersion());
|
||||
System.out.println("Created: " + version.getCreatedOn());
|
||||
}
|
||||
```
|
||||
|
||||
## Delete Key
|
||||
|
||||
```java
|
||||
import com.azure.core.util.polling.SyncPoller;
|
||||
|
||||
// Begin delete (soft-delete enabled vaults)
|
||||
SyncPoller<DeletedKey, Void> deletePoller = keyClient.beginDeleteKey("my-key");
|
||||
|
||||
// Wait for deletion
|
||||
DeletedKey deletedKey = deletePoller.poll().getValue();
|
||||
System.out.println("Deleted: " + deletedKey.getDeletedOn());
|
||||
|
||||
deletePoller.waitForCompletion();
|
||||
|
||||
// Purge deleted key (permanent deletion)
|
||||
keyClient.purgeDeletedKey("my-key");
|
||||
|
||||
// Recover deleted key
|
||||
SyncPoller<KeyVaultKey, Void> recoverPoller = keyClient.beginRecoverDeletedKey("my-key");
|
||||
recoverPoller.waitForCompletion();
|
||||
```
|
||||
|
||||
## Cryptographic Operations
|
||||
|
||||
### Encrypt/Decrypt
|
||||
|
||||
```java
|
||||
import com.azure.security.keyvault.keys.cryptography.models.*;
|
||||
|
||||
CryptographyClient cryptoClient = new CryptographyClientBuilder()
|
||||
.keyIdentifier("https://<vault>.vault.azure.net/keys/<key-name>")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
|
||||
byte[] plaintext = "Hello, World!".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// Encrypt
|
||||
EncryptResult encryptResult = cryptoClient.encrypt(EncryptionAlgorithm.RSA_OAEP, plaintext);
|
||||
byte[] ciphertext = encryptResult.getCipherText();
|
||||
System.out.println("Ciphertext length: " + ciphertext.length);
|
||||
|
||||
// Decrypt
|
||||
DecryptResult decryptResult = cryptoClient.decrypt(EncryptionAlgorithm.RSA_OAEP, ciphertext);
|
||||
String decrypted = new String(decryptResult.getPlainText(), StandardCharsets.UTF_8);
|
||||
System.out.println("Decrypted: " + decrypted);
|
||||
```
|
||||
|
||||
### Sign/Verify
|
||||
|
||||
```java
|
||||
import java.security.MessageDigest;
|
||||
|
||||
// Create digest of data
|
||||
byte[] data = "Data to sign".getBytes(StandardCharsets.UTF_8);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] digest = md.digest(data);
|
||||
|
||||
// Sign
|
||||
SignResult signResult = cryptoClient.sign(SignatureAlgorithm.RS256, digest);
|
||||
byte[] signature = signResult.getSignature();
|
||||
|
||||
// Verify
|
||||
VerifyResult verifyResult = cryptoClient.verify(SignatureAlgorithm.RS256, digest, signature);
|
||||
System.out.println("Valid signature: " + verifyResult.isValid());
|
||||
```
|
||||
|
||||
### Wrap/Unwrap Key
|
||||
|
||||
```java
|
||||
// Key to wrap (e.g., AES key)
|
||||
byte[] keyToWrap = new byte[32]; // 256-bit key
|
||||
new SecureRandom().nextBytes(keyToWrap);
|
||||
|
||||
// Wrap
|
||||
WrapResult wrapResult = cryptoClient.wrapKey(KeyWrapAlgorithm.RSA_OAEP, keyToWrap);
|
||||
byte[] wrappedKey = wrapResult.getEncryptedKey();
|
||||
|
||||
// Unwrap
|
||||
UnwrapResult unwrapResult = cryptoClient.unwrapKey(KeyWrapAlgorithm.RSA_OAEP, wrappedKey);
|
||||
byte[] unwrappedKey = unwrapResult.getKey();
|
||||
```
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
```java
|
||||
// Backup
|
||||
byte[] backup = keyClient.backupKey("my-key");
|
||||
|
||||
// Save backup to file
|
||||
Files.write(Paths.get("key-backup.blob"), backup);
|
||||
|
||||
// Restore
|
||||
byte[] backupData = Files.readAllBytes(Paths.get("key-backup.blob"));
|
||||
KeyVaultKey restoredKey = keyClient.restoreKeyBackup(backupData);
|
||||
```
|
||||
|
||||
## Key Rotation
|
||||
|
||||
```java
|
||||
// Rotate to new version
|
||||
KeyVaultKey rotatedKey = keyClient.rotateKey("my-key");
|
||||
System.out.println("New version: " + rotatedKey.getProperties().getVersion());
|
||||
|
||||
// Set rotation policy
|
||||
KeyRotationPolicy policy = new KeyRotationPolicy()
|
||||
.setExpiresIn("P90D") // Expire after 90 days
|
||||
.setLifetimeActions(Arrays.asList(
|
||||
new KeyRotationLifetimeAction(KeyRotationPolicyAction.ROTATE)
|
||||
.setTimeBeforeExpiry("P30D"))); // Rotate 30 days before expiry
|
||||
|
||||
keyClient.updateKeyRotationPolicy("my-key", policy);
|
||||
|
||||
// Get rotation policy
|
||||
KeyRotationPolicy currentPolicy = keyClient.getKeyRotationPolicy("my-key");
|
||||
```
|
||||
|
||||
## Import Key
|
||||
|
||||
```java
|
||||
import com.azure.security.keyvault.keys.models.ImportKeyOptions;
|
||||
import com.azure.security.keyvault.keys.models.JsonWebKey;
|
||||
|
||||
// Import existing key material
|
||||
JsonWebKey jsonWebKey = new JsonWebKey()
|
||||
.setKeyType(KeyType.RSA)
|
||||
.setN(modulus)
|
||||
.setE(exponent)
|
||||
.setD(privateExponent)
|
||||
// ... other RSA components
|
||||
;
|
||||
|
||||
ImportKeyOptions importOptions = new ImportKeyOptions("imported-key", jsonWebKey)
|
||||
.setHardwareProtected(false);
|
||||
|
||||
KeyVaultKey importedKey = keyClient.importKey(importOptions);
|
||||
```
|
||||
|
||||
## Encryption Algorithms
|
||||
|
||||
| Algorithm | Key Type | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `RSA1_5` | RSA | RSAES-PKCS1-v1_5 |
|
||||
| `RSA_OAEP` | RSA | RSAES with OAEP (recommended) |
|
||||
| `RSA_OAEP_256` | RSA | RSAES with OAEP using SHA-256 |
|
||||
| `A128GCM` | OCT | AES-GCM 128-bit |
|
||||
| `A256GCM` | OCT | AES-GCM 256-bit |
|
||||
| `A128CBC` | OCT | AES-CBC 128-bit |
|
||||
| `A256CBC` | OCT | AES-CBC 256-bit |
|
||||
|
||||
## Signature Algorithms
|
||||
|
||||
| Algorithm | Key Type | Hash |
|
||||
|-----------|----------|------|
|
||||
| `RS256` | RSA | SHA-256 |
|
||||
| `RS384` | RSA | SHA-384 |
|
||||
| `RS512` | RSA | SHA-512 |
|
||||
| `PS256` | RSA | SHA-256 (PSS) |
|
||||
| `ES256` | EC P-256 | SHA-256 |
|
||||
| `ES384` | EC P-384 | SHA-384 |
|
||||
| `ES512` | EC P-521 | SHA-512 |
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
import com.azure.core.exception.ResourceNotFoundException;
|
||||
|
||||
try {
|
||||
KeyVaultKey key = keyClient.getKey("non-existent-key");
|
||||
} catch (ResourceNotFoundException e) {
|
||||
System.out.println("Key not found: " + e.getMessage());
|
||||
} catch (HttpResponseException e) {
|
||||
System.out.println("HTTP error " + e.getResponse().getStatusCode());
|
||||
System.out.println("Message: " + e.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_KEYVAULT_URL=https://<vault-name>.vault.azure.net
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use HSM Keys for Production** - Set `setHardwareProtected(true)` for sensitive keys
|
||||
2. **Enable Soft Delete** - Protects against accidental deletion
|
||||
3. **Key Rotation** - Set up automatic rotation policies
|
||||
4. **Least Privilege** - Use separate keys for different operations
|
||||
5. **Local Crypto When Possible** - Use `CryptographyClient` with local key material to reduce round-trips
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "Key Vault keys Java", "cryptographic keys Java"
|
||||
- "encrypt decrypt Java", "sign verify Java"
|
||||
- "RSA key", "EC key", "HSM key"
|
||||
- "key rotation", "wrap unwrap key"
|
||||
356
skills/official/microsoft/java/entra/keyvault-secrets/SKILL.md
Normal file
356
skills/official/microsoft/java/entra/keyvault-secrets/SKILL.md
Normal file
@@ -0,0 +1,356 @@
|
||||
---
|
||||
name: azure-security-keyvault-secrets-java
|
||||
description: Azure Key Vault Secrets Java SDK for secret management. Use when storing, retrieving, or managing passwords, API keys, connection strings, or other sensitive configuration data.
|
||||
package: com.azure:azure-security-keyvault-secrets
|
||||
---
|
||||
|
||||
# Azure Key Vault Secrets (Java)
|
||||
|
||||
Securely store and manage secrets like passwords, API keys, and connection strings.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-security-keyvault-secrets</artifactId>
|
||||
<version>4.9.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
```java
|
||||
import com.azure.security.keyvault.secrets.SecretClient;
|
||||
import com.azure.security.keyvault.secrets.SecretClientBuilder;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
// Sync client
|
||||
SecretClient secretClient = new SecretClientBuilder()
|
||||
.vaultUrl("https://<vault-name>.vault.azure.net")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
|
||||
// Async client
|
||||
SecretAsyncClient secretAsyncClient = new SecretClientBuilder()
|
||||
.vaultUrl("https://<vault-name>.vault.azure.net")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
## Create/Set Secret
|
||||
|
||||
```java
|
||||
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
|
||||
|
||||
// Simple secret
|
||||
KeyVaultSecret secret = secretClient.setSecret("database-password", "P@ssw0rd123!");
|
||||
System.out.println("Secret name: " + secret.getName());
|
||||
System.out.println("Secret ID: " + secret.getId());
|
||||
|
||||
// Secret with options
|
||||
KeyVaultSecret secretWithOptions = secretClient.setSecret(
|
||||
new KeyVaultSecret("api-key", "sk_live_abc123xyz")
|
||||
.setProperties(new SecretProperties()
|
||||
.setContentType("application/json")
|
||||
.setExpiresOn(OffsetDateTime.now().plusYears(1))
|
||||
.setNotBefore(OffsetDateTime.now())
|
||||
.setEnabled(true)
|
||||
.setTags(Map.of(
|
||||
"environment", "production",
|
||||
"service", "payment-api"
|
||||
))
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Get Secret
|
||||
|
||||
```java
|
||||
// Get latest version
|
||||
KeyVaultSecret secret = secretClient.getSecret("database-password");
|
||||
String value = secret.getValue();
|
||||
System.out.println("Secret value: " + value);
|
||||
|
||||
// Get specific version
|
||||
KeyVaultSecret specificVersion = secretClient.getSecret("database-password", "<version-id>");
|
||||
|
||||
// Get only properties (no value)
|
||||
SecretProperties props = secretClient.getSecret("database-password").getProperties();
|
||||
System.out.println("Enabled: " + props.isEnabled());
|
||||
System.out.println("Created: " + props.getCreatedOn());
|
||||
```
|
||||
|
||||
## Update Secret Properties
|
||||
|
||||
```java
|
||||
// Get secret
|
||||
KeyVaultSecret secret = secretClient.getSecret("api-key");
|
||||
|
||||
// Update properties (cannot update value - create new version instead)
|
||||
secret.getProperties()
|
||||
.setEnabled(false)
|
||||
.setExpiresOn(OffsetDateTime.now().plusMonths(6))
|
||||
.setTags(Map.of("status", "rotating"));
|
||||
|
||||
SecretProperties updated = secretClient.updateSecretProperties(secret.getProperties());
|
||||
System.out.println("Updated: " + updated.getUpdatedOn());
|
||||
```
|
||||
|
||||
## List Secrets
|
||||
|
||||
```java
|
||||
import com.azure.core.util.paging.PagedIterable;
|
||||
import com.azure.security.keyvault.secrets.models.SecretProperties;
|
||||
|
||||
// List all secrets (properties only, no values)
|
||||
for (SecretProperties secretProps : secretClient.listPropertiesOfSecrets()) {
|
||||
System.out.println("Secret: " + secretProps.getName());
|
||||
System.out.println(" Enabled: " + secretProps.isEnabled());
|
||||
System.out.println(" Created: " + secretProps.getCreatedOn());
|
||||
System.out.println(" Content-Type: " + secretProps.getContentType());
|
||||
|
||||
// Get value if needed
|
||||
if (secretProps.isEnabled()) {
|
||||
KeyVaultSecret fullSecret = secretClient.getSecret(secretProps.getName());
|
||||
System.out.println(" Value: " + fullSecret.getValue().substring(0, 5) + "...");
|
||||
}
|
||||
}
|
||||
|
||||
// List versions of a secret
|
||||
for (SecretProperties version : secretClient.listPropertiesOfSecretVersions("database-password")) {
|
||||
System.out.println("Version: " + version.getVersion());
|
||||
System.out.println("Created: " + version.getCreatedOn());
|
||||
System.out.println("Enabled: " + version.isEnabled());
|
||||
}
|
||||
```
|
||||
|
||||
## Delete Secret
|
||||
|
||||
```java
|
||||
import com.azure.core.util.polling.SyncPoller;
|
||||
import com.azure.security.keyvault.secrets.models.DeletedSecret;
|
||||
|
||||
// Begin delete (returns poller for soft-delete enabled vaults)
|
||||
SyncPoller<DeletedSecret, Void> deletePoller = secretClient.beginDeleteSecret("old-secret");
|
||||
|
||||
// Wait for deletion
|
||||
DeletedSecret deletedSecret = deletePoller.poll().getValue();
|
||||
System.out.println("Deleted on: " + deletedSecret.getDeletedOn());
|
||||
System.out.println("Scheduled purge: " + deletedSecret.getScheduledPurgeDate());
|
||||
|
||||
deletePoller.waitForCompletion();
|
||||
```
|
||||
|
||||
## Recover Deleted Secret
|
||||
|
||||
```java
|
||||
// List deleted secrets
|
||||
for (DeletedSecret deleted : secretClient.listDeletedSecrets()) {
|
||||
System.out.println("Deleted: " + deleted.getName());
|
||||
System.out.println("Deletion date: " + deleted.getDeletedOn());
|
||||
}
|
||||
|
||||
// Recover deleted secret
|
||||
SyncPoller<KeyVaultSecret, Void> recoverPoller = secretClient.beginRecoverDeletedSecret("old-secret");
|
||||
recoverPoller.waitForCompletion();
|
||||
|
||||
KeyVaultSecret recovered = recoverPoller.getFinalResult();
|
||||
System.out.println("Recovered: " + recovered.getName());
|
||||
```
|
||||
|
||||
## Purge Deleted Secret
|
||||
|
||||
```java
|
||||
// Permanently delete (cannot be recovered)
|
||||
secretClient.purgeDeletedSecret("old-secret");
|
||||
|
||||
// Get deleted secret info first
|
||||
DeletedSecret deleted = secretClient.getDeletedSecret("old-secret");
|
||||
System.out.println("Will purge: " + deleted.getName());
|
||||
secretClient.purgeDeletedSecret("old-secret");
|
||||
```
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
```java
|
||||
// Backup secret (all versions)
|
||||
byte[] backup = secretClient.backupSecret("important-secret");
|
||||
|
||||
// Save to file
|
||||
Files.write(Paths.get("secret-backup.blob"), backup);
|
||||
|
||||
// Restore from backup
|
||||
byte[] backupData = Files.readAllBytes(Paths.get("secret-backup.blob"));
|
||||
KeyVaultSecret restored = secretClient.restoreSecretBackup(backupData);
|
||||
System.out.println("Restored: " + restored.getName());
|
||||
```
|
||||
|
||||
## Async Operations
|
||||
|
||||
```java
|
||||
SecretAsyncClient asyncClient = new SecretClientBuilder()
|
||||
.vaultUrl("https://<vault>.vault.azure.net")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildAsyncClient();
|
||||
|
||||
// Set secret async
|
||||
asyncClient.setSecret("async-secret", "async-value")
|
||||
.subscribe(
|
||||
secret -> System.out.println("Created: " + secret.getName()),
|
||||
error -> System.out.println("Error: " + error.getMessage())
|
||||
);
|
||||
|
||||
// Get secret async
|
||||
asyncClient.getSecret("async-secret")
|
||||
.subscribe(secret -> System.out.println("Value: " + secret.getValue()));
|
||||
|
||||
// List secrets async
|
||||
asyncClient.listPropertiesOfSecrets()
|
||||
.doOnNext(props -> System.out.println("Found: " + props.getName()))
|
||||
.subscribe();
|
||||
```
|
||||
|
||||
## Configuration Patterns
|
||||
|
||||
### Load Multiple Secrets
|
||||
|
||||
```java
|
||||
public class ConfigLoader {
|
||||
private final SecretClient client;
|
||||
|
||||
public ConfigLoader(String vaultUrl) {
|
||||
this.client = new SecretClientBuilder()
|
||||
.vaultUrl(vaultUrl)
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
}
|
||||
|
||||
public Map<String, String> loadSecrets(List<String> secretNames) {
|
||||
Map<String, String> secrets = new HashMap<>();
|
||||
for (String name : secretNames) {
|
||||
try {
|
||||
KeyVaultSecret secret = client.getSecret(name);
|
||||
secrets.put(name, secret.getValue());
|
||||
} catch (ResourceNotFoundException e) {
|
||||
System.out.println("Secret not found: " + name);
|
||||
}
|
||||
}
|
||||
return secrets;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
ConfigLoader loader = new ConfigLoader("https://my-vault.vault.azure.net");
|
||||
Map<String, String> config = loader.loadSecrets(
|
||||
Arrays.asList("db-connection-string", "api-key", "jwt-secret")
|
||||
);
|
||||
```
|
||||
|
||||
### Secret Rotation Pattern
|
||||
|
||||
```java
|
||||
public void rotateSecret(String secretName, String newValue) {
|
||||
// Get current secret
|
||||
KeyVaultSecret current = secretClient.getSecret(secretName);
|
||||
|
||||
// Disable old version
|
||||
current.getProperties().setEnabled(false);
|
||||
secretClient.updateSecretProperties(current.getProperties());
|
||||
|
||||
// Create new version with new value
|
||||
KeyVaultSecret newSecret = secretClient.setSecret(secretName, newValue);
|
||||
System.out.println("Rotated to version: " + newSecret.getProperties().getVersion());
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
import com.azure.core.exception.ResourceNotFoundException;
|
||||
|
||||
try {
|
||||
KeyVaultSecret secret = secretClient.getSecret("my-secret");
|
||||
System.out.println("Value: " + secret.getValue());
|
||||
} catch (ResourceNotFoundException e) {
|
||||
System.out.println("Secret not found");
|
||||
} catch (HttpResponseException e) {
|
||||
int status = e.getResponse().getStatusCode();
|
||||
if (status == 403) {
|
||||
System.out.println("Access denied - check permissions");
|
||||
} else if (status == 429) {
|
||||
System.out.println("Rate limited - retry later");
|
||||
} else {
|
||||
System.out.println("HTTP error: " + status);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Secret Properties
|
||||
|
||||
| Property | Description |
|
||||
|----------|-------------|
|
||||
| `name` | Secret name |
|
||||
| `value` | Secret value (string) |
|
||||
| `id` | Full identifier URL |
|
||||
| `contentType` | MIME type hint |
|
||||
| `enabled` | Whether secret can be retrieved |
|
||||
| `notBefore` | Activation time |
|
||||
| `expiresOn` | Expiration time |
|
||||
| `createdOn` | Creation timestamp |
|
||||
| `updatedOn` | Last update timestamp |
|
||||
| `recoveryLevel` | Soft-delete recovery level |
|
||||
| `tags` | User-defined metadata |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_KEYVAULT_URL=https://<vault-name>.vault.azure.net
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Enable Soft Delete** - Protects against accidental deletion
|
||||
2. **Use Tags** - Tag secrets with environment, service, owner
|
||||
3. **Set Expiration** - Use `setExpiresOn()` for credentials that should rotate
|
||||
4. **Content Type** - Set `contentType` to indicate format (e.g., `application/json`)
|
||||
5. **Version Management** - Don't delete old versions immediately during rotation
|
||||
6. **Access Logging** - Enable diagnostic logging on Key Vault
|
||||
7. **Least Privilege** - Use separate vaults for different environments
|
||||
|
||||
## Common Secret Types
|
||||
|
||||
```java
|
||||
// Database connection string
|
||||
secretClient.setSecret(new KeyVaultSecret("db-connection",
|
||||
"Server=myserver.database.windows.net;Database=mydb;...")
|
||||
.setProperties(new SecretProperties()
|
||||
.setContentType("text/plain")
|
||||
.setTags(Map.of("type", "connection-string"))));
|
||||
|
||||
// API key
|
||||
secretClient.setSecret(new KeyVaultSecret("stripe-api-key", "sk_live_...")
|
||||
.setProperties(new SecretProperties()
|
||||
.setContentType("text/plain")
|
||||
.setExpiresOn(OffsetDateTime.now().plusYears(1))));
|
||||
|
||||
// JSON configuration
|
||||
secretClient.setSecret(new KeyVaultSecret("app-config",
|
||||
"{\"endpoint\":\"https://...\",\"key\":\"...\"}")
|
||||
.setProperties(new SecretProperties()
|
||||
.setContentType("application/json")));
|
||||
|
||||
// Certificate password
|
||||
secretClient.setSecret(new KeyVaultSecret("cert-password", "CertP@ss!")
|
||||
.setProperties(new SecretProperties()
|
||||
.setContentType("text/plain")
|
||||
.setTags(Map.of("certificate", "my-cert"))));
|
||||
```
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "Key Vault secrets Java", "secret management Java"
|
||||
- "store password", "store API key", "connection string"
|
||||
- "retrieve secret", "rotate secret"
|
||||
- "Azure secrets", "vault secrets"
|
||||
256
skills/official/microsoft/java/foundry/anomalydetector/SKILL.md
Normal file
256
skills/official/microsoft/java/foundry/anomalydetector/SKILL.md
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
name: azure-ai-anomalydetector-java
|
||||
description: Build anomaly detection applications with Azure AI Anomaly Detector SDK for Java. Use when implementing univariate/multivariate anomaly detection, time-series analysis, or AI-powered monitoring.
|
||||
package: com.azure:azure-ai-anomalydetector
|
||||
---
|
||||
|
||||
# Azure AI Anomaly Detector SDK for Java
|
||||
|
||||
Build anomaly detection applications using the Azure AI Anomaly Detector SDK for Java.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-ai-anomalydetector</artifactId>
|
||||
<version>3.0.0-beta.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### Sync and Async Clients
|
||||
|
||||
```java
|
||||
import com.azure.ai.anomalydetector.AnomalyDetectorClientBuilder;
|
||||
import com.azure.ai.anomalydetector.MultivariateClient;
|
||||
import com.azure.ai.anomalydetector.UnivariateClient;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
|
||||
String endpoint = System.getenv("AZURE_ANOMALY_DETECTOR_ENDPOINT");
|
||||
String key = System.getenv("AZURE_ANOMALY_DETECTOR_API_KEY");
|
||||
|
||||
// Multivariate client for multiple correlated signals
|
||||
MultivariateClient multivariateClient = new AnomalyDetectorClientBuilder()
|
||||
.credential(new AzureKeyCredential(key))
|
||||
.endpoint(endpoint)
|
||||
.buildMultivariateClient();
|
||||
|
||||
// Univariate client for single variable analysis
|
||||
UnivariateClient univariateClient = new AnomalyDetectorClientBuilder()
|
||||
.credential(new AzureKeyCredential(key))
|
||||
.endpoint(endpoint)
|
||||
.buildUnivariateClient();
|
||||
```
|
||||
|
||||
### With DefaultAzureCredential
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
MultivariateClient client = new AnomalyDetectorClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.endpoint(endpoint)
|
||||
.buildMultivariateClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Univariate Anomaly Detection
|
||||
- **Batch Detection**: Analyze entire time series at once
|
||||
- **Streaming Detection**: Real-time detection on latest data point
|
||||
- **Change Point Detection**: Detect trend changes in time series
|
||||
|
||||
### Multivariate Anomaly Detection
|
||||
- Detect anomalies across 300+ correlated signals
|
||||
- Uses Graph Attention Network for inter-correlations
|
||||
- Three-step process: Train → Inference → Results
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Univariate Batch Detection
|
||||
|
||||
```java
|
||||
import com.azure.ai.anomalydetector.models.*;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
List<TimeSeriesPoint> series = List.of(
|
||||
new TimeSeriesPoint(OffsetDateTime.parse("2023-01-01T00:00:00Z"), 1.0),
|
||||
new TimeSeriesPoint(OffsetDateTime.parse("2023-01-02T00:00:00Z"), 2.5),
|
||||
// ... more data points (minimum 12 points required)
|
||||
);
|
||||
|
||||
UnivariateDetectionOptions options = new UnivariateDetectionOptions(series)
|
||||
.setGranularity(TimeGranularity.DAILY)
|
||||
.setSensitivity(95);
|
||||
|
||||
UnivariateEntireDetectionResult result = univariateClient.detectUnivariateEntireSeries(options);
|
||||
|
||||
// Check for anomalies
|
||||
for (int i = 0; i < result.getIsAnomaly().size(); i++) {
|
||||
if (result.getIsAnomaly().get(i)) {
|
||||
System.out.printf("Anomaly detected at index %d with value %.2f%n",
|
||||
i, series.get(i).getValue());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Univariate Last Point Detection (Streaming)
|
||||
|
||||
```java
|
||||
UnivariateLastDetectionResult lastResult = univariateClient.detectUnivariateLastPoint(options);
|
||||
|
||||
if (lastResult.isAnomaly()) {
|
||||
System.out.println("Latest point is an anomaly!");
|
||||
System.out.printf("Expected: %.2f, Upper: %.2f, Lower: %.2f%n",
|
||||
lastResult.getExpectedValue(),
|
||||
lastResult.getUpperMargin(),
|
||||
lastResult.getLowerMargin());
|
||||
}
|
||||
```
|
||||
|
||||
### Change Point Detection
|
||||
|
||||
```java
|
||||
UnivariateChangePointDetectionOptions changeOptions =
|
||||
new UnivariateChangePointDetectionOptions(series, TimeGranularity.DAILY);
|
||||
|
||||
UnivariateChangePointDetectionResult changeResult =
|
||||
univariateClient.detectUnivariateChangePoint(changeOptions);
|
||||
|
||||
for (int i = 0; i < changeResult.getIsChangePoint().size(); i++) {
|
||||
if (changeResult.getIsChangePoint().get(i)) {
|
||||
System.out.printf("Change point at index %d with confidence %.2f%n",
|
||||
i, changeResult.getConfidenceScores().get(i));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multivariate Model Training
|
||||
|
||||
```java
|
||||
import com.azure.ai.anomalydetector.models.*;
|
||||
import com.azure.core.util.polling.SyncPoller;
|
||||
|
||||
// Prepare training request with blob storage data
|
||||
ModelInfo modelInfo = new ModelInfo()
|
||||
.setDataSource("https://storage.blob.core.windows.net/container/data.zip?sasToken")
|
||||
.setStartTime(OffsetDateTime.parse("2023-01-01T00:00:00Z"))
|
||||
.setEndTime(OffsetDateTime.parse("2023-06-01T00:00:00Z"))
|
||||
.setSlidingWindow(200)
|
||||
.setDisplayName("MyMultivariateModel");
|
||||
|
||||
// Train model (long-running operation)
|
||||
AnomalyDetectionModel trainedModel = multivariateClient.trainMultivariateModel(modelInfo);
|
||||
|
||||
String modelId = trainedModel.getModelId();
|
||||
System.out.println("Model ID: " + modelId);
|
||||
|
||||
// Check training status
|
||||
AnomalyDetectionModel model = multivariateClient.getMultivariateModel(modelId);
|
||||
System.out.println("Status: " + model.getModelInfo().getStatus());
|
||||
```
|
||||
|
||||
### Multivariate Batch Inference
|
||||
|
||||
```java
|
||||
MultivariateBatchDetectionOptions detectionOptions = new MultivariateBatchDetectionOptions()
|
||||
.setDataSource("https://storage.blob.core.windows.net/container/inference-data.zip?sasToken")
|
||||
.setStartTime(OffsetDateTime.parse("2023-07-01T00:00:00Z"))
|
||||
.setEndTime(OffsetDateTime.parse("2023-07-31T00:00:00Z"))
|
||||
.setTopContributorCount(10);
|
||||
|
||||
MultivariateDetectionResult detectionResult =
|
||||
multivariateClient.detectMultivariateBatchAnomaly(modelId, detectionOptions);
|
||||
|
||||
String resultId = detectionResult.getResultId();
|
||||
|
||||
// Poll for results
|
||||
MultivariateDetectionResult result = multivariateClient.getBatchDetectionResult(resultId);
|
||||
for (AnomalyState state : result.getResults()) {
|
||||
if (state.getValue().isAnomaly()) {
|
||||
System.out.printf("Anomaly at %s, severity: %.2f%n",
|
||||
state.getTimestamp(),
|
||||
state.getValue().getSeverity());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multivariate Last Point Detection
|
||||
|
||||
```java
|
||||
MultivariateLastDetectionOptions lastOptions = new MultivariateLastDetectionOptions()
|
||||
.setVariables(List.of(
|
||||
new VariableValues("variable1", List.of("timestamp1"), List.of(1.0f)),
|
||||
new VariableValues("variable2", List.of("timestamp1"), List.of(2.5f))
|
||||
))
|
||||
.setTopContributorCount(5);
|
||||
|
||||
MultivariateLastDetectionResult lastResult =
|
||||
multivariateClient.detectMultivariateLastAnomaly(modelId, lastOptions);
|
||||
|
||||
if (lastResult.getValue().isAnomaly()) {
|
||||
System.out.println("Anomaly detected!");
|
||||
// Check contributing variables
|
||||
for (AnomalyContributor contributor : lastResult.getValue().getInterpretation()) {
|
||||
System.out.printf("Variable: %s, Contribution: %.2f%n",
|
||||
contributor.getVariable(),
|
||||
contributor.getContributionScore());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Model Management
|
||||
|
||||
```java
|
||||
// List all models
|
||||
PagedIterable<AnomalyDetectionModel> models = multivariateClient.listMultivariateModels();
|
||||
for (AnomalyDetectionModel m : models) {
|
||||
System.out.printf("Model: %s, Status: %s%n",
|
||||
m.getModelId(),
|
||||
m.getModelInfo().getStatus());
|
||||
}
|
||||
|
||||
// Delete a model
|
||||
multivariateClient.deleteMultivariateModel(modelId);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
univariateClient.detectUnivariateEntireSeries(options);
|
||||
} catch (HttpResponseException e) {
|
||||
System.out.println("Status code: " + e.getResponse().getStatusCode());
|
||||
System.out.println("Error: " + e.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_ANOMALY_DETECTOR_ENDPOINT=https://<resource>.cognitiveservices.azure.com/
|
||||
AZURE_ANOMALY_DETECTOR_API_KEY=<your-api-key>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Minimum Data Points**: Univariate requires at least 12 points; more data improves accuracy
|
||||
2. **Granularity Alignment**: Match `TimeGranularity` to your actual data frequency
|
||||
3. **Sensitivity Tuning**: Higher values (0-99) detect more anomalies
|
||||
4. **Multivariate Training**: Use 200-1000 sliding window based on pattern complexity
|
||||
5. **Error Handling**: Always handle `HttpResponseException` for API errors
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "anomaly detection Java"
|
||||
- "detect anomalies time series"
|
||||
- "multivariate anomaly Java"
|
||||
- "univariate anomaly detection"
|
||||
- "streaming anomaly detection"
|
||||
- "change point detection"
|
||||
- "Azure AI Anomaly Detector"
|
||||
282
skills/official/microsoft/java/foundry/contentsafety/SKILL.md
Normal file
282
skills/official/microsoft/java/foundry/contentsafety/SKILL.md
Normal file
@@ -0,0 +1,282 @@
|
||||
---
|
||||
name: azure-ai-contentsafety-java
|
||||
description: Build content moderation applications with Azure AI Content Safety SDK for Java. Use when implementing text/image analysis, blocklist management, or harm detection for hate, violence, sexual content, and self-harm.
|
||||
package: com.azure:azure-ai-contentsafety
|
||||
---
|
||||
|
||||
# Azure AI Content Safety SDK for Java
|
||||
|
||||
Build content moderation applications using the Azure AI Content Safety SDK for Java.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-ai-contentsafety</artifactId>
|
||||
<version>1.1.0-beta.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### With API Key
|
||||
|
||||
```java
|
||||
import com.azure.ai.contentsafety.ContentSafetyClient;
|
||||
import com.azure.ai.contentsafety.ContentSafetyClientBuilder;
|
||||
import com.azure.ai.contentsafety.BlocklistClient;
|
||||
import com.azure.ai.contentsafety.BlocklistClientBuilder;
|
||||
import com.azure.core.credential.KeyCredential;
|
||||
|
||||
String endpoint = System.getenv("CONTENT_SAFETY_ENDPOINT");
|
||||
String key = System.getenv("CONTENT_SAFETY_KEY");
|
||||
|
||||
ContentSafetyClient contentSafetyClient = new ContentSafetyClientBuilder()
|
||||
.credential(new KeyCredential(key))
|
||||
.endpoint(endpoint)
|
||||
.buildClient();
|
||||
|
||||
BlocklistClient blocklistClient = new BlocklistClientBuilder()
|
||||
.credential(new KeyCredential(key))
|
||||
.endpoint(endpoint)
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### With DefaultAzureCredential
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
ContentSafetyClient client = new ContentSafetyClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.endpoint(endpoint)
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Harm Categories
|
||||
| Category | Description |
|
||||
|----------|-------------|
|
||||
| Hate | Discriminatory language based on identity groups |
|
||||
| Sexual | Sexual content, relationships, acts |
|
||||
| Violence | Physical harm, weapons, injury |
|
||||
| Self-harm | Self-injury, suicide-related content |
|
||||
|
||||
### Severity Levels
|
||||
- Text: 0-7 scale (default outputs 0, 2, 4, 6)
|
||||
- Image: 0, 2, 4, 6 (trimmed scale)
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Analyze Text
|
||||
|
||||
```java
|
||||
import com.azure.ai.contentsafety.models.*;
|
||||
|
||||
AnalyzeTextResult result = contentSafetyClient.analyzeText(
|
||||
new AnalyzeTextOptions("This is text to analyze"));
|
||||
|
||||
for (TextCategoriesAnalysis category : result.getCategoriesAnalysis()) {
|
||||
System.out.printf("Category: %s, Severity: %d%n",
|
||||
category.getCategory(),
|
||||
category.getSeverity());
|
||||
}
|
||||
```
|
||||
|
||||
### Analyze Text with Options
|
||||
|
||||
```java
|
||||
AnalyzeTextOptions options = new AnalyzeTextOptions("Text to analyze")
|
||||
.setCategories(Arrays.asList(
|
||||
TextCategory.HATE,
|
||||
TextCategory.VIOLENCE))
|
||||
.setOutputType(AnalyzeTextOutputType.EIGHT_SEVERITY_LEVELS);
|
||||
|
||||
AnalyzeTextResult result = contentSafetyClient.analyzeText(options);
|
||||
```
|
||||
|
||||
### Analyze Text with Blocklist
|
||||
|
||||
```java
|
||||
AnalyzeTextOptions options = new AnalyzeTextOptions("I h*te you and want to k*ll you")
|
||||
.setBlocklistNames(Arrays.asList("my-blocklist"))
|
||||
.setHaltOnBlocklistHit(true);
|
||||
|
||||
AnalyzeTextResult result = contentSafetyClient.analyzeText(options);
|
||||
|
||||
if (result.getBlocklistsMatch() != null) {
|
||||
for (TextBlocklistMatch match : result.getBlocklistsMatch()) {
|
||||
System.out.printf("Blocklist: %s, Item: %s, Text: %s%n",
|
||||
match.getBlocklistName(),
|
||||
match.getBlocklistItemId(),
|
||||
match.getBlocklistItemText());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Analyze Image
|
||||
|
||||
```java
|
||||
import com.azure.ai.contentsafety.models.*;
|
||||
import com.azure.core.util.BinaryData;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
// From file
|
||||
byte[] imageBytes = Files.readAllBytes(Paths.get("image.png"));
|
||||
ContentSafetyImageData imageData = new ContentSafetyImageData()
|
||||
.setContent(BinaryData.fromBytes(imageBytes));
|
||||
|
||||
AnalyzeImageResult result = contentSafetyClient.analyzeImage(
|
||||
new AnalyzeImageOptions(imageData));
|
||||
|
||||
for (ImageCategoriesAnalysis category : result.getCategoriesAnalysis()) {
|
||||
System.out.printf("Category: %s, Severity: %d%n",
|
||||
category.getCategory(),
|
||||
category.getSeverity());
|
||||
}
|
||||
```
|
||||
|
||||
### Analyze Image from URL
|
||||
|
||||
```java
|
||||
ContentSafetyImageData imageData = new ContentSafetyImageData()
|
||||
.setBlobUrl("https://example.com/image.jpg");
|
||||
|
||||
AnalyzeImageResult result = contentSafetyClient.analyzeImage(
|
||||
new AnalyzeImageOptions(imageData));
|
||||
```
|
||||
|
||||
## Blocklist Management
|
||||
|
||||
### Create or Update Blocklist
|
||||
|
||||
```java
|
||||
import com.azure.core.http.rest.RequestOptions;
|
||||
import com.azure.core.http.rest.Response;
|
||||
import com.azure.core.util.BinaryData;
|
||||
import java.util.Map;
|
||||
|
||||
Map<String, String> description = Map.of("description", "Custom blocklist");
|
||||
BinaryData resource = BinaryData.fromObject(description);
|
||||
|
||||
Response<BinaryData> response = blocklistClient.createOrUpdateTextBlocklistWithResponse(
|
||||
"my-blocklist", resource, new RequestOptions());
|
||||
|
||||
if (response.getStatusCode() == 201) {
|
||||
System.out.println("Blocklist created");
|
||||
} else if (response.getStatusCode() == 200) {
|
||||
System.out.println("Blocklist updated");
|
||||
}
|
||||
```
|
||||
|
||||
### Add Block Items
|
||||
|
||||
```java
|
||||
import com.azure.ai.contentsafety.models.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
List<TextBlocklistItem> items = Arrays.asList(
|
||||
new TextBlocklistItem("badword1").setDescription("Offensive term"),
|
||||
new TextBlocklistItem("badword2").setDescription("Another term")
|
||||
);
|
||||
|
||||
AddOrUpdateTextBlocklistItemsResult result = blocklistClient.addOrUpdateBlocklistItems(
|
||||
"my-blocklist",
|
||||
new AddOrUpdateTextBlocklistItemsOptions(items));
|
||||
|
||||
for (TextBlocklistItem item : result.getBlocklistItems()) {
|
||||
System.out.printf("Added: %s (ID: %s)%n",
|
||||
item.getText(),
|
||||
item.getBlocklistItemId());
|
||||
}
|
||||
```
|
||||
|
||||
### List Blocklists
|
||||
|
||||
```java
|
||||
PagedIterable<TextBlocklist> blocklists = blocklistClient.listTextBlocklists();
|
||||
|
||||
for (TextBlocklist blocklist : blocklists) {
|
||||
System.out.printf("Blocklist: %s, Description: %s%n",
|
||||
blocklist.getName(),
|
||||
blocklist.getDescription());
|
||||
}
|
||||
```
|
||||
|
||||
### Get Blocklist
|
||||
|
||||
```java
|
||||
TextBlocklist blocklist = blocklistClient.getTextBlocklist("my-blocklist");
|
||||
System.out.println("Name: " + blocklist.getName());
|
||||
```
|
||||
|
||||
### List Block Items
|
||||
|
||||
```java
|
||||
PagedIterable<TextBlocklistItem> items =
|
||||
blocklistClient.listTextBlocklistItems("my-blocklist");
|
||||
|
||||
for (TextBlocklistItem item : items) {
|
||||
System.out.printf("ID: %s, Text: %s%n",
|
||||
item.getBlocklistItemId(),
|
||||
item.getText());
|
||||
}
|
||||
```
|
||||
|
||||
### Remove Block Items
|
||||
|
||||
```java
|
||||
List<String> itemIds = Arrays.asList("item-id-1", "item-id-2");
|
||||
|
||||
blocklistClient.removeBlocklistItems(
|
||||
"my-blocklist",
|
||||
new RemoveTextBlocklistItemsOptions(itemIds));
|
||||
```
|
||||
|
||||
### Delete Blocklist
|
||||
|
||||
```java
|
||||
blocklistClient.deleteTextBlocklist("my-blocklist");
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
contentSafetyClient.analyzeText(new AnalyzeTextOptions("test"));
|
||||
} catch (HttpResponseException e) {
|
||||
System.out.println("Status: " + e.getResponse().getStatusCode());
|
||||
System.out.println("Error: " + e.getMessage());
|
||||
// Common codes: InvalidRequestBody, ResourceNotFound, TooManyRequests
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
CONTENT_SAFETY_ENDPOINT=https://<resource>.cognitiveservices.azure.com/
|
||||
CONTENT_SAFETY_KEY=<your-api-key>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Blocklist Delay**: Changes take ~5 minutes to take effect
|
||||
2. **Category Selection**: Only request needed categories to reduce latency
|
||||
3. **Severity Thresholds**: Typically block severity >= 4 for strict moderation
|
||||
4. **Batch Processing**: Process multiple items in parallel for throughput
|
||||
5. **Caching**: Cache blocklist results where appropriate
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "content safety Java"
|
||||
- "content moderation Azure"
|
||||
- "analyze text safety"
|
||||
- "image moderation Java"
|
||||
- "blocklist management"
|
||||
- "hate speech detection"
|
||||
- "harmful content filter"
|
||||
341
skills/official/microsoft/java/foundry/formrecognizer/SKILL.md
Normal file
341
skills/official/microsoft/java/foundry/formrecognizer/SKILL.md
Normal file
@@ -0,0 +1,341 @@
|
||||
---
|
||||
name: azure-ai-formrecognizer-java
|
||||
description: Build document analysis applications with Azure Document Intelligence (Form Recognizer) SDK for Java. Use when extracting text, tables, key-value pairs from documents, receipts, invoices, or building custom document models.
|
||||
package: com.azure:azure-ai-formrecognizer
|
||||
---
|
||||
|
||||
# Azure Document Intelligence (Form Recognizer) SDK for Java
|
||||
|
||||
Build document analysis applications using the Azure AI Document Intelligence SDK for Java.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-ai-formrecognizer</artifactId>
|
||||
<version>4.2.0-beta.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### DocumentAnalysisClient
|
||||
|
||||
```java
|
||||
import com.azure.ai.formrecognizer.documentanalysis.DocumentAnalysisClient;
|
||||
import com.azure.ai.formrecognizer.documentanalysis.DocumentAnalysisClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
|
||||
DocumentAnalysisClient client = new DocumentAnalysisClientBuilder()
|
||||
.credential(new AzureKeyCredential("{key}"))
|
||||
.endpoint("{endpoint}")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### DocumentModelAdministrationClient
|
||||
|
||||
```java
|
||||
import com.azure.ai.formrecognizer.documentanalysis.administration.DocumentModelAdministrationClient;
|
||||
import com.azure.ai.formrecognizer.documentanalysis.administration.DocumentModelAdministrationClientBuilder;
|
||||
|
||||
DocumentModelAdministrationClient adminClient = new DocumentModelAdministrationClientBuilder()
|
||||
.credential(new AzureKeyCredential("{key}"))
|
||||
.endpoint("{endpoint}")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### With DefaultAzureCredential
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
DocumentAnalysisClient client = new DocumentAnalysisClientBuilder()
|
||||
.endpoint("{endpoint}")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Prebuilt Models
|
||||
|
||||
| Model ID | Purpose |
|
||||
|----------|---------|
|
||||
| `prebuilt-layout` | Extract text, tables, selection marks |
|
||||
| `prebuilt-document` | General document with key-value pairs |
|
||||
| `prebuilt-receipt` | Receipt data extraction |
|
||||
| `prebuilt-invoice` | Invoice field extraction |
|
||||
| `prebuilt-businessCard` | Business card parsing |
|
||||
| `prebuilt-idDocument` | ID document (passport, license) |
|
||||
| `prebuilt-tax.us.w2` | US W2 tax forms |
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Extract Layout
|
||||
|
||||
```java
|
||||
import com.azure.ai.formrecognizer.documentanalysis.models.*;
|
||||
import com.azure.core.util.BinaryData;
|
||||
import com.azure.core.util.polling.SyncPoller;
|
||||
import java.io.File;
|
||||
|
||||
File document = new File("document.pdf");
|
||||
BinaryData documentData = BinaryData.fromFile(document.toPath());
|
||||
|
||||
SyncPoller<OperationResult, AnalyzeResult> poller =
|
||||
client.beginAnalyzeDocument("prebuilt-layout", documentData);
|
||||
|
||||
AnalyzeResult result = poller.getFinalResult();
|
||||
|
||||
// Process pages
|
||||
for (DocumentPage page : result.getPages()) {
|
||||
System.out.printf("Page %d: %.2f x %.2f %s%n",
|
||||
page.getPageNumber(),
|
||||
page.getWidth(),
|
||||
page.getHeight(),
|
||||
page.getUnit());
|
||||
|
||||
// Lines
|
||||
for (DocumentLine line : page.getLines()) {
|
||||
System.out.println("Line: " + line.getContent());
|
||||
}
|
||||
|
||||
// Selection marks (checkboxes)
|
||||
for (DocumentSelectionMark mark : page.getSelectionMarks()) {
|
||||
System.out.printf("Checkbox: %s (confidence: %.2f)%n",
|
||||
mark.getSelectionMarkState(),
|
||||
mark.getConfidence());
|
||||
}
|
||||
}
|
||||
|
||||
// Tables
|
||||
for (DocumentTable table : result.getTables()) {
|
||||
System.out.printf("Table: %d rows x %d columns%n",
|
||||
table.getRowCount(),
|
||||
table.getColumnCount());
|
||||
|
||||
for (DocumentTableCell cell : table.getCells()) {
|
||||
System.out.printf("Cell[%d,%d]: %s%n",
|
||||
cell.getRowIndex(),
|
||||
cell.getColumnIndex(),
|
||||
cell.getContent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Analyze from URL
|
||||
|
||||
```java
|
||||
String documentUrl = "https://example.com/invoice.pdf";
|
||||
|
||||
SyncPoller<OperationResult, AnalyzeResult> poller =
|
||||
client.beginAnalyzeDocumentFromUrl("prebuilt-invoice", documentUrl);
|
||||
|
||||
AnalyzeResult result = poller.getFinalResult();
|
||||
```
|
||||
|
||||
### Analyze Receipt
|
||||
|
||||
```java
|
||||
SyncPoller<OperationResult, AnalyzeResult> poller =
|
||||
client.beginAnalyzeDocumentFromUrl("prebuilt-receipt", receiptUrl);
|
||||
|
||||
AnalyzeResult result = poller.getFinalResult();
|
||||
|
||||
for (AnalyzedDocument doc : result.getDocuments()) {
|
||||
Map<String, DocumentField> fields = doc.getFields();
|
||||
|
||||
DocumentField merchantName = fields.get("MerchantName");
|
||||
if (merchantName != null && merchantName.getType() == DocumentFieldType.STRING) {
|
||||
System.out.printf("Merchant: %s (confidence: %.2f)%n",
|
||||
merchantName.getValueAsString(),
|
||||
merchantName.getConfidence());
|
||||
}
|
||||
|
||||
DocumentField transactionDate = fields.get("TransactionDate");
|
||||
if (transactionDate != null && transactionDate.getType() == DocumentFieldType.DATE) {
|
||||
System.out.printf("Date: %s%n", transactionDate.getValueAsDate());
|
||||
}
|
||||
|
||||
DocumentField items = fields.get("Items");
|
||||
if (items != null && items.getType() == DocumentFieldType.LIST) {
|
||||
for (DocumentField item : items.getValueAsList()) {
|
||||
Map<String, DocumentField> itemFields = item.getValueAsMap();
|
||||
System.out.printf("Item: %s, Price: %.2f%n",
|
||||
itemFields.get("Name").getValueAsString(),
|
||||
itemFields.get("Price").getValueAsDouble());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### General Document Analysis
|
||||
|
||||
```java
|
||||
SyncPoller<OperationResult, AnalyzeResult> poller =
|
||||
client.beginAnalyzeDocumentFromUrl("prebuilt-document", documentUrl);
|
||||
|
||||
AnalyzeResult result = poller.getFinalResult();
|
||||
|
||||
// Key-value pairs
|
||||
for (DocumentKeyValuePair kvp : result.getKeyValuePairs()) {
|
||||
System.out.printf("Key: %s => Value: %s%n",
|
||||
kvp.getKey().getContent(),
|
||||
kvp.getValue() != null ? kvp.getValue().getContent() : "null");
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Models
|
||||
|
||||
### Build Custom Model
|
||||
|
||||
```java
|
||||
import com.azure.ai.formrecognizer.documentanalysis.administration.models.*;
|
||||
|
||||
String blobContainerUrl = "{SAS_URL_of_training_data}";
|
||||
String prefix = "training-docs/";
|
||||
|
||||
SyncPoller<OperationResult, DocumentModelDetails> poller = adminClient.beginBuildDocumentModel(
|
||||
blobContainerUrl,
|
||||
DocumentModelBuildMode.TEMPLATE,
|
||||
prefix,
|
||||
new BuildDocumentModelOptions()
|
||||
.setModelId("my-custom-model")
|
||||
.setDescription("Custom invoice model"),
|
||||
Context.NONE);
|
||||
|
||||
DocumentModelDetails model = poller.getFinalResult();
|
||||
|
||||
System.out.println("Model ID: " + model.getModelId());
|
||||
System.out.println("Created: " + model.getCreatedOn());
|
||||
|
||||
model.getDocumentTypes().forEach((docType, details) -> {
|
||||
System.out.println("Document type: " + docType);
|
||||
details.getFieldSchema().forEach((field, schema) -> {
|
||||
System.out.printf(" Field: %s (%s)%n", field, schema.getType());
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Analyze with Custom Model
|
||||
|
||||
```java
|
||||
SyncPoller<OperationResult, AnalyzeResult> poller =
|
||||
client.beginAnalyzeDocumentFromUrl("my-custom-model", documentUrl);
|
||||
|
||||
AnalyzeResult result = poller.getFinalResult();
|
||||
|
||||
for (AnalyzedDocument doc : result.getDocuments()) {
|
||||
System.out.printf("Document type: %s (confidence: %.2f)%n",
|
||||
doc.getDocType(),
|
||||
doc.getConfidence());
|
||||
|
||||
doc.getFields().forEach((name, field) -> {
|
||||
System.out.printf("Field '%s': %s (confidence: %.2f)%n",
|
||||
name,
|
||||
field.getContent(),
|
||||
field.getConfidence());
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Compose Models
|
||||
|
||||
```java
|
||||
List<String> modelIds = Arrays.asList("model-1", "model-2", "model-3");
|
||||
|
||||
SyncPoller<OperationResult, DocumentModelDetails> poller =
|
||||
adminClient.beginComposeDocumentModel(
|
||||
modelIds,
|
||||
new ComposeDocumentModelOptions()
|
||||
.setModelId("composed-model")
|
||||
.setDescription("Composed from multiple models"));
|
||||
|
||||
DocumentModelDetails composedModel = poller.getFinalResult();
|
||||
```
|
||||
|
||||
### Manage Models
|
||||
|
||||
```java
|
||||
// List models
|
||||
PagedIterable<DocumentModelSummary> models = adminClient.listDocumentModels();
|
||||
for (DocumentModelSummary summary : models) {
|
||||
System.out.printf("Model: %s, Created: %s%n",
|
||||
summary.getModelId(),
|
||||
summary.getCreatedOn());
|
||||
}
|
||||
|
||||
// Get model details
|
||||
DocumentModelDetails model = adminClient.getDocumentModel("model-id");
|
||||
|
||||
// Delete model
|
||||
adminClient.deleteDocumentModel("model-id");
|
||||
|
||||
// Check resource limits
|
||||
ResourceDetails resources = adminClient.getResourceDetails();
|
||||
System.out.printf("Models: %d / %d%n",
|
||||
resources.getCustomDocumentModelCount(),
|
||||
resources.getCustomDocumentModelLimit());
|
||||
```
|
||||
|
||||
## Document Classification
|
||||
|
||||
### Build Classifier
|
||||
|
||||
```java
|
||||
Map<String, ClassifierDocumentTypeDetails> docTypes = new HashMap<>();
|
||||
docTypes.put("invoice", new ClassifierDocumentTypeDetails()
|
||||
.setAzureBlobSource(new AzureBlobContentSource(containerUrl).setPrefix("invoices/")));
|
||||
docTypes.put("receipt", new ClassifierDocumentTypeDetails()
|
||||
.setAzureBlobSource(new AzureBlobContentSource(containerUrl).setPrefix("receipts/")));
|
||||
|
||||
SyncPoller<OperationResult, DocumentClassifierDetails> poller =
|
||||
adminClient.beginBuildDocumentClassifier(docTypes,
|
||||
new BuildDocumentClassifierOptions().setClassifierId("my-classifier"));
|
||||
|
||||
DocumentClassifierDetails classifier = poller.getFinalResult();
|
||||
```
|
||||
|
||||
### Classify Document
|
||||
|
||||
```java
|
||||
SyncPoller<OperationResult, AnalyzeResult> poller =
|
||||
client.beginClassifyDocumentFromUrl("my-classifier", documentUrl, Context.NONE);
|
||||
|
||||
AnalyzeResult result = poller.getFinalResult();
|
||||
|
||||
for (AnalyzedDocument doc : result.getDocuments()) {
|
||||
System.out.printf("Classified as: %s (confidence: %.2f)%n",
|
||||
doc.getDocType(),
|
||||
doc.getConfidence());
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
client.beginAnalyzeDocumentFromUrl("prebuilt-receipt", "invalid-url");
|
||||
} catch (HttpResponseException e) {
|
||||
System.out.println("Status: " + e.getResponse().getStatusCode());
|
||||
System.out.println("Error: " + e.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
FORM_RECOGNIZER_ENDPOINT=https://<resource>.cognitiveservices.azure.com/
|
||||
FORM_RECOGNIZER_KEY=<your-api-key>
|
||||
```
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "document intelligence Java"
|
||||
- "form recognizer SDK"
|
||||
- "extract text from PDF"
|
||||
- "OCR document Java"
|
||||
- "analyze invoice receipt"
|
||||
- "custom document model"
|
||||
- "document classification"
|
||||
152
skills/official/microsoft/java/foundry/projects/SKILL.md
Normal file
152
skills/official/microsoft/java/foundry/projects/SKILL.md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
name: azure-ai-projects-java
|
||||
description: |
|
||||
Azure AI Projects SDK for Java. High-level SDK for Azure AI Foundry project management including connections, datasets, indexes, and evaluations.
|
||||
Triggers: "AIProjectClient java", "azure ai projects java", "Foundry project java", "ConnectionsClient", "DatasetsClient", "IndexesClient".
|
||||
package: com.azure:azure-ai-projects
|
||||
---
|
||||
|
||||
# Azure AI Projects SDK for Java
|
||||
|
||||
High-level SDK for Azure AI Foundry project management with access to connections, datasets, indexes, and evaluations.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-ai-projects</artifactId>
|
||||
<version>1.0.0-beta.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
PROJECT_ENDPOINT=https://<resource>.services.ai.azure.com/api/projects/<project>
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
```java
|
||||
import com.azure.ai.projects.AIProjectClientBuilder;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
AIProjectClientBuilder builder = new AIProjectClientBuilder()
|
||||
.endpoint(System.getenv("PROJECT_ENDPOINT"))
|
||||
.credential(new DefaultAzureCredentialBuilder().build());
|
||||
```
|
||||
|
||||
## Client Hierarchy
|
||||
|
||||
The SDK provides multiple sub-clients for different operations:
|
||||
|
||||
| Client | Purpose |
|
||||
|--------|---------|
|
||||
| `ConnectionsClient` | Enumerate connected Azure resources |
|
||||
| `DatasetsClient` | Upload documents and manage datasets |
|
||||
| `DeploymentsClient` | Enumerate AI model deployments |
|
||||
| `IndexesClient` | Create and manage search indexes |
|
||||
| `EvaluationsClient` | Run AI model evaluations |
|
||||
| `EvaluatorsClient` | Manage evaluator configurations |
|
||||
| `SchedulesClient` | Manage scheduled operations |
|
||||
|
||||
```java
|
||||
// Build sub-clients from builder
|
||||
ConnectionsClient connectionsClient = builder.buildConnectionsClient();
|
||||
DatasetsClient datasetsClient = builder.buildDatasetsClient();
|
||||
DeploymentsClient deploymentsClient = builder.buildDeploymentsClient();
|
||||
IndexesClient indexesClient = builder.buildIndexesClient();
|
||||
EvaluationsClient evaluationsClient = builder.buildEvaluationsClient();
|
||||
```
|
||||
|
||||
## Core Operations
|
||||
|
||||
### List Connections
|
||||
|
||||
```java
|
||||
import com.azure.ai.projects.models.Connection;
|
||||
import com.azure.core.http.rest.PagedIterable;
|
||||
|
||||
PagedIterable<Connection> connections = connectionsClient.listConnections();
|
||||
for (Connection connection : connections) {
|
||||
System.out.println("Name: " + connection.getName());
|
||||
System.out.println("Type: " + connection.getType());
|
||||
System.out.println("Credential Type: " + connection.getCredentials().getType());
|
||||
}
|
||||
```
|
||||
|
||||
### List Indexes
|
||||
|
||||
```java
|
||||
indexesClient.listLatest().forEach(index -> {
|
||||
System.out.println("Index name: " + index.getName());
|
||||
System.out.println("Version: " + index.getVersion());
|
||||
System.out.println("Description: " + index.getDescription());
|
||||
});
|
||||
```
|
||||
|
||||
### Create or Update Index
|
||||
|
||||
```java
|
||||
import com.azure.ai.projects.models.AzureAISearchIndex;
|
||||
import com.azure.ai.projects.models.Index;
|
||||
|
||||
String indexName = "my-index";
|
||||
String indexVersion = "1.0";
|
||||
String searchConnectionName = System.getenv("AI_SEARCH_CONNECTION_NAME");
|
||||
String searchIndexName = System.getenv("AI_SEARCH_INDEX_NAME");
|
||||
|
||||
Index index = indexesClient.createOrUpdate(
|
||||
indexName,
|
||||
indexVersion,
|
||||
new AzureAISearchIndex()
|
||||
.setConnectionName(searchConnectionName)
|
||||
.setIndexName(searchIndexName)
|
||||
);
|
||||
|
||||
System.out.println("Created index: " + index.getName());
|
||||
```
|
||||
|
||||
### Access OpenAI Evaluations
|
||||
|
||||
The SDK exposes OpenAI's official SDK for evaluations:
|
||||
|
||||
```java
|
||||
import com.openai.services.EvalService;
|
||||
|
||||
EvalService evalService = evaluationsClient.getOpenAIClient();
|
||||
// Use OpenAI evaluation APIs directly
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use DefaultAzureCredential** for production authentication
|
||||
2. **Reuse client builder** to create multiple sub-clients efficiently
|
||||
3. **Handle pagination** when listing resources with `PagedIterable`
|
||||
4. **Use environment variables** for connection names and configuration
|
||||
5. **Check connection types** before accessing credentials
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
import com.azure.core.exception.ResourceNotFoundException;
|
||||
|
||||
try {
|
||||
Index index = indexesClient.get(indexName, version);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
System.err.println("Index not found: " + indexName);
|
||||
} catch (HttpResponseException e) {
|
||||
System.err.println("Error: " + e.getResponse().getStatusCode());
|
||||
}
|
||||
```
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| Product Docs | https://learn.microsoft.com/azure/ai-studio/ |
|
||||
| API Reference | https://learn.microsoft.com/rest/api/aifoundry/aiprojects/ |
|
||||
| GitHub Source | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-projects |
|
||||
| Samples | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-projects/src/samples |
|
||||
@@ -0,0 +1,289 @@
|
||||
---
|
||||
name: azure-ai-vision-imageanalysis-java
|
||||
description: Build image analysis applications with Azure AI Vision SDK for Java. Use when implementing image captioning, OCR text extraction, object detection, tagging, or smart cropping.
|
||||
package: com.azure:azure-ai-vision-imageanalysis
|
||||
---
|
||||
|
||||
# Azure AI Vision Image Analysis SDK for Java
|
||||
|
||||
Build image analysis applications using the Azure AI Vision Image Analysis SDK for Java.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-ai-vision-imageanalysis</artifactId>
|
||||
<version>1.1.0-beta.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### With API Key
|
||||
|
||||
```java
|
||||
import com.azure.ai.vision.imageanalysis.ImageAnalysisClient;
|
||||
import com.azure.ai.vision.imageanalysis.ImageAnalysisClientBuilder;
|
||||
import com.azure.core.credential.KeyCredential;
|
||||
|
||||
String endpoint = System.getenv("VISION_ENDPOINT");
|
||||
String key = System.getenv("VISION_KEY");
|
||||
|
||||
ImageAnalysisClient client = new ImageAnalysisClientBuilder()
|
||||
.endpoint(endpoint)
|
||||
.credential(new KeyCredential(key))
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### Async Client
|
||||
|
||||
```java
|
||||
import com.azure.ai.vision.imageanalysis.ImageAnalysisAsyncClient;
|
||||
|
||||
ImageAnalysisAsyncClient asyncClient = new ImageAnalysisClientBuilder()
|
||||
.endpoint(endpoint)
|
||||
.credential(new KeyCredential(key))
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
### With DefaultAzureCredential
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
ImageAnalysisClient client = new ImageAnalysisClientBuilder()
|
||||
.endpoint(endpoint)
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Visual Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| `CAPTION` | Generate human-readable image description |
|
||||
| `DENSE_CAPTIONS` | Captions for up to 10 regions |
|
||||
| `READ` | OCR - Extract text from images |
|
||||
| `TAGS` | Content tags for objects, scenes, actions |
|
||||
| `OBJECTS` | Detect objects with bounding boxes |
|
||||
| `SMART_CROPS` | Smart thumbnail regions |
|
||||
| `PEOPLE` | Detect people with locations |
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Generate Caption
|
||||
|
||||
```java
|
||||
import com.azure.ai.vision.imageanalysis.models.*;
|
||||
import com.azure.core.util.BinaryData;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
// From file
|
||||
BinaryData imageData = BinaryData.fromFile(new File("image.jpg").toPath());
|
||||
|
||||
ImageAnalysisResult result = client.analyze(
|
||||
imageData,
|
||||
Arrays.asList(VisualFeatures.CAPTION),
|
||||
new ImageAnalysisOptions().setGenderNeutralCaption(true));
|
||||
|
||||
System.out.printf("Caption: \"%s\" (confidence: %.4f)%n",
|
||||
result.getCaption().getText(),
|
||||
result.getCaption().getConfidence());
|
||||
```
|
||||
|
||||
### Generate Caption from URL
|
||||
|
||||
```java
|
||||
ImageAnalysisResult result = client.analyzeFromUrl(
|
||||
"https://example.com/image.jpg",
|
||||
Arrays.asList(VisualFeatures.CAPTION),
|
||||
new ImageAnalysisOptions().setGenderNeutralCaption(true));
|
||||
|
||||
System.out.printf("Caption: \"%s\"%n", result.getCaption().getText());
|
||||
```
|
||||
|
||||
### Extract Text (OCR)
|
||||
|
||||
```java
|
||||
ImageAnalysisResult result = client.analyze(
|
||||
BinaryData.fromFile(new File("document.jpg").toPath()),
|
||||
Arrays.asList(VisualFeatures.READ),
|
||||
null);
|
||||
|
||||
for (DetectedTextBlock block : result.getRead().getBlocks()) {
|
||||
for (DetectedTextLine line : block.getLines()) {
|
||||
System.out.printf("Line: '%s'%n", line.getText());
|
||||
System.out.printf(" Bounding polygon: %s%n", line.getBoundingPolygon());
|
||||
|
||||
for (DetectedTextWord word : line.getWords()) {
|
||||
System.out.printf(" Word: '%s' (confidence: %.4f)%n",
|
||||
word.getText(),
|
||||
word.getConfidence());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Detect Objects
|
||||
|
||||
```java
|
||||
ImageAnalysisResult result = client.analyzeFromUrl(
|
||||
imageUrl,
|
||||
Arrays.asList(VisualFeatures.OBJECTS),
|
||||
null);
|
||||
|
||||
for (DetectedObject obj : result.getObjects()) {
|
||||
System.out.printf("Object: %s (confidence: %.4f)%n",
|
||||
obj.getTags().get(0).getName(),
|
||||
obj.getTags().get(0).getConfidence());
|
||||
|
||||
ImageBoundingBox box = obj.getBoundingBox();
|
||||
System.out.printf(" Location: x=%d, y=%d, w=%d, h=%d%n",
|
||||
box.getX(), box.getY(), box.getWidth(), box.getHeight());
|
||||
}
|
||||
```
|
||||
|
||||
### Get Tags
|
||||
|
||||
```java
|
||||
ImageAnalysisResult result = client.analyzeFromUrl(
|
||||
imageUrl,
|
||||
Arrays.asList(VisualFeatures.TAGS),
|
||||
null);
|
||||
|
||||
for (DetectedTag tag : result.getTags()) {
|
||||
System.out.printf("Tag: %s (confidence: %.4f)%n",
|
||||
tag.getName(),
|
||||
tag.getConfidence());
|
||||
}
|
||||
```
|
||||
|
||||
### Detect People
|
||||
|
||||
```java
|
||||
ImageAnalysisResult result = client.analyzeFromUrl(
|
||||
imageUrl,
|
||||
Arrays.asList(VisualFeatures.PEOPLE),
|
||||
null);
|
||||
|
||||
for (DetectedPerson person : result.getPeople()) {
|
||||
ImageBoundingBox box = person.getBoundingBox();
|
||||
System.out.printf("Person at x=%d, y=%d (confidence: %.4f)%n",
|
||||
box.getX(), box.getY(), person.getConfidence());
|
||||
}
|
||||
```
|
||||
|
||||
### Smart Cropping
|
||||
|
||||
```java
|
||||
ImageAnalysisResult result = client.analyzeFromUrl(
|
||||
imageUrl,
|
||||
Arrays.asList(VisualFeatures.SMART_CROPS),
|
||||
new ImageAnalysisOptions().setSmartCropsAspectRatios(Arrays.asList(1.0, 1.5)));
|
||||
|
||||
for (CropRegion crop : result.getSmartCrops()) {
|
||||
System.out.printf("Crop region: aspect=%.2f, x=%d, y=%d, w=%d, h=%d%n",
|
||||
crop.getAspectRatio(),
|
||||
crop.getBoundingBox().getX(),
|
||||
crop.getBoundingBox().getY(),
|
||||
crop.getBoundingBox().getWidth(),
|
||||
crop.getBoundingBox().getHeight());
|
||||
}
|
||||
```
|
||||
|
||||
### Dense Captions
|
||||
|
||||
```java
|
||||
ImageAnalysisResult result = client.analyzeFromUrl(
|
||||
imageUrl,
|
||||
Arrays.asList(VisualFeatures.DENSE_CAPTIONS),
|
||||
new ImageAnalysisOptions().setGenderNeutralCaption(true));
|
||||
|
||||
for (DenseCaption caption : result.getDenseCaptions()) {
|
||||
System.out.printf("Caption: \"%s\" (confidence: %.4f)%n",
|
||||
caption.getText(),
|
||||
caption.getConfidence());
|
||||
System.out.printf(" Region: x=%d, y=%d, w=%d, h=%d%n",
|
||||
caption.getBoundingBox().getX(),
|
||||
caption.getBoundingBox().getY(),
|
||||
caption.getBoundingBox().getWidth(),
|
||||
caption.getBoundingBox().getHeight());
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Features
|
||||
|
||||
```java
|
||||
ImageAnalysisResult result = client.analyzeFromUrl(
|
||||
imageUrl,
|
||||
Arrays.asList(
|
||||
VisualFeatures.CAPTION,
|
||||
VisualFeatures.TAGS,
|
||||
VisualFeatures.OBJECTS,
|
||||
VisualFeatures.READ),
|
||||
new ImageAnalysisOptions()
|
||||
.setGenderNeutralCaption(true)
|
||||
.setLanguage("en"));
|
||||
|
||||
// Access all results
|
||||
System.out.println("Caption: " + result.getCaption().getText());
|
||||
System.out.println("Tags: " + result.getTags().size());
|
||||
System.out.println("Objects: " + result.getObjects().size());
|
||||
System.out.println("Text blocks: " + result.getRead().getBlocks().size());
|
||||
```
|
||||
|
||||
### Async Analysis
|
||||
|
||||
```java
|
||||
asyncClient.analyzeFromUrl(
|
||||
imageUrl,
|
||||
Arrays.asList(VisualFeatures.CAPTION),
|
||||
null)
|
||||
.subscribe(
|
||||
result -> System.out.println("Caption: " + result.getCaption().getText()),
|
||||
error -> System.err.println("Error: " + error.getMessage()),
|
||||
() -> System.out.println("Complete")
|
||||
);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
client.analyzeFromUrl(imageUrl, Arrays.asList(VisualFeatures.CAPTION), null);
|
||||
} catch (HttpResponseException e) {
|
||||
System.out.println("Status: " + e.getResponse().getStatusCode());
|
||||
System.out.println("Error: " + e.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
VISION_ENDPOINT=https://<resource>.cognitiveservices.azure.com/
|
||||
VISION_KEY=<your-api-key>
|
||||
```
|
||||
|
||||
## Image Requirements
|
||||
|
||||
- Formats: JPEG, PNG, GIF, BMP, WEBP, ICO, TIFF, MPO
|
||||
- Size: < 20 MB
|
||||
- Dimensions: 50x50 to 16000x16000 pixels
|
||||
|
||||
## Regional Availability
|
||||
|
||||
Caption and Dense Captions require GPU-supported regions. Check [supported regions](https://learn.microsoft.com/azure/ai-services/computer-vision/concept-describe-images-40) before deployment.
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "image analysis Java"
|
||||
- "Azure Vision SDK"
|
||||
- "image captioning"
|
||||
- "OCR image text extraction"
|
||||
- "object detection image"
|
||||
- "smart crop thumbnail"
|
||||
- "detect people image"
|
||||
225
skills/official/microsoft/java/foundry/voicelive/SKILL.md
Normal file
225
skills/official/microsoft/java/foundry/voicelive/SKILL.md
Normal file
@@ -0,0 +1,225 @@
|
||||
---
|
||||
name: azure-ai-voicelive-java
|
||||
description: |
|
||||
Azure AI VoiceLive SDK for Java. Real-time bidirectional voice conversations with AI assistants using WebSocket.
|
||||
Triggers: "VoiceLiveClient java", "voice assistant java", "real-time voice java", "audio streaming java", "voice activity detection java".
|
||||
package: com.azure:azure-ai-voicelive
|
||||
---
|
||||
|
||||
# Azure AI VoiceLive SDK for Java
|
||||
|
||||
Real-time, bidirectional voice conversations with AI assistants using WebSocket technology.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-ai-voicelive</artifactId>
|
||||
<version>1.0.0-beta.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_VOICELIVE_ENDPOINT=https://<resource>.openai.azure.com/
|
||||
AZURE_VOICELIVE_API_KEY=<your-api-key>
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### API Key
|
||||
|
||||
```java
|
||||
import com.azure.ai.voicelive.VoiceLiveAsyncClient;
|
||||
import com.azure.ai.voicelive.VoiceLiveClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
|
||||
VoiceLiveAsyncClient client = new VoiceLiveClientBuilder()
|
||||
.endpoint(System.getenv("AZURE_VOICELIVE_ENDPOINT"))
|
||||
.credential(new AzureKeyCredential(System.getenv("AZURE_VOICELIVE_API_KEY")))
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
### DefaultAzureCredential (Recommended)
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
VoiceLiveAsyncClient client = new VoiceLiveClientBuilder()
|
||||
.endpoint(System.getenv("AZURE_VOICELIVE_ENDPOINT"))
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| `VoiceLiveAsyncClient` | Main entry point for voice sessions |
|
||||
| `VoiceLiveSessionAsyncClient` | Active WebSocket connection for streaming |
|
||||
| `VoiceLiveSessionOptions` | Configuration for session behavior |
|
||||
|
||||
### Audio Requirements
|
||||
|
||||
- **Sample Rate**: 24kHz (24000 Hz)
|
||||
- **Bit Depth**: 16-bit PCM
|
||||
- **Channels**: Mono (1 channel)
|
||||
- **Format**: Signed PCM, little-endian
|
||||
|
||||
## Core Workflow
|
||||
|
||||
### 1. Start Session
|
||||
|
||||
```java
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
client.startSession("gpt-4o-realtime-preview")
|
||||
.flatMap(session -> {
|
||||
System.out.println("Session started");
|
||||
|
||||
// Subscribe to events
|
||||
session.receiveEvents()
|
||||
.subscribe(
|
||||
event -> System.out.println("Event: " + event.getType()),
|
||||
error -> System.err.println("Error: " + error.getMessage())
|
||||
);
|
||||
|
||||
return Mono.just(session);
|
||||
})
|
||||
.block();
|
||||
```
|
||||
|
||||
### 2. Configure Session Options
|
||||
|
||||
```java
|
||||
import com.azure.ai.voicelive.models.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
ServerVadTurnDetection turnDetection = new ServerVadTurnDetection()
|
||||
.setThreshold(0.5) // Sensitivity (0.0-1.0)
|
||||
.setPrefixPaddingMs(300) // Audio before speech
|
||||
.setSilenceDurationMs(500) // Silence to end turn
|
||||
.setInterruptResponse(true) // Allow interruptions
|
||||
.setAutoTruncate(true)
|
||||
.setCreateResponse(true);
|
||||
|
||||
AudioInputTranscriptionOptions transcription = new AudioInputTranscriptionOptions(
|
||||
AudioInputTranscriptionOptionsModel.WHISPER_1);
|
||||
|
||||
VoiceLiveSessionOptions options = new VoiceLiveSessionOptions()
|
||||
.setInstructions("You are a helpful AI voice assistant.")
|
||||
.setVoice(BinaryData.fromObject(new OpenAIVoice(OpenAIVoiceName.ALLOY)))
|
||||
.setModalities(Arrays.asList(InteractionModality.TEXT, InteractionModality.AUDIO))
|
||||
.setInputAudioFormat(InputAudioFormat.PCM16)
|
||||
.setOutputAudioFormat(OutputAudioFormat.PCM16)
|
||||
.setInputAudioSamplingRate(24000)
|
||||
.setInputAudioNoiseReduction(new AudioNoiseReduction(AudioNoiseReductionType.NEAR_FIELD))
|
||||
.setInputAudioEchoCancellation(new AudioEchoCancellation())
|
||||
.setInputAudioTranscription(transcription)
|
||||
.setTurnDetection(turnDetection);
|
||||
|
||||
// Send configuration
|
||||
ClientEventSessionUpdate updateEvent = new ClientEventSessionUpdate(options);
|
||||
session.sendEvent(updateEvent).subscribe();
|
||||
```
|
||||
|
||||
### 3. Send Audio Input
|
||||
|
||||
```java
|
||||
byte[] audioData = readAudioChunk(); // Your PCM16 audio data
|
||||
session.sendInputAudio(BinaryData.fromBytes(audioData)).subscribe();
|
||||
```
|
||||
|
||||
### 4. Handle Events
|
||||
|
||||
```java
|
||||
session.receiveEvents().subscribe(event -> {
|
||||
ServerEventType eventType = event.getType();
|
||||
|
||||
if (ServerEventType.SESSION_CREATED.equals(eventType)) {
|
||||
System.out.println("Session created");
|
||||
} else if (ServerEventType.INPUT_AUDIO_BUFFER_SPEECH_STARTED.equals(eventType)) {
|
||||
System.out.println("User started speaking");
|
||||
} else if (ServerEventType.INPUT_AUDIO_BUFFER_SPEECH_STOPPED.equals(eventType)) {
|
||||
System.out.println("User stopped speaking");
|
||||
} else if (ServerEventType.RESPONSE_AUDIO_DELTA.equals(eventType)) {
|
||||
if (event instanceof SessionUpdateResponseAudioDelta) {
|
||||
SessionUpdateResponseAudioDelta audioEvent = (SessionUpdateResponseAudioDelta) event;
|
||||
playAudioChunk(audioEvent.getDelta());
|
||||
}
|
||||
} else if (ServerEventType.RESPONSE_DONE.equals(eventType)) {
|
||||
System.out.println("Response complete");
|
||||
} else if (ServerEventType.ERROR.equals(eventType)) {
|
||||
if (event instanceof SessionUpdateError) {
|
||||
SessionUpdateError errorEvent = (SessionUpdateError) event;
|
||||
System.err.println("Error: " + errorEvent.getError().getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Voice Configuration
|
||||
|
||||
### OpenAI Voices
|
||||
|
||||
```java
|
||||
// Available: ALLOY, ASH, BALLAD, CORAL, ECHO, SAGE, SHIMMER, VERSE
|
||||
VoiceLiveSessionOptions options = new VoiceLiveSessionOptions()
|
||||
.setVoice(BinaryData.fromObject(new OpenAIVoice(OpenAIVoiceName.ALLOY)));
|
||||
```
|
||||
|
||||
### Azure Voices
|
||||
|
||||
```java
|
||||
// Azure Standard Voice
|
||||
options.setVoice(BinaryData.fromObject(new AzureStandardVoice("en-US-JennyNeural")));
|
||||
|
||||
// Azure Custom Voice
|
||||
options.setVoice(BinaryData.fromObject(new AzureCustomVoice("myVoice", "endpointId")));
|
||||
|
||||
// Azure Personal Voice
|
||||
options.setVoice(BinaryData.fromObject(
|
||||
new AzurePersonalVoice("speakerProfileId", PersonalVoiceModels.PHOENIX_LATEST_NEURAL)));
|
||||
```
|
||||
|
||||
## Function Calling
|
||||
|
||||
```java
|
||||
VoiceLiveFunctionDefinition weatherFunction = new VoiceLiveFunctionDefinition("get_weather")
|
||||
.setDescription("Get current weather for a location")
|
||||
.setParameters(BinaryData.fromObject(parametersSchema));
|
||||
|
||||
VoiceLiveSessionOptions options = new VoiceLiveSessionOptions()
|
||||
.setTools(Arrays.asList(weatherFunction))
|
||||
.setInstructions("You have access to weather information.");
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use async client** — VoiceLive requires reactive patterns
|
||||
2. **Configure turn detection** for natural conversation flow
|
||||
3. **Enable noise reduction** for better speech recognition
|
||||
4. **Handle interruptions** gracefully with `setInterruptResponse(true)`
|
||||
5. **Use Whisper transcription** for input audio transcription
|
||||
6. **Close sessions** properly when conversation ends
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
session.receiveEvents()
|
||||
.doOnError(error -> System.err.println("Connection error: " + error.getMessage()))
|
||||
.onErrorResume(error -> {
|
||||
// Attempt reconnection or cleanup
|
||||
return Flux.empty();
|
||||
})
|
||||
.subscribe();
|
||||
```
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| GitHub Source | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-voicelive |
|
||||
| Samples | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-voicelive/src/samples |
|
||||
@@ -0,0 +1,470 @@
|
||||
---
|
||||
name: azure-appconfiguration-java
|
||||
description: |
|
||||
Azure App Configuration SDK for Java. Centralized application configuration management with key-value settings, feature flags, and snapshots.
|
||||
Triggers: "ConfigurationClient java", "app configuration java", "feature flag java", "configuration setting java", "azure config java".
|
||||
package: com.azure:azure-data-appconfiguration
|
||||
---
|
||||
|
||||
# Azure App Configuration SDK for Java
|
||||
|
||||
Client library for Azure App Configuration, a managed service for centralizing application configurations.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-data-appconfiguration</artifactId>
|
||||
<version>1.8.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Or use Azure SDK BOM:
|
||||
|
||||
```xml
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-sdk-bom</artifactId>
|
||||
<version>{bom_version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-data-appconfiguration</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Azure App Configuration store
|
||||
- Connection string or Entra ID credentials
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_APPCONFIG_CONNECTION_STRING=Endpoint=https://<store>.azconfig.io;Id=<id>;Secret=<secret>
|
||||
AZURE_APPCONFIG_ENDPOINT=https://<store>.azconfig.io
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### With Connection String
|
||||
|
||||
```java
|
||||
import com.azure.data.appconfiguration.ConfigurationClient;
|
||||
import com.azure.data.appconfiguration.ConfigurationClientBuilder;
|
||||
|
||||
ConfigurationClient configClient = new ConfigurationClientBuilder()
|
||||
.connectionString(System.getenv("AZURE_APPCONFIG_CONNECTION_STRING"))
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### Async Client
|
||||
|
||||
```java
|
||||
import com.azure.data.appconfiguration.ConfigurationAsyncClient;
|
||||
|
||||
ConfigurationAsyncClient asyncClient = new ConfigurationClientBuilder()
|
||||
.connectionString(connectionString)
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
### With Entra ID (Recommended)
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
ConfigurationClient configClient = new ConfigurationClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.endpoint(System.getenv("AZURE_APPCONFIG_ENDPOINT"))
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| Configuration Setting | Key-value pair with optional label |
|
||||
| Label | Dimension for separating settings (e.g., environments) |
|
||||
| Feature Flag | Special setting for feature management |
|
||||
| Secret Reference | Setting pointing to Key Vault secret |
|
||||
| Snapshot | Point-in-time immutable view of settings |
|
||||
|
||||
## Configuration Setting Operations
|
||||
|
||||
### Create Setting (Add)
|
||||
|
||||
Creates only if setting doesn't exist:
|
||||
|
||||
```java
|
||||
import com.azure.data.appconfiguration.models.ConfigurationSetting;
|
||||
|
||||
ConfigurationSetting setting = configClient.addConfigurationSetting(
|
||||
"app/database/connection",
|
||||
"Production",
|
||||
"Server=prod.db.com;Database=myapp"
|
||||
);
|
||||
```
|
||||
|
||||
### Create or Update Setting (Set)
|
||||
|
||||
Creates or overwrites:
|
||||
|
||||
```java
|
||||
ConfigurationSetting setting = configClient.setConfigurationSetting(
|
||||
"app/cache/enabled",
|
||||
"Production",
|
||||
"true"
|
||||
);
|
||||
```
|
||||
|
||||
### Get Setting
|
||||
|
||||
```java
|
||||
ConfigurationSetting setting = configClient.getConfigurationSetting(
|
||||
"app/database/connection",
|
||||
"Production"
|
||||
);
|
||||
System.out.println("Value: " + setting.getValue());
|
||||
System.out.println("Content-Type: " + setting.getContentType());
|
||||
System.out.println("Last Modified: " + setting.getLastModified());
|
||||
```
|
||||
|
||||
### Conditional Get (If Changed)
|
||||
|
||||
```java
|
||||
import com.azure.core.http.rest.Response;
|
||||
import com.azure.core.util.Context;
|
||||
|
||||
Response<ConfigurationSetting> response = configClient.getConfigurationSettingWithResponse(
|
||||
setting, // Setting with ETag
|
||||
null, // Accept datetime
|
||||
true, // ifChanged - only fetch if modified
|
||||
Context.NONE
|
||||
);
|
||||
|
||||
if (response.getStatusCode() == 304) {
|
||||
System.out.println("Setting not modified");
|
||||
} else {
|
||||
ConfigurationSetting updated = response.getValue();
|
||||
}
|
||||
```
|
||||
|
||||
### Update Setting
|
||||
|
||||
```java
|
||||
ConfigurationSetting updated = configClient.setConfigurationSetting(
|
||||
"app/cache/enabled",
|
||||
"Production",
|
||||
"false"
|
||||
);
|
||||
```
|
||||
|
||||
### Conditional Update (If Unchanged)
|
||||
|
||||
```java
|
||||
// Only update if ETag matches (no concurrent modifications)
|
||||
Response<ConfigurationSetting> response = configClient.setConfigurationSettingWithResponse(
|
||||
setting, // Setting with current ETag
|
||||
true, // ifUnchanged
|
||||
Context.NONE
|
||||
);
|
||||
```
|
||||
|
||||
### Delete Setting
|
||||
|
||||
```java
|
||||
ConfigurationSetting deleted = configClient.deleteConfigurationSetting(
|
||||
"app/cache/enabled",
|
||||
"Production"
|
||||
);
|
||||
```
|
||||
|
||||
### Conditional Delete
|
||||
|
||||
```java
|
||||
Response<ConfigurationSetting> response = configClient.deleteConfigurationSettingWithResponse(
|
||||
setting, // Setting with ETag
|
||||
true, // ifUnchanged
|
||||
Context.NONE
|
||||
);
|
||||
```
|
||||
|
||||
## List and Filter Settings
|
||||
|
||||
### List by Key Pattern
|
||||
|
||||
```java
|
||||
import com.azure.data.appconfiguration.models.SettingSelector;
|
||||
import com.azure.core.http.rest.PagedIterable;
|
||||
|
||||
SettingSelector selector = new SettingSelector()
|
||||
.setKeyFilter("app/*");
|
||||
|
||||
PagedIterable<ConfigurationSetting> settings = configClient.listConfigurationSettings(selector);
|
||||
for (ConfigurationSetting s : settings) {
|
||||
System.out.println(s.getKey() + " = " + s.getValue());
|
||||
}
|
||||
```
|
||||
|
||||
### List by Label
|
||||
|
||||
```java
|
||||
SettingSelector selector = new SettingSelector()
|
||||
.setKeyFilter("*")
|
||||
.setLabelFilter("Production");
|
||||
|
||||
PagedIterable<ConfigurationSetting> settings = configClient.listConfigurationSettings(selector);
|
||||
```
|
||||
|
||||
### List by Multiple Keys
|
||||
|
||||
```java
|
||||
SettingSelector selector = new SettingSelector()
|
||||
.setKeyFilter("app/database/*,app/cache/*");
|
||||
|
||||
PagedIterable<ConfigurationSetting> settings = configClient.listConfigurationSettings(selector);
|
||||
```
|
||||
|
||||
### List Revisions
|
||||
|
||||
```java
|
||||
SettingSelector selector = new SettingSelector()
|
||||
.setKeyFilter("app/database/connection");
|
||||
|
||||
PagedIterable<ConfigurationSetting> revisions = configClient.listRevisions(selector);
|
||||
for (ConfigurationSetting revision : revisions) {
|
||||
System.out.println("Value: " + revision.getValue() + ", Modified: " + revision.getLastModified());
|
||||
}
|
||||
```
|
||||
|
||||
## Feature Flags
|
||||
|
||||
### Create Feature Flag
|
||||
|
||||
```java
|
||||
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
|
||||
import com.azure.data.appconfiguration.models.FeatureFlagFilter;
|
||||
import java.util.Arrays;
|
||||
|
||||
FeatureFlagFilter percentageFilter = new FeatureFlagFilter("Microsoft.Percentage")
|
||||
.addParameter("Value", 50);
|
||||
|
||||
FeatureFlagConfigurationSetting featureFlag = new FeatureFlagConfigurationSetting("beta-feature", true)
|
||||
.setDescription("Beta feature rollout")
|
||||
.setClientFilters(Arrays.asList(percentageFilter));
|
||||
|
||||
FeatureFlagConfigurationSetting created = (FeatureFlagConfigurationSetting)
|
||||
configClient.addConfigurationSetting(featureFlag);
|
||||
```
|
||||
|
||||
### Get Feature Flag
|
||||
|
||||
```java
|
||||
FeatureFlagConfigurationSetting flag = (FeatureFlagConfigurationSetting)
|
||||
configClient.getConfigurationSetting(featureFlag);
|
||||
|
||||
System.out.println("Feature: " + flag.getFeatureId());
|
||||
System.out.println("Enabled: " + flag.isEnabled());
|
||||
System.out.println("Filters: " + flag.getClientFilters());
|
||||
```
|
||||
|
||||
### Update Feature Flag
|
||||
|
||||
```java
|
||||
featureFlag.setEnabled(false);
|
||||
FeatureFlagConfigurationSetting updated = (FeatureFlagConfigurationSetting)
|
||||
configClient.setConfigurationSetting(featureFlag);
|
||||
```
|
||||
|
||||
## Secret References
|
||||
|
||||
### Create Secret Reference
|
||||
|
||||
```java
|
||||
import com.azure.data.appconfiguration.models.SecretReferenceConfigurationSetting;
|
||||
|
||||
SecretReferenceConfigurationSetting secretRef = new SecretReferenceConfigurationSetting(
|
||||
"app/secrets/api-key",
|
||||
"https://myvault.vault.azure.net/secrets/api-key"
|
||||
);
|
||||
|
||||
SecretReferenceConfigurationSetting created = (SecretReferenceConfigurationSetting)
|
||||
configClient.addConfigurationSetting(secretRef);
|
||||
```
|
||||
|
||||
### Get Secret Reference
|
||||
|
||||
```java
|
||||
SecretReferenceConfigurationSetting ref = (SecretReferenceConfigurationSetting)
|
||||
configClient.getConfigurationSetting(secretRef);
|
||||
|
||||
System.out.println("Secret URI: " + ref.getSecretId());
|
||||
```
|
||||
|
||||
## Read-Only Settings
|
||||
|
||||
### Set Read-Only
|
||||
|
||||
```java
|
||||
ConfigurationSetting readOnly = configClient.setReadOnly(
|
||||
"app/critical/setting",
|
||||
"Production",
|
||||
true
|
||||
);
|
||||
```
|
||||
|
||||
### Clear Read-Only
|
||||
|
||||
```java
|
||||
ConfigurationSetting writable = configClient.setReadOnly(
|
||||
"app/critical/setting",
|
||||
"Production",
|
||||
false
|
||||
);
|
||||
```
|
||||
|
||||
## Snapshots
|
||||
|
||||
### Create Snapshot
|
||||
|
||||
```java
|
||||
import com.azure.data.appconfiguration.models.ConfigurationSnapshot;
|
||||
import com.azure.data.appconfiguration.models.ConfigurationSettingsFilter;
|
||||
import com.azure.core.util.polling.SyncPoller;
|
||||
import com.azure.core.util.polling.PollOperationDetails;
|
||||
|
||||
List<ConfigurationSettingsFilter> filters = new ArrayList<>();
|
||||
filters.add(new ConfigurationSettingsFilter("app/*"));
|
||||
|
||||
SyncPoller<PollOperationDetails, ConfigurationSnapshot> poller = configClient.beginCreateSnapshot(
|
||||
"release-v1.0",
|
||||
new ConfigurationSnapshot(filters),
|
||||
Context.NONE
|
||||
);
|
||||
poller.setPollInterval(Duration.ofSeconds(10));
|
||||
poller.waitForCompletion();
|
||||
|
||||
ConfigurationSnapshot snapshot = poller.getFinalResult();
|
||||
System.out.println("Snapshot: " + snapshot.getName() + ", Status: " + snapshot.getStatus());
|
||||
```
|
||||
|
||||
### Get Snapshot
|
||||
|
||||
```java
|
||||
ConfigurationSnapshot snapshot = configClient.getSnapshot("release-v1.0");
|
||||
System.out.println("Created: " + snapshot.getCreatedAt());
|
||||
System.out.println("Items: " + snapshot.getItemCount());
|
||||
```
|
||||
|
||||
### List Settings in Snapshot
|
||||
|
||||
```java
|
||||
PagedIterable<ConfigurationSetting> settings =
|
||||
configClient.listConfigurationSettingsForSnapshot("release-v1.0");
|
||||
|
||||
for (ConfigurationSetting setting : settings) {
|
||||
System.out.println(setting.getKey() + " = " + setting.getValue());
|
||||
}
|
||||
```
|
||||
|
||||
### Archive Snapshot
|
||||
|
||||
```java
|
||||
ConfigurationSnapshot archived = configClient.archiveSnapshot("release-v1.0");
|
||||
System.out.println("Status: " + archived.getStatus()); // archived
|
||||
```
|
||||
|
||||
### Recover Snapshot
|
||||
|
||||
```java
|
||||
ConfigurationSnapshot recovered = configClient.recoverSnapshot("release-v1.0");
|
||||
System.out.println("Status: " + recovered.getStatus()); // ready
|
||||
```
|
||||
|
||||
### List All Snapshots
|
||||
|
||||
```java
|
||||
import com.azure.data.appconfiguration.models.SnapshotSelector;
|
||||
|
||||
SnapshotSelector selector = new SnapshotSelector().setNameFilter("release-*");
|
||||
PagedIterable<ConfigurationSnapshot> snapshots = configClient.listSnapshots(selector);
|
||||
|
||||
for (ConfigurationSnapshot snap : snapshots) {
|
||||
System.out.println(snap.getName() + " - " + snap.getStatus());
|
||||
}
|
||||
```
|
||||
|
||||
## Labels
|
||||
|
||||
### List Labels
|
||||
|
||||
```java
|
||||
import com.azure.data.appconfiguration.models.SettingLabelSelector;
|
||||
|
||||
configClient.listLabels(new SettingLabelSelector().setNameFilter("*"))
|
||||
.forEach(label -> System.out.println("Label: " + label.getName()));
|
||||
```
|
||||
|
||||
## Async Operations
|
||||
|
||||
```java
|
||||
ConfigurationAsyncClient asyncClient = new ConfigurationClientBuilder()
|
||||
.connectionString(connectionString)
|
||||
.buildAsyncClient();
|
||||
|
||||
// Async list with reactive streams
|
||||
asyncClient.listConfigurationSettings(new SettingSelector().setLabelFilter("Production"))
|
||||
.subscribe(
|
||||
setting -> System.out.println(setting.getKey() + " = " + setting.getValue()),
|
||||
error -> System.err.println("Error: " + error.getMessage()),
|
||||
() -> System.out.println("Completed")
|
||||
);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
configClient.getConfigurationSetting("nonexistent", null);
|
||||
} catch (HttpResponseException e) {
|
||||
if (e.getResponse().getStatusCode() == 404) {
|
||||
System.err.println("Setting not found");
|
||||
} else {
|
||||
System.err.println("Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use labels** — Separate configurations by environment (Dev, Staging, Production)
|
||||
2. **Use snapshots** — Create immutable snapshots for releases
|
||||
3. **Feature flags** — Use for gradual rollouts and A/B testing
|
||||
4. **Secret references** — Store sensitive values in Key Vault
|
||||
5. **Conditional requests** — Use ETags for optimistic concurrency
|
||||
6. **Read-only protection** — Lock critical production settings
|
||||
7. **Use Entra ID** — Preferred over connection strings
|
||||
8. **Async client** — Use for high-throughput scenarios
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| Maven Package | https://central.sonatype.com/artifact/com.azure/azure-data-appconfiguration |
|
||||
| GitHub | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/appconfiguration/azure-data-appconfiguration |
|
||||
| API Documentation | https://aka.ms/java-docs |
|
||||
| Product Docs | https://learn.microsoft.com/azure/azure-app-configuration |
|
||||
| Samples | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/appconfiguration/azure-data-appconfiguration/src/samples |
|
||||
| Troubleshooting | https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/appconfiguration/azure-data-appconfiguration/TROUBLESHOOTING.md |
|
||||
305
skills/official/microsoft/java/messaging/eventgrid/SKILL.md
Normal file
305
skills/official/microsoft/java/messaging/eventgrid/SKILL.md
Normal file
@@ -0,0 +1,305 @@
|
||||
---
|
||||
name: azure-eventgrid-java
|
||||
description: Build event-driven applications with Azure Event Grid SDK for Java. Use when publishing events, implementing pub/sub patterns, or integrating with Azure services via events.
|
||||
package: com.azure:azure-messaging-eventgrid
|
||||
---
|
||||
|
||||
# Azure Event Grid SDK for Java
|
||||
|
||||
Build event-driven applications using the Azure Event Grid SDK for Java.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-messaging-eventgrid</artifactId>
|
||||
<version>4.27.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### EventGridPublisherClient
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventgrid.EventGridPublisherClient;
|
||||
import com.azure.messaging.eventgrid.EventGridPublisherClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
|
||||
// With API Key
|
||||
EventGridPublisherClient<EventGridEvent> client = new EventGridPublisherClientBuilder()
|
||||
.endpoint("<topic-endpoint>")
|
||||
.credential(new AzureKeyCredential("<access-key>"))
|
||||
.buildEventGridEventPublisherClient();
|
||||
|
||||
// For CloudEvents
|
||||
EventGridPublisherClient<CloudEvent> cloudClient = new EventGridPublisherClientBuilder()
|
||||
.endpoint("<topic-endpoint>")
|
||||
.credential(new AzureKeyCredential("<access-key>"))
|
||||
.buildCloudEventPublisherClient();
|
||||
```
|
||||
|
||||
### With DefaultAzureCredential
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
EventGridPublisherClient<EventGridEvent> client = new EventGridPublisherClientBuilder()
|
||||
.endpoint("<topic-endpoint>")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildEventGridEventPublisherClient();
|
||||
```
|
||||
|
||||
### Async Client
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventgrid.EventGridPublisherAsyncClient;
|
||||
|
||||
EventGridPublisherAsyncClient<EventGridEvent> asyncClient = new EventGridPublisherClientBuilder()
|
||||
.endpoint("<topic-endpoint>")
|
||||
.credential(new AzureKeyCredential("<access-key>"))
|
||||
.buildEventGridEventPublisherAsyncClient();
|
||||
```
|
||||
|
||||
## Event Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `EventGridEvent` | Azure Event Grid native schema |
|
||||
| `CloudEvent` | CNCF CloudEvents 1.0 specification |
|
||||
| `BinaryData` | Custom schema events |
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Publish EventGridEvent
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventgrid.EventGridEvent;
|
||||
import com.azure.core.util.BinaryData;
|
||||
|
||||
EventGridEvent event = new EventGridEvent(
|
||||
"resource/path", // subject
|
||||
"MyApp.Events.OrderCreated", // eventType
|
||||
BinaryData.fromObject(new OrderData("order-123", 99.99)), // data
|
||||
"1.0" // dataVersion
|
||||
);
|
||||
|
||||
client.sendEvent(event);
|
||||
```
|
||||
|
||||
### Publish Multiple Events
|
||||
|
||||
```java
|
||||
List<EventGridEvent> events = Arrays.asList(
|
||||
new EventGridEvent("orders/1", "Order.Created",
|
||||
BinaryData.fromObject(order1), "1.0"),
|
||||
new EventGridEvent("orders/2", "Order.Created",
|
||||
BinaryData.fromObject(order2), "1.0")
|
||||
);
|
||||
|
||||
client.sendEvents(events);
|
||||
```
|
||||
|
||||
### Publish CloudEvent
|
||||
|
||||
```java
|
||||
import com.azure.core.models.CloudEvent;
|
||||
import com.azure.core.models.CloudEventDataFormat;
|
||||
|
||||
CloudEvent cloudEvent = new CloudEvent(
|
||||
"/myapp/orders", // source
|
||||
"order.created", // type
|
||||
BinaryData.fromObject(orderData), // data
|
||||
CloudEventDataFormat.JSON // dataFormat
|
||||
);
|
||||
cloudEvent.setSubject("orders/12345");
|
||||
cloudEvent.setId(UUID.randomUUID().toString());
|
||||
|
||||
cloudClient.sendEvent(cloudEvent);
|
||||
```
|
||||
|
||||
### Publish CloudEvents Batch
|
||||
|
||||
```java
|
||||
List<CloudEvent> cloudEvents = Arrays.asList(
|
||||
new CloudEvent("/app", "event.type1", BinaryData.fromString("data1"), CloudEventDataFormat.JSON),
|
||||
new CloudEvent("/app", "event.type2", BinaryData.fromString("data2"), CloudEventDataFormat.JSON)
|
||||
);
|
||||
|
||||
cloudClient.sendEvents(cloudEvents);
|
||||
```
|
||||
|
||||
### Async Publishing
|
||||
|
||||
```java
|
||||
asyncClient.sendEvent(event)
|
||||
.subscribe(
|
||||
unused -> System.out.println("Event sent successfully"),
|
||||
error -> System.err.println("Error: " + error.getMessage())
|
||||
);
|
||||
|
||||
// With multiple events
|
||||
asyncClient.sendEvents(events)
|
||||
.doOnSuccess(unused -> System.out.println("All events sent"))
|
||||
.doOnError(error -> System.err.println("Failed: " + error))
|
||||
.block(); // Block if needed
|
||||
```
|
||||
|
||||
### Custom Event Data Class
|
||||
|
||||
```java
|
||||
public class OrderData {
|
||||
private String orderId;
|
||||
private double amount;
|
||||
private String customerId;
|
||||
|
||||
public OrderData(String orderId, double amount) {
|
||||
this.orderId = orderId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
// Getters and setters
|
||||
}
|
||||
|
||||
// Usage
|
||||
OrderData order = new OrderData("ORD-123", 150.00);
|
||||
EventGridEvent event = new EventGridEvent(
|
||||
"orders/" + order.getOrderId(),
|
||||
"MyApp.Order.Created",
|
||||
BinaryData.fromObject(order),
|
||||
"1.0"
|
||||
);
|
||||
```
|
||||
|
||||
## Receiving Events
|
||||
|
||||
### Parse EventGridEvent
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventgrid.EventGridEvent;
|
||||
|
||||
// From JSON string (e.g., webhook payload)
|
||||
String jsonPayload = "[{\"id\": \"...\", ...}]";
|
||||
List<EventGridEvent> events = EventGridEvent.fromString(jsonPayload);
|
||||
|
||||
for (EventGridEvent event : events) {
|
||||
System.out.println("Event Type: " + event.getEventType());
|
||||
System.out.println("Subject: " + event.getSubject());
|
||||
System.out.println("Event Time: " + event.getEventTime());
|
||||
|
||||
// Get data
|
||||
BinaryData data = event.getData();
|
||||
OrderData orderData = data.toObject(OrderData.class);
|
||||
}
|
||||
```
|
||||
|
||||
### Parse CloudEvent
|
||||
|
||||
```java
|
||||
import com.azure.core.models.CloudEvent;
|
||||
|
||||
String cloudEventJson = "[{\"specversion\": \"1.0\", ...}]";
|
||||
List<CloudEvent> cloudEvents = CloudEvent.fromString(cloudEventJson);
|
||||
|
||||
for (CloudEvent event : cloudEvents) {
|
||||
System.out.println("Type: " + event.getType());
|
||||
System.out.println("Source: " + event.getSource());
|
||||
System.out.println("ID: " + event.getId());
|
||||
|
||||
MyEventData data = event.getData().toObject(MyEventData.class);
|
||||
}
|
||||
```
|
||||
|
||||
### Handle System Events
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventgrid.systemevents.*;
|
||||
|
||||
for (EventGridEvent event : events) {
|
||||
if (event.getEventType().equals("Microsoft.Storage.BlobCreated")) {
|
||||
StorageBlobCreatedEventData blobData =
|
||||
event.getData().toObject(StorageBlobCreatedEventData.class);
|
||||
System.out.println("Blob URL: " + blobData.getUrl());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Event Grid Namespaces (MQTT/Pull)
|
||||
|
||||
### Receive from Namespace Topic
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventgrid.namespaces.EventGridReceiverClient;
|
||||
import com.azure.messaging.eventgrid.namespaces.EventGridReceiverClientBuilder;
|
||||
import com.azure.messaging.eventgrid.namespaces.models.*;
|
||||
|
||||
EventGridReceiverClient receiverClient = new EventGridReceiverClientBuilder()
|
||||
.endpoint("<namespace-endpoint>")
|
||||
.credential(new AzureKeyCredential("<key>"))
|
||||
.topicName("my-topic")
|
||||
.subscriptionName("my-subscription")
|
||||
.buildClient();
|
||||
|
||||
// Receive events
|
||||
ReceiveResult result = receiverClient.receive(10, Duration.ofSeconds(30));
|
||||
|
||||
for (ReceiveDetails detail : result.getValue()) {
|
||||
CloudEvent event = detail.getEvent();
|
||||
System.out.println("Event: " + event.getType());
|
||||
|
||||
// Acknowledge the event
|
||||
receiverClient.acknowledge(Arrays.asList(detail.getBrokerProperties().getLockToken()));
|
||||
}
|
||||
```
|
||||
|
||||
### Reject or Release Events
|
||||
|
||||
```java
|
||||
// Reject (don't retry)
|
||||
receiverClient.reject(Arrays.asList(lockToken));
|
||||
|
||||
// Release (retry later)
|
||||
receiverClient.release(Arrays.asList(lockToken));
|
||||
|
||||
// Release with delay
|
||||
receiverClient.release(Arrays.asList(lockToken),
|
||||
new ReleaseOptions().setDelay(ReleaseDelay.BY_60_SECONDS));
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
client.sendEvent(event);
|
||||
} catch (HttpResponseException e) {
|
||||
System.out.println("Status: " + e.getResponse().getStatusCode());
|
||||
System.out.println("Error: " + e.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
EVENT_GRID_TOPIC_ENDPOINT=https://<topic-name>.<region>.eventgrid.azure.net/api/events
|
||||
EVENT_GRID_ACCESS_KEY=<your-access-key>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Batch Events**: Send multiple events in one call when possible
|
||||
2. **Idempotency**: Include unique event IDs for deduplication
|
||||
3. **Schema Validation**: Use strongly-typed event data classes
|
||||
4. **Retry Logic**: Built-in, but consider dead-letter for failures
|
||||
5. **Event Size**: Keep events under 1MB (64KB for basic tier)
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "Event Grid Java"
|
||||
- "publish events Azure"
|
||||
- "CloudEvent SDK"
|
||||
- "event-driven messaging"
|
||||
- "pub/sub Azure"
|
||||
- "webhook events"
|
||||
356
skills/official/microsoft/java/messaging/eventhubs/SKILL.md
Normal file
356
skills/official/microsoft/java/messaging/eventhubs/SKILL.md
Normal file
@@ -0,0 +1,356 @@
|
||||
---
|
||||
name: azure-eventhub-java
|
||||
description: Build real-time streaming applications with Azure Event Hubs SDK for Java. Use when implementing event streaming, high-throughput data ingestion, or building event-driven architectures.
|
||||
package: com.azure:azure-messaging-eventhubs
|
||||
---
|
||||
|
||||
# Azure Event Hubs SDK for Java
|
||||
|
||||
Build real-time streaming applications using the Azure Event Hubs SDK for Java.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-messaging-eventhubs</artifactId>
|
||||
<version>5.19.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- For checkpoint store (production) -->
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-messaging-eventhubs-checkpointstore-blob</artifactId>
|
||||
<version>1.20.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### EventHubProducerClient
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventhubs.EventHubProducerClient;
|
||||
import com.azure.messaging.eventhubs.EventHubClientBuilder;
|
||||
|
||||
// With connection string
|
||||
EventHubProducerClient producer = new EventHubClientBuilder()
|
||||
.connectionString("<connection-string>", "<event-hub-name>")
|
||||
.buildProducerClient();
|
||||
|
||||
// Full connection string with EntityPath
|
||||
EventHubProducerClient producer = new EventHubClientBuilder()
|
||||
.connectionString("<connection-string-with-entity-path>")
|
||||
.buildProducerClient();
|
||||
```
|
||||
|
||||
### With DefaultAzureCredential
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
EventHubProducerClient producer = new EventHubClientBuilder()
|
||||
.fullyQualifiedNamespace("<namespace>.servicebus.windows.net")
|
||||
.eventHubName("<event-hub-name>")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildProducerClient();
|
||||
```
|
||||
|
||||
### EventHubConsumerClient
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventhubs.EventHubConsumerClient;
|
||||
|
||||
EventHubConsumerClient consumer = new EventHubClientBuilder()
|
||||
.connectionString("<connection-string>", "<event-hub-name>")
|
||||
.consumerGroup(EventHubClientBuilder.DEFAULT_CONSUMER_GROUP_NAME)
|
||||
.buildConsumerClient();
|
||||
```
|
||||
|
||||
### Async Clients
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventhubs.EventHubProducerAsyncClient;
|
||||
import com.azure.messaging.eventhubs.EventHubConsumerAsyncClient;
|
||||
|
||||
EventHubProducerAsyncClient asyncProducer = new EventHubClientBuilder()
|
||||
.connectionString("<connection-string>", "<event-hub-name>")
|
||||
.buildAsyncProducerClient();
|
||||
|
||||
EventHubConsumerAsyncClient asyncConsumer = new EventHubClientBuilder()
|
||||
.connectionString("<connection-string>", "<event-hub-name>")
|
||||
.consumerGroup("$Default")
|
||||
.buildAsyncConsumerClient();
|
||||
```
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Send Single Event
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventhubs.EventData;
|
||||
|
||||
EventData eventData = new EventData("Hello, Event Hubs!");
|
||||
producer.send(Collections.singletonList(eventData));
|
||||
```
|
||||
|
||||
### Send Event Batch
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventhubs.EventDataBatch;
|
||||
import com.azure.messaging.eventhubs.models.CreateBatchOptions;
|
||||
|
||||
// Create batch
|
||||
EventDataBatch batch = producer.createBatch();
|
||||
|
||||
// Add events (returns false if batch is full)
|
||||
for (int i = 0; i < 100; i++) {
|
||||
EventData event = new EventData("Event " + i);
|
||||
if (!batch.tryAdd(event)) {
|
||||
// Batch is full, send and create new batch
|
||||
producer.send(batch);
|
||||
batch = producer.createBatch();
|
||||
batch.tryAdd(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Send remaining events
|
||||
if (batch.getCount() > 0) {
|
||||
producer.send(batch);
|
||||
}
|
||||
```
|
||||
|
||||
### Send to Specific Partition
|
||||
|
||||
```java
|
||||
CreateBatchOptions options = new CreateBatchOptions()
|
||||
.setPartitionId("0");
|
||||
|
||||
EventDataBatch batch = producer.createBatch(options);
|
||||
batch.tryAdd(new EventData("Partition 0 event"));
|
||||
producer.send(batch);
|
||||
```
|
||||
|
||||
### Send with Partition Key
|
||||
|
||||
```java
|
||||
CreateBatchOptions options = new CreateBatchOptions()
|
||||
.setPartitionKey("customer-123");
|
||||
|
||||
EventDataBatch batch = producer.createBatch(options);
|
||||
batch.tryAdd(new EventData("Customer event"));
|
||||
producer.send(batch);
|
||||
```
|
||||
|
||||
### Event with Properties
|
||||
|
||||
```java
|
||||
EventData event = new EventData("Order created");
|
||||
event.getProperties().put("orderId", "ORD-123");
|
||||
event.getProperties().put("customerId", "CUST-456");
|
||||
event.getProperties().put("priority", 1);
|
||||
|
||||
producer.send(Collections.singletonList(event));
|
||||
```
|
||||
|
||||
### Receive Events (Simple)
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventhubs.models.EventPosition;
|
||||
import com.azure.messaging.eventhubs.models.PartitionEvent;
|
||||
|
||||
// Receive from specific partition
|
||||
Iterable<PartitionEvent> events = consumer.receiveFromPartition(
|
||||
"0", // partitionId
|
||||
10, // maxEvents
|
||||
EventPosition.earliest(), // startingPosition
|
||||
Duration.ofSeconds(30) // timeout
|
||||
);
|
||||
|
||||
for (PartitionEvent partitionEvent : events) {
|
||||
EventData event = partitionEvent.getData();
|
||||
System.out.println("Body: " + event.getBodyAsString());
|
||||
System.out.println("Sequence: " + event.getSequenceNumber());
|
||||
System.out.println("Offset: " + event.getOffset());
|
||||
}
|
||||
```
|
||||
|
||||
### EventProcessorClient (Production)
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventhubs.EventProcessorClient;
|
||||
import com.azure.messaging.eventhubs.EventProcessorClientBuilder;
|
||||
import com.azure.messaging.eventhubs.checkpointstore.blob.BlobCheckpointStore;
|
||||
import com.azure.storage.blob.BlobContainerAsyncClient;
|
||||
import com.azure.storage.blob.BlobContainerClientBuilder;
|
||||
|
||||
// Create checkpoint store
|
||||
BlobContainerAsyncClient blobClient = new BlobContainerClientBuilder()
|
||||
.connectionString("<storage-connection-string>")
|
||||
.containerName("checkpoints")
|
||||
.buildAsyncClient();
|
||||
|
||||
// Create processor
|
||||
EventProcessorClient processor = new EventProcessorClientBuilder()
|
||||
.connectionString("<eventhub-connection-string>", "<event-hub-name>")
|
||||
.consumerGroup("$Default")
|
||||
.checkpointStore(new BlobCheckpointStore(blobClient))
|
||||
.processEvent(eventContext -> {
|
||||
EventData event = eventContext.getEventData();
|
||||
System.out.println("Processing: " + event.getBodyAsString());
|
||||
|
||||
// Checkpoint after processing
|
||||
eventContext.updateCheckpoint();
|
||||
})
|
||||
.processError(errorContext -> {
|
||||
System.err.println("Error: " + errorContext.getThrowable().getMessage());
|
||||
System.err.println("Partition: " + errorContext.getPartitionContext().getPartitionId());
|
||||
})
|
||||
.buildEventProcessorClient();
|
||||
|
||||
// Start processing
|
||||
processor.start();
|
||||
|
||||
// Keep running...
|
||||
Thread.sleep(Duration.ofMinutes(5).toMillis());
|
||||
|
||||
// Stop gracefully
|
||||
processor.stop();
|
||||
```
|
||||
|
||||
### Batch Processing
|
||||
|
||||
```java
|
||||
EventProcessorClient processor = new EventProcessorClientBuilder()
|
||||
.connectionString("<connection-string>", "<event-hub-name>")
|
||||
.consumerGroup("$Default")
|
||||
.checkpointStore(new BlobCheckpointStore(blobClient))
|
||||
.processEventBatch(eventBatchContext -> {
|
||||
List<EventData> events = eventBatchContext.getEvents();
|
||||
System.out.printf("Received %d events%n", events.size());
|
||||
|
||||
for (EventData event : events) {
|
||||
// Process each event
|
||||
System.out.println(event.getBodyAsString());
|
||||
}
|
||||
|
||||
// Checkpoint after batch
|
||||
eventBatchContext.updateCheckpoint();
|
||||
}, 50) // maxBatchSize
|
||||
.processError(errorContext -> {
|
||||
System.err.println("Error: " + errorContext.getThrowable());
|
||||
})
|
||||
.buildEventProcessorClient();
|
||||
```
|
||||
|
||||
### Async Receiving
|
||||
|
||||
```java
|
||||
asyncConsumer.receiveFromPartition("0", EventPosition.latest())
|
||||
.subscribe(
|
||||
partitionEvent -> {
|
||||
EventData event = partitionEvent.getData();
|
||||
System.out.println("Received: " + event.getBodyAsString());
|
||||
},
|
||||
error -> System.err.println("Error: " + error),
|
||||
() -> System.out.println("Complete")
|
||||
);
|
||||
```
|
||||
|
||||
### Get Event Hub Properties
|
||||
|
||||
```java
|
||||
// Get hub info
|
||||
EventHubProperties hubProps = producer.getEventHubProperties();
|
||||
System.out.println("Hub: " + hubProps.getName());
|
||||
System.out.println("Partitions: " + hubProps.getPartitionIds());
|
||||
|
||||
// Get partition info
|
||||
PartitionProperties partitionProps = producer.getPartitionProperties("0");
|
||||
System.out.println("Begin sequence: " + partitionProps.getBeginningSequenceNumber());
|
||||
System.out.println("Last sequence: " + partitionProps.getLastEnqueuedSequenceNumber());
|
||||
System.out.println("Last offset: " + partitionProps.getLastEnqueuedOffset());
|
||||
```
|
||||
|
||||
## Event Positions
|
||||
|
||||
```java
|
||||
// Start from beginning
|
||||
EventPosition.earliest()
|
||||
|
||||
// Start from end (new events only)
|
||||
EventPosition.latest()
|
||||
|
||||
// From specific offset
|
||||
EventPosition.fromOffset(12345L)
|
||||
|
||||
// From specific sequence number
|
||||
EventPosition.fromSequenceNumber(100L)
|
||||
|
||||
// From specific time
|
||||
EventPosition.fromEnqueuedTime(Instant.now().minus(Duration.ofHours(1)))
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.messaging.eventhubs.models.ErrorContext;
|
||||
|
||||
.processError(errorContext -> {
|
||||
Throwable error = errorContext.getThrowable();
|
||||
String partitionId = errorContext.getPartitionContext().getPartitionId();
|
||||
|
||||
if (error instanceof AmqpException) {
|
||||
AmqpException amqpError = (AmqpException) error;
|
||||
if (amqpError.isTransient()) {
|
||||
System.out.println("Transient error, will retry");
|
||||
}
|
||||
}
|
||||
|
||||
System.err.printf("Error on partition %s: %s%n", partitionId, error.getMessage());
|
||||
})
|
||||
```
|
||||
|
||||
## Resource Cleanup
|
||||
|
||||
```java
|
||||
// Always close clients
|
||||
try {
|
||||
producer.send(batch);
|
||||
} finally {
|
||||
producer.close();
|
||||
}
|
||||
|
||||
// Or use try-with-resources
|
||||
try (EventHubProducerClient producer = new EventHubClientBuilder()
|
||||
.connectionString(connectionString, eventHubName)
|
||||
.buildProducerClient()) {
|
||||
producer.send(events);
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
EVENT_HUBS_CONNECTION_STRING=Endpoint=sb://<namespace>.servicebus.windows.net/;SharedAccessKeyName=...
|
||||
EVENT_HUBS_NAME=<event-hub-name>
|
||||
STORAGE_CONNECTION_STRING=<for-checkpointing>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use EventProcessorClient**: For production, provides load balancing and checkpointing
|
||||
2. **Batch Events**: Use `EventDataBatch` for efficient sending
|
||||
3. **Partition Keys**: Use for ordering guarantees within a partition
|
||||
4. **Checkpointing**: Checkpoint after processing to avoid reprocessing
|
||||
5. **Error Handling**: Handle transient errors with retries
|
||||
6. **Close Clients**: Always close producer/consumer when done
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "Event Hubs Java"
|
||||
- "event streaming Azure"
|
||||
- "real-time data ingestion"
|
||||
- "EventProcessorClient"
|
||||
- "event hub producer consumer"
|
||||
- "partition processing"
|
||||
302
skills/official/microsoft/java/messaging/webpubsub/SKILL.md
Normal file
302
skills/official/microsoft/java/messaging/webpubsub/SKILL.md
Normal file
@@ -0,0 +1,302 @@
|
||||
---
|
||||
name: azure-messaging-webpubsub-java
|
||||
description: Build real-time web applications with Azure Web PubSub SDK for Java. Use when implementing WebSocket-based messaging, live updates, chat applications, or server-to-client push notifications.
|
||||
package: com.azure:azure-messaging-webpubsub
|
||||
---
|
||||
|
||||
# Azure Web PubSub SDK for Java
|
||||
|
||||
Build real-time web applications using the Azure Web PubSub SDK for Java.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-messaging-webpubsub</artifactId>
|
||||
<version>1.5.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### With Connection String
|
||||
|
||||
```java
|
||||
import com.azure.messaging.webpubsub.WebPubSubServiceClient;
|
||||
import com.azure.messaging.webpubsub.WebPubSubServiceClientBuilder;
|
||||
|
||||
WebPubSubServiceClient client = new WebPubSubServiceClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.hub("chat")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### With Access Key
|
||||
|
||||
```java
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
|
||||
WebPubSubServiceClient client = new WebPubSubServiceClientBuilder()
|
||||
.credential(new AzureKeyCredential("<access-key>"))
|
||||
.endpoint("<endpoint>")
|
||||
.hub("chat")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### With DefaultAzureCredential
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
|
||||
WebPubSubServiceClient client = new WebPubSubServiceClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.endpoint("<endpoint>")
|
||||
.hub("chat")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### Async Client
|
||||
|
||||
```java
|
||||
import com.azure.messaging.webpubsub.WebPubSubServiceAsyncClient;
|
||||
|
||||
WebPubSubServiceAsyncClient asyncClient = new WebPubSubServiceClientBuilder()
|
||||
.connectionString("<connection-string>")
|
||||
.hub("chat")
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Hub**: Logical isolation unit for connections
|
||||
- **Group**: Subset of connections within a hub
|
||||
- **Connection**: Individual WebSocket client connection
|
||||
- **User**: Entity that can have multiple connections
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Send to All Connections
|
||||
|
||||
```java
|
||||
import com.azure.messaging.webpubsub.models.WebPubSubContentType;
|
||||
|
||||
// Send text message
|
||||
client.sendToAll("Hello everyone!", WebPubSubContentType.TEXT_PLAIN);
|
||||
|
||||
// Send JSON
|
||||
String jsonMessage = "{\"type\": \"notification\", \"message\": \"New update!\"}";
|
||||
client.sendToAll(jsonMessage, WebPubSubContentType.APPLICATION_JSON);
|
||||
```
|
||||
|
||||
### Send to All with Filter
|
||||
|
||||
```java
|
||||
import com.azure.core.http.rest.RequestOptions;
|
||||
import com.azure.core.util.BinaryData;
|
||||
|
||||
BinaryData message = BinaryData.fromString("Hello filtered users!");
|
||||
|
||||
// Filter by userId
|
||||
client.sendToAllWithResponse(
|
||||
message,
|
||||
WebPubSubContentType.TEXT_PLAIN,
|
||||
message.getLength(),
|
||||
new RequestOptions().addQueryParam("filter", "userId ne 'user1'"));
|
||||
|
||||
// Filter by groups
|
||||
client.sendToAllWithResponse(
|
||||
message,
|
||||
WebPubSubContentType.TEXT_PLAIN,
|
||||
message.getLength(),
|
||||
new RequestOptions().addQueryParam("filter", "'GroupA' in groups and not('GroupB' in groups)"));
|
||||
```
|
||||
|
||||
### Send to Group
|
||||
|
||||
```java
|
||||
// Send to all connections in a group
|
||||
client.sendToGroup("java-developers", "Hello Java devs!", WebPubSubContentType.TEXT_PLAIN);
|
||||
|
||||
// Send JSON to group
|
||||
String json = "{\"event\": \"update\", \"data\": {\"version\": \"2.0\"}}";
|
||||
client.sendToGroup("subscribers", json, WebPubSubContentType.APPLICATION_JSON);
|
||||
```
|
||||
|
||||
### Send to Specific Connection
|
||||
|
||||
```java
|
||||
// Send to a specific connection by ID
|
||||
client.sendToConnection("connectionId123", "Private message", WebPubSubContentType.TEXT_PLAIN);
|
||||
```
|
||||
|
||||
### Send to User
|
||||
|
||||
```java
|
||||
// Send to all connections for a specific user
|
||||
client.sendToUser("andy", "Hello Andy!", WebPubSubContentType.TEXT_PLAIN);
|
||||
```
|
||||
|
||||
### Manage Groups
|
||||
|
||||
```java
|
||||
// Add connection to group
|
||||
client.addConnectionToGroup("premium-users", "connectionId123");
|
||||
|
||||
// Remove connection from group
|
||||
client.removeConnectionFromGroup("premium-users", "connectionId123");
|
||||
|
||||
// Add user to group (all their connections)
|
||||
client.addUserToGroup("admin-group", "userId456");
|
||||
|
||||
// Remove user from group
|
||||
client.removeUserFromGroup("admin-group", "userId456");
|
||||
|
||||
// Check if user is in group
|
||||
boolean exists = client.userExistsInGroup("admin-group", "userId456");
|
||||
```
|
||||
|
||||
### Manage Connections
|
||||
|
||||
```java
|
||||
// Check if connection exists
|
||||
boolean connected = client.connectionExists("connectionId123");
|
||||
|
||||
// Close a connection
|
||||
client.closeConnection("connectionId123");
|
||||
|
||||
// Close with reason
|
||||
client.closeConnection("connectionId123", "Session expired");
|
||||
|
||||
// Check if user exists (has any connections)
|
||||
boolean userOnline = client.userExists("userId456");
|
||||
|
||||
// Close all connections for a user
|
||||
client.closeUserConnections("userId456");
|
||||
|
||||
// Close all connections in a group
|
||||
client.closeGroupConnections("inactive-group");
|
||||
```
|
||||
|
||||
### Generate Client Access Token
|
||||
|
||||
```java
|
||||
import com.azure.messaging.webpubsub.models.GetClientAccessTokenOptions;
|
||||
import com.azure.messaging.webpubsub.models.WebPubSubClientAccessToken;
|
||||
|
||||
// Basic token
|
||||
WebPubSubClientAccessToken token = client.getClientAccessToken(
|
||||
new GetClientAccessTokenOptions());
|
||||
System.out.println("URL: " + token.getUrl());
|
||||
|
||||
// With user ID
|
||||
WebPubSubClientAccessToken userToken = client.getClientAccessToken(
|
||||
new GetClientAccessTokenOptions().setUserId("user123"));
|
||||
|
||||
// With roles (permissions)
|
||||
WebPubSubClientAccessToken roleToken = client.getClientAccessToken(
|
||||
new GetClientAccessTokenOptions()
|
||||
.setUserId("user123")
|
||||
.addRole("webpubsub.joinLeaveGroup")
|
||||
.addRole("webpubsub.sendToGroup"));
|
||||
|
||||
// With groups to join on connect
|
||||
WebPubSubClientAccessToken groupToken = client.getClientAccessToken(
|
||||
new GetClientAccessTokenOptions()
|
||||
.setUserId("user123")
|
||||
.addGroup("announcements")
|
||||
.addGroup("updates"));
|
||||
|
||||
// With custom expiration
|
||||
WebPubSubClientAccessToken expToken = client.getClientAccessToken(
|
||||
new GetClientAccessTokenOptions()
|
||||
.setUserId("user123")
|
||||
.setExpiresAfter(Duration.ofHours(2)));
|
||||
```
|
||||
|
||||
### Grant/Revoke Permissions
|
||||
|
||||
```java
|
||||
import com.azure.messaging.webpubsub.models.WebPubSubPermission;
|
||||
|
||||
// Grant permission to send to a group
|
||||
client.grantPermission(
|
||||
WebPubSubPermission.SEND_TO_GROUP,
|
||||
"connectionId123",
|
||||
new RequestOptions().addQueryParam("targetName", "chat-room"));
|
||||
|
||||
// Revoke permission
|
||||
client.revokePermission(
|
||||
WebPubSubPermission.SEND_TO_GROUP,
|
||||
"connectionId123",
|
||||
new RequestOptions().addQueryParam("targetName", "chat-room"));
|
||||
|
||||
// Check permission
|
||||
boolean hasPermission = client.checkPermission(
|
||||
WebPubSubPermission.SEND_TO_GROUP,
|
||||
"connectionId123",
|
||||
new RequestOptions().addQueryParam("targetName", "chat-room"));
|
||||
```
|
||||
|
||||
### Async Operations
|
||||
|
||||
```java
|
||||
asyncClient.sendToAll("Async message!", WebPubSubContentType.TEXT_PLAIN)
|
||||
.subscribe(
|
||||
unused -> System.out.println("Message sent"),
|
||||
error -> System.err.println("Error: " + error.getMessage())
|
||||
);
|
||||
|
||||
asyncClient.sendToGroup("developers", "Group message", WebPubSubContentType.TEXT_PLAIN)
|
||||
.doOnSuccess(v -> System.out.println("Sent to group"))
|
||||
.doOnError(e -> System.err.println("Failed: " + e))
|
||||
.subscribe();
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
client.sendToConnection("invalid-id", "test", WebPubSubContentType.TEXT_PLAIN);
|
||||
} catch (HttpResponseException e) {
|
||||
System.out.println("Status: " + e.getResponse().getStatusCode());
|
||||
System.out.println("Error: " + e.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
WEB_PUBSUB_CONNECTION_STRING=Endpoint=https://<resource>.webpubsub.azure.com;AccessKey=...
|
||||
WEB_PUBSUB_ENDPOINT=https://<resource>.webpubsub.azure.com
|
||||
WEB_PUBSUB_ACCESS_KEY=<your-access-key>
|
||||
```
|
||||
|
||||
## Client Roles
|
||||
|
||||
| Role | Permission |
|
||||
|------|------------|
|
||||
| `webpubsub.joinLeaveGroup` | Join/leave any group |
|
||||
| `webpubsub.sendToGroup` | Send to any group |
|
||||
| `webpubsub.joinLeaveGroup.<group>` | Join/leave specific group |
|
||||
| `webpubsub.sendToGroup.<group>` | Send to specific group |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Groups**: Organize connections into groups for targeted messaging
|
||||
2. **User IDs**: Associate connections with user IDs for user-level messaging
|
||||
3. **Token Expiration**: Set appropriate token expiration for security
|
||||
4. **Roles**: Grant minimal required permissions via roles
|
||||
5. **Hub Isolation**: Use separate hubs for different application features
|
||||
6. **Connection Management**: Clean up inactive connections
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
- "Web PubSub Java"
|
||||
- "WebSocket messaging Azure"
|
||||
- "real-time push notifications"
|
||||
- "server-sent events"
|
||||
- "chat application backend"
|
||||
- "live updates broadcasting"
|
||||
230
skills/official/microsoft/java/monitoring/ingestion/SKILL.md
Normal file
230
skills/official/microsoft/java/monitoring/ingestion/SKILL.md
Normal file
@@ -0,0 +1,230 @@
|
||||
---
|
||||
name: azure-monitor-ingestion-java
|
||||
description: |
|
||||
Azure Monitor Ingestion SDK for Java. Send custom logs to Azure Monitor via Data Collection Rules (DCR) and Data Collection Endpoints (DCE).
|
||||
Triggers: "LogsIngestionClient java", "azure monitor ingestion java", "custom logs java", "DCR java", "data collection rule java".
|
||||
package: com.azure:azure-monitor-ingestion
|
||||
---
|
||||
|
||||
# Azure Monitor Ingestion SDK for Java
|
||||
|
||||
Client library for sending custom logs to Azure Monitor using the Logs Ingestion API via Data Collection Rules.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-monitor-ingestion</artifactId>
|
||||
<version>1.2.11</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Or use Azure SDK BOM:
|
||||
|
||||
```xml
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-sdk-bom</artifactId>
|
||||
<version>{bom_version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-monitor-ingestion</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Data Collection Endpoint (DCE)
|
||||
- Data Collection Rule (DCR)
|
||||
- Log Analytics workspace
|
||||
- Target table (custom or built-in: CommonSecurityLog, SecurityEvents, Syslog, WindowsEvents)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
DATA_COLLECTION_ENDPOINT=https://<dce-name>.<region>.ingest.monitor.azure.com
|
||||
DATA_COLLECTION_RULE_ID=dcr-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
STREAM_NAME=Custom-MyTable_CL
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### Synchronous Client
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredential;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
import com.azure.monitor.ingestion.LogsIngestionClient;
|
||||
import com.azure.monitor.ingestion.LogsIngestionClientBuilder;
|
||||
|
||||
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
|
||||
|
||||
LogsIngestionClient client = new LogsIngestionClientBuilder()
|
||||
.endpoint("<data-collection-endpoint>")
|
||||
.credential(credential)
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### Asynchronous Client
|
||||
|
||||
```java
|
||||
import com.azure.monitor.ingestion.LogsIngestionAsyncClient;
|
||||
|
||||
LogsIngestionAsyncClient asyncClient = new LogsIngestionClientBuilder()
|
||||
.endpoint("<data-collection-endpoint>")
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| Data Collection Endpoint (DCE) | Ingestion endpoint URL for your region |
|
||||
| Data Collection Rule (DCR) | Defines data transformation and routing to tables |
|
||||
| Stream Name | Target stream in the DCR (e.g., `Custom-MyTable_CL`) |
|
||||
| Log Analytics Workspace | Destination for ingested logs |
|
||||
|
||||
## Core Operations
|
||||
|
||||
### Upload Custom Logs
|
||||
|
||||
```java
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
List<Object> logs = new ArrayList<>();
|
||||
logs.add(new MyLogEntry("2024-01-15T10:30:00Z", "INFO", "Application started"));
|
||||
logs.add(new MyLogEntry("2024-01-15T10:30:05Z", "DEBUG", "Processing request"));
|
||||
|
||||
client.upload("<data-collection-rule-id>", "<stream-name>", logs);
|
||||
System.out.println("Logs uploaded successfully");
|
||||
```
|
||||
|
||||
### Upload with Concurrency
|
||||
|
||||
For large log collections, enable concurrent uploads:
|
||||
|
||||
```java
|
||||
import com.azure.monitor.ingestion.models.LogsUploadOptions;
|
||||
import com.azure.core.util.Context;
|
||||
|
||||
List<Object> logs = getLargeLogs(); // Large collection
|
||||
|
||||
LogsUploadOptions options = new LogsUploadOptions()
|
||||
.setMaxConcurrency(3);
|
||||
|
||||
client.upload("<data-collection-rule-id>", "<stream-name>", logs, options, Context.NONE);
|
||||
```
|
||||
|
||||
### Upload with Error Handling
|
||||
|
||||
Handle partial upload failures gracefully:
|
||||
|
||||
```java
|
||||
LogsUploadOptions options = new LogsUploadOptions()
|
||||
.setLogsUploadErrorConsumer(uploadError -> {
|
||||
System.err.println("Upload error: " + uploadError.getResponseException().getMessage());
|
||||
System.err.println("Failed logs count: " + uploadError.getFailedLogs().size());
|
||||
|
||||
// Option 1: Log and continue
|
||||
// Option 2: Throw to abort remaining uploads
|
||||
// throw uploadError.getResponseException();
|
||||
});
|
||||
|
||||
client.upload("<data-collection-rule-id>", "<stream-name>", logs, options, Context.NONE);
|
||||
```
|
||||
|
||||
### Async Upload with Reactor
|
||||
|
||||
```java
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
List<Object> logs = getLogs();
|
||||
|
||||
asyncClient.upload("<data-collection-rule-id>", "<stream-name>", logs)
|
||||
.doOnSuccess(v -> System.out.println("Upload completed"))
|
||||
.doOnError(e -> System.err.println("Upload failed: " + e.getMessage()))
|
||||
.subscribe();
|
||||
```
|
||||
|
||||
## Log Entry Model Example
|
||||
|
||||
```java
|
||||
public class MyLogEntry {
|
||||
private String timeGenerated;
|
||||
private String level;
|
||||
private String message;
|
||||
|
||||
public MyLogEntry(String timeGenerated, String level, String message) {
|
||||
this.timeGenerated = timeGenerated;
|
||||
this.level = level;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
// Getters required for JSON serialization
|
||||
public String getTimeGenerated() { return timeGenerated; }
|
||||
public String getLevel() { return level; }
|
||||
public String getMessage() { return message; }
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
|
||||
try {
|
||||
client.upload(ruleId, streamName, logs);
|
||||
} catch (HttpResponseException e) {
|
||||
System.err.println("HTTP Status: " + e.getResponse().getStatusCode());
|
||||
System.err.println("Error: " + e.getMessage());
|
||||
|
||||
if (e.getResponse().getStatusCode() == 403) {
|
||||
System.err.println("Check DCR permissions and managed identity");
|
||||
} else if (e.getResponse().getStatusCode() == 404) {
|
||||
System.err.println("Verify DCE endpoint and DCR ID");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Batch logs** — Upload in batches rather than one at a time
|
||||
2. **Use concurrency** — Set `maxConcurrency` for large uploads
|
||||
3. **Handle partial failures** — Use error consumer to log failed entries
|
||||
4. **Match DCR schema** — Log entry fields must match DCR transformation expectations
|
||||
5. **Include TimeGenerated** — Most tables require a timestamp field
|
||||
6. **Reuse client** — Create once, reuse throughout application
|
||||
7. **Use async for high throughput** — `LogsIngestionAsyncClient` for reactive patterns
|
||||
|
||||
## Querying Uploaded Logs
|
||||
|
||||
Use [azure-monitor-query](../query/SKILL.md) to query ingested logs:
|
||||
|
||||
```java
|
||||
// See azure-monitor-query skill for LogsQueryClient usage
|
||||
String query = "MyTable_CL | where TimeGenerated > ago(1h) | limit 10";
|
||||
```
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| Maven Package | https://central.sonatype.com/artifact/com.azure/azure-monitor-ingestion |
|
||||
| GitHub | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/monitor/azure-monitor-ingestion |
|
||||
| Product Docs | https://learn.microsoft.com/azure/azure-monitor/logs/logs-ingestion-api-overview |
|
||||
| DCE Overview | https://learn.microsoft.com/azure/azure-monitor/essentials/data-collection-endpoint-overview |
|
||||
| DCR Overview | https://learn.microsoft.com/azure/azure-monitor/essentials/data-collection-rule-overview |
|
||||
| Troubleshooting | https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/monitor/azure-monitor-ingestion/TROUBLESHOOTING.md |
|
||||
@@ -0,0 +1,284 @@
|
||||
---
|
||||
name: azure-monitor-opentelemetry-exporter-java
|
||||
description: |
|
||||
Azure Monitor OpenTelemetry Exporter for Java. Export OpenTelemetry traces, metrics, and logs to Azure Monitor/Application Insights.
|
||||
Triggers: "AzureMonitorExporter java", "opentelemetry azure java", "application insights java otel", "azure monitor tracing java".
|
||||
Note: This package is DEPRECATED. Migrate to azure-monitor-opentelemetry-autoconfigure.
|
||||
package: com.azure:azure-monitor-opentelemetry-exporter
|
||||
---
|
||||
|
||||
# Azure Monitor OpenTelemetry Exporter for Java
|
||||
|
||||
> **⚠️ DEPRECATION NOTICE**: This package is deprecated. Migrate to `azure-monitor-opentelemetry-autoconfigure`.
|
||||
>
|
||||
> See [Migration Guide](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/MIGRATION.md) for detailed instructions.
|
||||
|
||||
Export OpenTelemetry telemetry data to Azure Monitor / Application Insights.
|
||||
|
||||
## Installation (Deprecated)
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-monitor-opentelemetry-exporter</artifactId>
|
||||
<version>1.0.0-beta.x</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Recommended: Use Autoconfigure Instead
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-monitor-opentelemetry-autoconfigure</artifactId>
|
||||
<version>LATEST</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=xxx;IngestionEndpoint=https://xxx.in.applicationinsights.azure.com/
|
||||
```
|
||||
|
||||
## Basic Setup with Autoconfigure (Recommended)
|
||||
|
||||
### Using Environment Variable
|
||||
|
||||
```java
|
||||
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
|
||||
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder;
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import com.azure.monitor.opentelemetry.exporter.AzureMonitorExporter;
|
||||
|
||||
// Connection string from APPLICATIONINSIGHTS_CONNECTION_STRING env var
|
||||
AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder();
|
||||
AzureMonitorExporter.customize(sdkBuilder);
|
||||
OpenTelemetry openTelemetry = sdkBuilder.build().getOpenTelemetrySdk();
|
||||
```
|
||||
|
||||
### With Explicit Connection String
|
||||
|
||||
```java
|
||||
AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder();
|
||||
AzureMonitorExporter.customize(sdkBuilder, "{connection-string}");
|
||||
OpenTelemetry openTelemetry = sdkBuilder.build().getOpenTelemetrySdk();
|
||||
```
|
||||
|
||||
## Creating Spans
|
||||
|
||||
```java
|
||||
import io.opentelemetry.api.trace.Tracer;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.context.Scope;
|
||||
|
||||
// Get tracer
|
||||
Tracer tracer = openTelemetry.getTracer("com.example.myapp");
|
||||
|
||||
// Create span
|
||||
Span span = tracer.spanBuilder("myOperation").startSpan();
|
||||
|
||||
try (Scope scope = span.makeCurrent()) {
|
||||
// Your application logic
|
||||
doWork();
|
||||
} catch (Throwable t) {
|
||||
span.recordException(t);
|
||||
throw t;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
```
|
||||
|
||||
## Adding Span Attributes
|
||||
|
||||
```java
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
|
||||
Span span = tracer.spanBuilder("processOrder")
|
||||
.setAttribute("order.id", "12345")
|
||||
.setAttribute("customer.tier", "premium")
|
||||
.startSpan();
|
||||
|
||||
try (Scope scope = span.makeCurrent()) {
|
||||
// Add attributes during execution
|
||||
span.setAttribute("items.count", 3);
|
||||
span.setAttribute("total.amount", 99.99);
|
||||
|
||||
processOrder();
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Span Processor
|
||||
|
||||
```java
|
||||
import io.opentelemetry.sdk.trace.SpanProcessor;
|
||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||
import io.opentelemetry.context.Context;
|
||||
|
||||
private static final AttributeKey<String> CUSTOM_ATTR = AttributeKey.stringKey("custom.attribute");
|
||||
|
||||
SpanProcessor customProcessor = new SpanProcessor() {
|
||||
@Override
|
||||
public void onStart(Context context, ReadWriteSpan span) {
|
||||
// Add custom attribute to every span
|
||||
span.setAttribute(CUSTOM_ATTR, "customValue");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStartRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(ReadableSpan span) {
|
||||
// Post-processing if needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEndRequired() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Register processor
|
||||
AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder();
|
||||
AzureMonitorExporter.customize(sdkBuilder);
|
||||
|
||||
sdkBuilder.addTracerProviderCustomizer(
|
||||
(sdkTracerProviderBuilder, configProperties) ->
|
||||
sdkTracerProviderBuilder.addSpanProcessor(customProcessor)
|
||||
);
|
||||
|
||||
OpenTelemetry openTelemetry = sdkBuilder.build().getOpenTelemetrySdk();
|
||||
```
|
||||
|
||||
## Nested Spans
|
||||
|
||||
```java
|
||||
public void parentOperation() {
|
||||
Span parentSpan = tracer.spanBuilder("parentOperation").startSpan();
|
||||
try (Scope scope = parentSpan.makeCurrent()) {
|
||||
childOperation();
|
||||
} finally {
|
||||
parentSpan.end();
|
||||
}
|
||||
}
|
||||
|
||||
public void childOperation() {
|
||||
// Automatically links to parent via Context
|
||||
Span childSpan = tracer.spanBuilder("childOperation").startSpan();
|
||||
try (Scope scope = childSpan.makeCurrent()) {
|
||||
// Child work
|
||||
} finally {
|
||||
childSpan.end();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Recording Exceptions
|
||||
|
||||
```java
|
||||
Span span = tracer.spanBuilder("riskyOperation").startSpan();
|
||||
try (Scope scope = span.makeCurrent()) {
|
||||
performRiskyWork();
|
||||
} catch (Exception e) {
|
||||
span.recordException(e);
|
||||
span.setStatus(StatusCode.ERROR, e.getMessage());
|
||||
throw e;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
```
|
||||
|
||||
## Metrics (via OpenTelemetry)
|
||||
|
||||
```java
|
||||
import io.opentelemetry.api.metrics.Meter;
|
||||
import io.opentelemetry.api.metrics.LongCounter;
|
||||
import io.opentelemetry.api.metrics.LongHistogram;
|
||||
|
||||
Meter meter = openTelemetry.getMeter("com.example.myapp");
|
||||
|
||||
// Counter
|
||||
LongCounter requestCounter = meter.counterBuilder("http.requests")
|
||||
.setDescription("Total HTTP requests")
|
||||
.setUnit("requests")
|
||||
.build();
|
||||
|
||||
requestCounter.add(1, Attributes.of(
|
||||
AttributeKey.stringKey("http.method"), "GET",
|
||||
AttributeKey.longKey("http.status_code"), 200L
|
||||
));
|
||||
|
||||
// Histogram
|
||||
LongHistogram latencyHistogram = meter.histogramBuilder("http.latency")
|
||||
.setDescription("Request latency")
|
||||
.setUnit("ms")
|
||||
.ofLongs()
|
||||
.build();
|
||||
|
||||
latencyHistogram.record(150, Attributes.of(
|
||||
AttributeKey.stringKey("http.route"), "/api/users"
|
||||
));
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| Connection String | Application Insights connection string with instrumentation key |
|
||||
| Tracer | Creates spans for distributed tracing |
|
||||
| Span | Represents a unit of work with timing and attributes |
|
||||
| SpanProcessor | Intercepts span lifecycle for customization |
|
||||
| Exporter | Sends telemetry to Azure Monitor |
|
||||
|
||||
## Migration to Autoconfigure
|
||||
|
||||
The `azure-monitor-opentelemetry-autoconfigure` package provides:
|
||||
- Automatic instrumentation of common libraries
|
||||
- Simplified configuration
|
||||
- Better integration with OpenTelemetry SDK
|
||||
|
||||
### Migration Steps
|
||||
|
||||
1. Replace dependency:
|
||||
```xml
|
||||
<!-- Remove -->
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-monitor-opentelemetry-exporter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Add -->
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-monitor-opentelemetry-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2. Update initialization code per [Migration Guide](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/MIGRATION.md)
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use autoconfigure** — Migrate to `azure-monitor-opentelemetry-autoconfigure`
|
||||
2. **Set meaningful span names** — Use descriptive operation names
|
||||
3. **Add relevant attributes** — Include contextual data for debugging
|
||||
4. **Handle exceptions** — Always record exceptions on spans
|
||||
5. **Use semantic conventions** — Follow OpenTelemetry semantic conventions
|
||||
6. **End spans in finally** — Ensure spans are always ended
|
||||
7. **Use try-with-resources** — Scope management with try-with-resources pattern
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| Maven Package | https://central.sonatype.com/artifact/com.azure/azure-monitor-opentelemetry-exporter |
|
||||
| GitHub | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/monitor/azure-monitor-opentelemetry-exporter |
|
||||
| Migration Guide | https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/MIGRATION.md |
|
||||
| Autoconfigure Package | https://central.sonatype.com/artifact/com.azure/azure-monitor-opentelemetry-autoconfigure |
|
||||
| OpenTelemetry Java | https://opentelemetry.io/docs/languages/java/ |
|
||||
| Application Insights | https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview |
|
||||
417
skills/official/microsoft/java/monitoring/query/SKILL.md
Normal file
417
skills/official/microsoft/java/monitoring/query/SKILL.md
Normal file
@@ -0,0 +1,417 @@
|
||||
---
|
||||
name: azure-monitor-query-java
|
||||
description: |
|
||||
Azure Monitor Query SDK for Java. Execute Kusto queries against Log Analytics workspaces and query metrics from Azure resources.
|
||||
Triggers: "LogsQueryClient java", "MetricsQueryClient java", "kusto query java", "log analytics java", "azure monitor query java".
|
||||
Note: This package is deprecated. Migrate to azure-monitor-query-logs and azure-monitor-query-metrics.
|
||||
package: com.azure:azure-monitor-query
|
||||
---
|
||||
|
||||
# Azure Monitor Query SDK for Java
|
||||
|
||||
> **DEPRECATION NOTICE**: This package is deprecated in favor of:
|
||||
> - `azure-monitor-query-logs` — For Log Analytics queries
|
||||
> - `azure-monitor-query-metrics` — For metrics queries
|
||||
>
|
||||
> See migration guides: [Logs Migration](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/monitor/azure-monitor-query-logs/migration-guide.md) | [Metrics Migration](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/monitor/azure-monitor-query-metrics/migration-guide.md)
|
||||
|
||||
Client library for querying Azure Monitor Logs and Metrics.
|
||||
|
||||
## Installation
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-monitor-query</artifactId>
|
||||
<version>1.5.9</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Or use Azure SDK BOM:
|
||||
|
||||
```xml
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-sdk-bom</artifactId>
|
||||
<version>{bom_version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-monitor-query</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Log Analytics workspace (for logs queries)
|
||||
- Azure resource (for metrics queries)
|
||||
- TokenCredential with appropriate permissions
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
LOG_ANALYTICS_WORKSPACE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
AZURE_RESOURCE_ID=/subscriptions/{sub}/resourceGroups/{rg}/providers/{provider}/{resource}
|
||||
```
|
||||
|
||||
## Client Creation
|
||||
|
||||
### LogsQueryClient (Sync)
|
||||
|
||||
```java
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
import com.azure.monitor.query.LogsQueryClient;
|
||||
import com.azure.monitor.query.LogsQueryClientBuilder;
|
||||
|
||||
LogsQueryClient logsClient = new LogsQueryClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### LogsQueryAsyncClient
|
||||
|
||||
```java
|
||||
import com.azure.monitor.query.LogsQueryAsyncClient;
|
||||
|
||||
LogsQueryAsyncClient logsAsyncClient = new LogsQueryClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
### MetricsQueryClient (Sync)
|
||||
|
||||
```java
|
||||
import com.azure.monitor.query.MetricsQueryClient;
|
||||
import com.azure.monitor.query.MetricsQueryClientBuilder;
|
||||
|
||||
MetricsQueryClient metricsClient = new MetricsQueryClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
### MetricsQueryAsyncClient
|
||||
|
||||
```java
|
||||
import com.azure.monitor.query.MetricsQueryAsyncClient;
|
||||
|
||||
MetricsQueryAsyncClient metricsAsyncClient = new MetricsQueryClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.buildAsyncClient();
|
||||
```
|
||||
|
||||
### Sovereign Cloud Configuration
|
||||
|
||||
```java
|
||||
// Azure China Cloud - Logs
|
||||
LogsQueryClient logsClient = new LogsQueryClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.endpoint("https://api.loganalytics.azure.cn/v1")
|
||||
.buildClient();
|
||||
|
||||
// Azure China Cloud - Metrics
|
||||
MetricsQueryClient metricsClient = new MetricsQueryClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.endpoint("https://management.chinacloudapi.cn")
|
||||
.buildClient();
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| Logs | Log and performance data from Azure resources via Kusto Query Language |
|
||||
| Metrics | Numeric time-series data collected at regular intervals |
|
||||
| Workspace ID | Log Analytics workspace identifier |
|
||||
| Resource ID | Azure resource URI for metrics queries |
|
||||
| QueryTimeInterval | Time range for the query |
|
||||
|
||||
## Logs Query Operations
|
||||
|
||||
### Basic Query
|
||||
|
||||
```java
|
||||
import com.azure.monitor.query.models.LogsQueryResult;
|
||||
import com.azure.monitor.query.models.LogsTableRow;
|
||||
import com.azure.monitor.query.models.QueryTimeInterval;
|
||||
import java.time.Duration;
|
||||
|
||||
LogsQueryResult result = logsClient.queryWorkspace(
|
||||
"{workspace-id}",
|
||||
"AzureActivity | summarize count() by ResourceGroup | top 10 by count_",
|
||||
new QueryTimeInterval(Duration.ofDays(7))
|
||||
);
|
||||
|
||||
for (LogsTableRow row : result.getTable().getRows()) {
|
||||
System.out.println(row.getColumnValue("ResourceGroup") + ": " + row.getColumnValue("count_"));
|
||||
}
|
||||
```
|
||||
|
||||
### Query by Resource ID
|
||||
|
||||
```java
|
||||
LogsQueryResult result = logsClient.queryResource(
|
||||
"{resource-id}",
|
||||
"AzureMetrics | where TimeGenerated > ago(1h)",
|
||||
new QueryTimeInterval(Duration.ofDays(1))
|
||||
);
|
||||
|
||||
for (LogsTableRow row : result.getTable().getRows()) {
|
||||
System.out.println(row.getColumnValue("MetricName") + " " + row.getColumnValue("Average"));
|
||||
}
|
||||
```
|
||||
|
||||
### Map Results to Custom Model
|
||||
|
||||
```java
|
||||
// Define model class
|
||||
public class ActivityLog {
|
||||
private String resourceGroup;
|
||||
private String operationName;
|
||||
|
||||
public String getResourceGroup() { return resourceGroup; }
|
||||
public String getOperationName() { return operationName; }
|
||||
}
|
||||
|
||||
// Query with model mapping
|
||||
List<ActivityLog> logs = logsClient.queryWorkspace(
|
||||
"{workspace-id}",
|
||||
"AzureActivity | project ResourceGroup, OperationName | take 100",
|
||||
new QueryTimeInterval(Duration.ofDays(2)),
|
||||
ActivityLog.class
|
||||
);
|
||||
|
||||
for (ActivityLog log : logs) {
|
||||
System.out.println(log.getOperationName() + " - " + log.getResourceGroup());
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Query
|
||||
|
||||
```java
|
||||
import com.azure.monitor.query.models.LogsBatchQuery;
|
||||
import com.azure.monitor.query.models.LogsBatchQueryResult;
|
||||
import com.azure.monitor.query.models.LogsBatchQueryResultCollection;
|
||||
import com.azure.core.util.Context;
|
||||
|
||||
LogsBatchQuery batchQuery = new LogsBatchQuery();
|
||||
String q1 = batchQuery.addWorkspaceQuery("{workspace-id}", "AzureActivity | count", new QueryTimeInterval(Duration.ofDays(1)));
|
||||
String q2 = batchQuery.addWorkspaceQuery("{workspace-id}", "Heartbeat | count", new QueryTimeInterval(Duration.ofDays(1)));
|
||||
String q3 = batchQuery.addWorkspaceQuery("{workspace-id}", "Perf | count", new QueryTimeInterval(Duration.ofDays(1)));
|
||||
|
||||
LogsBatchQueryResultCollection results = logsClient
|
||||
.queryBatchWithResponse(batchQuery, Context.NONE)
|
||||
.getValue();
|
||||
|
||||
LogsBatchQueryResult result1 = results.getResult(q1);
|
||||
LogsBatchQueryResult result2 = results.getResult(q2);
|
||||
LogsBatchQueryResult result3 = results.getResult(q3);
|
||||
|
||||
// Check for failures
|
||||
if (result3.getQueryResultStatus() == LogsQueryResultStatus.FAILURE) {
|
||||
System.err.println("Query failed: " + result3.getError().getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
### Query with Options
|
||||
|
||||
```java
|
||||
import com.azure.monitor.query.models.LogsQueryOptions;
|
||||
import com.azure.core.http.rest.Response;
|
||||
|
||||
LogsQueryOptions options = new LogsQueryOptions()
|
||||
.setServerTimeout(Duration.ofMinutes(10))
|
||||
.setIncludeStatistics(true)
|
||||
.setIncludeVisualization(true);
|
||||
|
||||
Response<LogsQueryResult> response = logsClient.queryWorkspaceWithResponse(
|
||||
"{workspace-id}",
|
||||
"AzureActivity | summarize count() by bin(TimeGenerated, 1h)",
|
||||
new QueryTimeInterval(Duration.ofDays(7)),
|
||||
options,
|
||||
Context.NONE
|
||||
);
|
||||
|
||||
LogsQueryResult result = response.getValue();
|
||||
|
||||
// Access statistics
|
||||
BinaryData statistics = result.getStatistics();
|
||||
// Access visualization data
|
||||
BinaryData visualization = result.getVisualization();
|
||||
```
|
||||
|
||||
### Query Multiple Workspaces
|
||||
|
||||
```java
|
||||
import java.util.Arrays;
|
||||
|
||||
LogsQueryOptions options = new LogsQueryOptions()
|
||||
.setAdditionalWorkspaces(Arrays.asList("{workspace-id-2}", "{workspace-id-3}"));
|
||||
|
||||
Response<LogsQueryResult> response = logsClient.queryWorkspaceWithResponse(
|
||||
"{workspace-id-1}",
|
||||
"AzureActivity | summarize count() by TenantId",
|
||||
new QueryTimeInterval(Duration.ofDays(1)),
|
||||
options,
|
||||
Context.NONE
|
||||
);
|
||||
```
|
||||
|
||||
## Metrics Query Operations
|
||||
|
||||
### Basic Metrics Query
|
||||
|
||||
```java
|
||||
import com.azure.monitor.query.models.MetricsQueryResult;
|
||||
import com.azure.monitor.query.models.MetricResult;
|
||||
import com.azure.monitor.query.models.TimeSeriesElement;
|
||||
import com.azure.monitor.query.models.MetricValue;
|
||||
import java.util.Arrays;
|
||||
|
||||
MetricsQueryResult result = metricsClient.queryResource(
|
||||
"{resource-uri}",
|
||||
Arrays.asList("SuccessfulCalls", "TotalCalls")
|
||||
);
|
||||
|
||||
for (MetricResult metric : result.getMetrics()) {
|
||||
System.out.println("Metric: " + metric.getMetricName());
|
||||
for (TimeSeriesElement ts : metric.getTimeSeries()) {
|
||||
System.out.println(" Dimensions: " + ts.getMetadata());
|
||||
for (MetricValue value : ts.getValues()) {
|
||||
System.out.println(" " + value.getTimeStamp() + ": " + value.getTotal());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Metrics with Aggregations
|
||||
|
||||
```java
|
||||
import com.azure.monitor.query.models.MetricsQueryOptions;
|
||||
import com.azure.monitor.query.models.AggregationType;
|
||||
|
||||
Response<MetricsQueryResult> response = metricsClient.queryResourceWithResponse(
|
||||
"{resource-id}",
|
||||
Arrays.asList("SuccessfulCalls", "TotalCalls"),
|
||||
new MetricsQueryOptions()
|
||||
.setGranularity(Duration.ofHours(1))
|
||||
.setAggregations(Arrays.asList(AggregationType.AVERAGE, AggregationType.COUNT)),
|
||||
Context.NONE
|
||||
);
|
||||
|
||||
MetricsQueryResult result = response.getValue();
|
||||
```
|
||||
|
||||
### Query Multiple Resources (MetricsClient)
|
||||
|
||||
```java
|
||||
import com.azure.monitor.query.MetricsClient;
|
||||
import com.azure.monitor.query.MetricsClientBuilder;
|
||||
import com.azure.monitor.query.models.MetricsQueryResourcesResult;
|
||||
|
||||
MetricsClient metricsClient = new MetricsClientBuilder()
|
||||
.credential(new DefaultAzureCredentialBuilder().build())
|
||||
.endpoint("{endpoint}")
|
||||
.buildClient();
|
||||
|
||||
MetricsQueryResourcesResult result = metricsClient.queryResources(
|
||||
Arrays.asList("{resourceId1}", "{resourceId2}"),
|
||||
Arrays.asList("{metric1}", "{metric2}"),
|
||||
"{metricNamespace}"
|
||||
);
|
||||
|
||||
for (MetricsQueryResult queryResult : result.getMetricsQueryResults()) {
|
||||
for (MetricResult metric : queryResult.getMetrics()) {
|
||||
System.out.println(metric.getMetricName());
|
||||
metric.getTimeSeries().stream()
|
||||
.flatMap(ts -> ts.getValues().stream())
|
||||
.forEach(mv -> System.out.println(
|
||||
mv.getTimeStamp() + " Count=" + mv.getCount() + " Avg=" + mv.getAverage()));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response Structure
|
||||
|
||||
### Logs Response Hierarchy
|
||||
|
||||
```
|
||||
LogsQueryResult
|
||||
├── statistics (BinaryData)
|
||||
├── visualization (BinaryData)
|
||||
├── error
|
||||
└── tables (List<LogsTable>)
|
||||
├── name
|
||||
├── columns (List<LogsTableColumn>)
|
||||
│ ├── name
|
||||
│ └── type
|
||||
└── rows (List<LogsTableRow>)
|
||||
├── rowIndex
|
||||
└── rowCells (List<LogsTableCell>)
|
||||
```
|
||||
|
||||
### Metrics Response Hierarchy
|
||||
|
||||
```
|
||||
MetricsQueryResult
|
||||
├── granularity
|
||||
├── timeInterval
|
||||
├── namespace
|
||||
├── resourceRegion
|
||||
└── metrics (List<MetricResult>)
|
||||
├── id, name, type, unit
|
||||
└── timeSeries (List<TimeSeriesElement>)
|
||||
├── metadata (dimensions)
|
||||
└── values (List<MetricValue>)
|
||||
├── timeStamp
|
||||
├── count, average, total
|
||||
├── maximum, minimum
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```java
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
import com.azure.monitor.query.models.LogsQueryResultStatus;
|
||||
|
||||
try {
|
||||
LogsQueryResult result = logsClient.queryWorkspace(workspaceId, query, timeInterval);
|
||||
|
||||
// Check partial failure
|
||||
if (result.getStatus() == LogsQueryResultStatus.PARTIAL_FAILURE) {
|
||||
System.err.println("Partial failure: " + result.getError().getMessage());
|
||||
}
|
||||
} catch (HttpResponseException e) {
|
||||
System.err.println("Query failed: " + e.getMessage());
|
||||
System.err.println("Status: " + e.getResponse().getStatusCode());
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use batch queries** — Combine multiple queries into a single request
|
||||
2. **Set appropriate timeouts** — Long queries may need extended server timeout
|
||||
3. **Limit result size** — Use `top` or `take` in Kusto queries
|
||||
4. **Use projections** — Select only needed columns with `project`
|
||||
5. **Check query status** — Handle PARTIAL_FAILURE results gracefully
|
||||
6. **Cache results** — Metrics don't change frequently; cache when appropriate
|
||||
7. **Migrate to new packages** — Plan migration to `azure-monitor-query-logs` and `azure-monitor-query-metrics`
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| Maven Package | https://central.sonatype.com/artifact/com.azure/azure-monitor-query |
|
||||
| GitHub | https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/monitor/azure-monitor-query |
|
||||
| API Reference | https://learn.microsoft.com/java/api/com.azure.monitor.query |
|
||||
| Kusto Query Language | https://learn.microsoft.com/azure/data-explorer/kusto/query/ |
|
||||
| Log Analytics Limits | https://learn.microsoft.com/azure/azure-monitor/service-limits#la-query-api |
|
||||
| Troubleshooting | https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/monitor/azure-monitor-query/TROUBLESHOOTING.md |
|
||||
Reference in New Issue
Block a user